BESTNET TECH BLOG
AI एजेंट को Windows की वास्तविक मशीन पर काम सौंपकर ― SSH गैर-संवादात्मकता, वर्ण-कोड, T-SQL के "जोड़ों" पर ठोकर खाने का युद्ध-अनुभव
श्रृंखला के मुख्य भाग में, Oracle 19c से SQL Server 2022 की माइग्रेशन प्रीप्रोसेसिंग को AI कोडिंग एजेंट (Claude Code) को वास्तविक डेटा साझा किए बिना स्वचालित करके, लक्ष्य परिवेश की वास्तविक मशीन पर अनुप्रयोग/फ़ीचर-सत्यापन तक पूरा करने की रिपोर्ट दी थी।
यह लेख उसका पर्दे-के-पीछे का हिस्सा है। AI एजेंट को "वास्तविक मशीन पर काम" सौंपने पर, अड़चन आमतौर पर AI की प्रतिभा में नहीं, बल्कि AI और शेल और OS के वर्ण-कोड और टूल के "जोड़ों" पर आती थी। SSH की गैर-संवादात्मकता, Windows की वर्ण-विकृति, cmd की क्वोटिंग, कोड से T-SQL डालना — ये सब अकेले-अकेले मामूली हैं, पर स्वचालन को रोकने के लिए पर्याप्त। युद्ध में खाई गई ठोकरों में से, इस लेख में तकनीकी-झुकाव वाली 10 को, लक्षण·कारण·उपाय के रूप में दर्ज करते हैं (बाकी 2 AI के सुरक्षा-तंत्र से जुड़ी गवर्नेंस की बात है, इसलिए विशेषांक भाग 2 "AI गार्डरेल" में)। इसी प्रकार के स्वचालन पर विचार करने वालों के कुछ घंटे बचा सकें तो खुशी होगी।
इस लेख के कमांड-उदाहरणों में, कनेक्शन-गंतव्य और प्रमाणीकरण-जानकारी को पूरी तरह प्लेसहोल्डर (
USER@HOST,-pw '****',C:\work) से बदल दिया गया है।
1. कनेक्शन — हैंड्स-फ़्री SSH को स्थापित करना #
AI एजेंट से काम कराने की पूर्वधारणा के रूप में, मानव द्वारा एक बार भी पासवर्ड टाइप किए बिना SSH कमांड का चलना आवश्यक है। यहीं तुरंत 3।
① OpenSSH पासवर्ड को गैर-संवादात्मक रूप से नहीं दे सकता #
- लक्षण:
ssh user@host "コマンド"पासवर्ड-इनपुट के इंतज़ार में रुक जाता है। स्क्रिप्ट से स्वतः निष्पादित नहीं होता। - कारण: OpenSSH क्लाइंट सुरक्षा कारणों से, पासवर्ड को केवल टर्मिनल (tty) से ही पढ़ता है। स्टैंडर्ड इनपुट में पाइप करने पर भी नहीं लेता। Windows में
sshpassभी मानक रूप में नहीं है। - उपाय: PuTTY के
plinkका उपयोग करें और-pwसे पासवर्ड दें।
plink -ssh -pw '****' USER@HOST "whoami"
सुरक्षा की दृष्टि से, प्रोडक्शन परिचालन में सार्वजनिक-कुंजी प्रमाणीकरण (कुंजी-जोड़ी +
pageantया-i) को सर्वप्रथम विचार करें।-pwका कमांड-लाइन से देना प्रोसेस-सूची या शेल-इतिहास में रह सकता है, इसलिए मजबूरीवश उपयोग करने पर भी डिस्पोज़ेबल + कार्य के बाद रोटेशन पूर्वधारणा है।
② plink की पहली बार की होस्ट-कुंजी प्रॉम्प्ट पर हैंग हो जाता है #
- लक्षण:
echo y | plink ...करने पर भी आगे नहीं बढ़ता। टाइमआउट होने तक कोई प्रतिक्रिया नहीं। - कारण: plink पहली बार के कनेक्शन पर कुंजी-कैश की पुष्टि "Store key in cache? (y/n)" को टर्मिनल (tty) से पढ़ता है। AI एजेंट के निष्पादन जैसे tty-रहित परिवेश (पाइप / प्रोसेस-स्टार्ट के माध्यम से) में, पाइप किए गए
yको नहीं ले पाता और हैंग हो जाता है। संवादात्मक कंसोल परecho y | plinkके काम करने वाली संरचना भी होती है, पर स्वतः-निष्पादन में भरोसेमंद नहीं। - उपाय: पहले
ssh-keyscanसे होस्ट-कुंजी लें, और SHA256 फ़िंगरप्रिंट को-hostkeyसे पिन कर दें।-batchसे प्रॉम्प्ट को ही निष्क्रिय करें।
# 1) ホスト鍵のフィンガープリントを取得
ssh-keyscan -T 6 HOST 2>/dev/null | ssh-keygen -lf -
# => 256 SHA256:<fingerprint> <host> (...) のように、鍵種ごとに1行ずつ出る
# 2) 取得値を -hostkey で固定し、-batch で非対話接続
# サーバが複数の鍵種(ED25519/RSA等)を返す環境では、ネゴシエートされる鍵に備え各 -hostkey を列挙する
plink -ssh -hostkey SHA256:<fingerprint> -batch -pw '****' USER@HOST "コマンド"
पिनिंग सुरक्षा की दृष्टि से भी प्लस है (मैन-इन-द-मिडल हमले के विरुद्ध, अपेक्षित कुंजी के अलावा को अस्वीकार करता है)।
③ लंबे-समय वाले कमांड को "फेंककर इंतज़ार करना" वाला डिज़ाइन बनाएँ #
- लक्षण: ISO डाउनलोड या उत्पाद-इंस्टॉलेशन जैसी लंबी प्रोसेस को सिंक्रोनस निष्पादित करने पर, टूल-पक्ष के टाइमआउट से टकराकर कट जाता है।
- कारण: SSH सेशन पकड़े हुए 10 मिनट से अधिक ब्लॉक करने वाली प्रोसेस, एजेंट की निष्पादन-सीमा में आसानी से फँस जाती है।
- उपाय: लंबी प्रोसेस को बैकग्राउंड में स्टार्ट करें, और पूर्णता को सेंटिनेल-फ़ाइल या समाप्ति-सूचना से उठाएँ। SSH सेशन को लंबे समय तक न घेरें।
:: 非同期起動し、完了時に終了コードをセンチネルへ(同一 cmd /c 内なので & は逐次評価される)
start "" /b cmd /c "installer.exe /quiet & echo DONE=%errorlevel%> C:\work\done.txt"
:: 別コマンドで完了確認: if exist C:\work\done.txt type C:\work\done.txt
2. वर्ण-कोड — CP932 और BOM का दोहरा दाँव #
जापानी Windows से सामना करने पर, वर्ण-विकृति "दिखावट की समस्या" नहीं, बल्कि प्रोसेस को तोड़ने वाला बग बन जाती है।
④ रिमोट आउटपुट Shift-JIS में गड़बड़ा जाता है #
- लक्षण: कमांड-परिणाम
�w�肳�ꂽ�p�X...जैसी वर्ण-विकृति में अपठनीय हो जाता है। - कारण: जापानी Windows CP932 (Shift-JIS) में आउटपुट करता है। UTF-8 मानने वाले टर्मिनल को मिलने पर गड़बड़ा जाता है।
- उपाय: रिमोट-पक्ष कमांड के आरंभ में
chcp 65001(UTF-8 में स्विच)। PowerShell में आउटपुट एन्कोडिंग भी स्पष्ट करें।
chcp 65001>nul & 後続コマンド
[Console]::OutputEncoding = [Text.UTF8Encoding]::new()
⑤ PowerShell 5.1 BOM-रहित .ps1 को CP932 के रूप में पढ़ता है【सबसे ज़्यादा समय बर्बाद करने वाली अड़चन】 #
- लक्षण: स्क्रिप्ट के जापानी लिटरल (
'すべて'आदि)'鬟溷刀'जैसी अलग चीज़ में बदल जाते हैं, और'~' 付近に不適切な構文がありますके साथ SQL या प्रोसेस गिर जाती है। - कारण: PowerShell 5.1, BOM-रहित
.ps1को सिस्टम ANSI कोड-पेज (जापानी परिवेश में CP932) के रूप में व्याख्यायित करता है। UTF-8 में सहेजी स्क्रिप्ट का जापानी टूट जाता है। केवल-ASCII वाली स्क्रिप्ट अक्षत रहती है, इसलिए पता देर से चलना ही परेशानी है। - उपाय: जापानी सहित
.ps1को UTF-8 (BOM सहित) में सहेजें। PS5.1, BOM देखकर UTF-8 निर्धारित करता है।
# UTF-8 BOM付きで書き出す(既存のBOM無しUTF-8を変換する例)
$txt = [IO.File]::ReadAllText($src, [Text.UTF8Encoding]::new($false))
[IO.File]::WriteAllText($dst, $txt, [Text.UTF8Encoding]::new($true)) # $true = BOM付き
ये दोनों (④⑤) अलग परतों की समस्याएँ हैं (टर्मिनल-आउटपुट बनाम स्क्रिप्ट-रीडिंग), पर लक्षण एक-समान "वर्ण-विकृति" होने के कारण आसानी से गड्डमड्ड हो जाते हैं। आउटपुट के लिए chcp, इनपुट (स्क्रिप्ट) के लिए BOM — इस तरह अलग करके याद रखें तो जल्दी होता है।
3. निष्पादन-पैटर्न — cmd क्वोट-नरक से पलायन #
⑥ cmd के माध्यम से इनलाइन PowerShell | " ; पर टूट जाता है #
- लक्षण:
powershell -Command "Get-ChildItem | Select-Object Name"चलाने पर'Select-Object' は…認識されていません।"के नेस्टिंग पर भी बिगड़ जाता है। - कारण: SSH→cmd→PowerShell इस तरह बहु-स्तरीय रूप से गुज़रने के कारण,
|को cmd पाइप के रूप में पहले व्याख्यायित कर लेता है। डबल-क्वोट का नेस्टिंग भी cmd के स्तर पर टूट जाता है। स्टैंडर्ड इनपुट में डालने वाला तरीका (-Command -) भी, लंबी स्क्रिप्ट होने पर बीच में कट जाता था। - उपाय: इनलाइन छोड़कर, लोकल में
.ps1लिखें→pscpसे ट्रांसफ़र करें→powershell -NoProfile -Fileसे निष्पादित करें। इस कार्य के परिवेश (Windows Server 2022 का डिफ़ॉल्टRemoteSigned) में, लोकल की अहस्ताक्षरित.ps1जस की तस चल गई। इससे क्वोट-समस्या और स्टैंडर्ड-इनपुट-कटाव दोनों के अधिकांश हल हो जाते हैं।
इस कार्य में यह पैटर्न स्थिर रहा। "जटिल रिमोट प्रोसेस को इनलाइन में मेहनत न करके, फ़ाइल बनाकर भेजें और
-Fileसे चलाएँ"। एक्ज़ीक्यूशन-पॉलिसी कोBypassसे कमज़ोर करने की भी ज़रूरत नहीं (वह ऑपरेशन उल्टा सुरक्षा-तंत्र द्वारा रोका गया था — विशेषांक भाग 2 देखें)।हालाँकि पूर्वधारणा पर ध्यान देना आवश्यक है।
RemoteSignedमें ट्रांसफ़र-स्क्रिप्ट चलेगी या नहीं, यह संगठन की ग्रुप-पॉलिसी या हस्ताक्षर-पॉलिसी (AllSigned / Constrained Language Mode / AppLocker・WDAC आदि) पर बदलता है। साथ ही "pscp-ट्रांसफ़र फ़ाइल पर MOTW (Mark of the Web) नहीं लगता = हस्ताक्षर-गार्ड प्रभावी नहीं होता" — यह लाभ नहीं, बल्कि एक ट्रेड-ऑफ़ है, और तृतीय-पक्ष-स्रोत स्क्रिप्ट में उल्टा जोखिम है। कंपनी के भीतर सामग्री को समझे हुए टूल होने के कारण स्वीकार किया, यह पूर्वधारणा न भूलें।
pscp -hostkey SHA256:xxxx -batch -pw '****' .\task.ps1 USER@HOST:C:/work/task.ps1
plink -ssh -hostkey SHA256:xxxx -batch -pw '****' USER@HOST "powershell -NoProfile -File C:\work\task.ps1"
4. SQL डालना — कोड से T-SQL चलाते समय की अड़चनें #
स्कीमा DDL को, sqlcmd के बिना System.Data.SqlClient (.NET मानक की SQL Server कनेक्शन लाइब्रेरी। sqlcmd या SSMS के बिना एप्लिकेशन से सीधे DDL/DML जारी कर सकती है, पर इसके बदले sqlcmd-विशिष्ट फ़ीचर = GO विभाजक आदि नहीं रखती) से, एकीकृत प्रमाणीकरण द्वारा सीधे चलाने पर की 3 लगातार अड़चनें।
⑦ GO विभाजन, टिप्पणी के भीतर के GO से टूट जाता है #
- लक्षण: DDL को बैच निष्पादित करने पर
コメントの終了マーク '*/' がありませんया'=' 付近に不適切な構文के साथ कुछ बैच गिर जाते हैं। - कारण:
System.Data.SqlClientGOको नहीं समझता (GOsqlcmd/SSMS का बैच-विभाजक है और T-SQL नहीं)। स्वयं विभाजित करना आवश्यक है, पर सरलता से^\s*GO\s*$से तोड़ने पर,/* … */टिप्पणी के भीतर इंडेंट के साथ लिखेGO(उदाहरण-कोड आदि) तक को विभाजित कर देता है और टिप्पणी खंडित हो जाती है। - उपाय: विभाजन केवल पंक्ति-आरंभ (कॉलम 0) के
GOपर करें। टिप्पणी के भीतर काGOयदि इंडेंटेड है (यह कोडबेस ऐसा ही था) तो इससे बाहर रखा जा सकता है। अधिक कड़ाई से "टिप्पणी और स्ट्रिंग-लिटरल को हटाकर फिरGOविभाजन" करना मूलतः उचित है।
# 列0のGOのみで分割(インデントされたコメント内GOは無視)
$batches = [regex]::Split($script, '(?im)^GO[ \t]*;?[ \t]*\r?$')
⑧ इंडेक्स्ड व्यू का निर्माण ARITHABORT से विफल हो जाता है #
- लक्षण:
CREATE UNIQUE CLUSTERED INDEX(इंडेक्स्ड व्यू बनाना)…SET options have incorrect settings: 'ARITHABORT'के साथ विफल। - कारण:
System.Data.SqlClientडिफ़ॉल्ट रूप सेARITHABORT OFF। इंडेक्स्ड व्यू के निर्माण के लिए कई SET-ऑप्शन का ON होना आवश्यक है। - उपाय: कनेक्शन के तुरंत बाद आवश्यक SET डालकर फिर DDL चलाएँ।
SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON;
SET ARITHABORT ON; SET CONCAT_NULL_YIELDS_NULL ON;
SET QUOTED_IDENTIFIER ON; SET NUMERIC_ROUNDABORT OFF;
⑨ संख्या-प्राप्ति पर 列名 'rows' が無効です #
- लक्षण: टेबल-वार संख्या को
SELECT SUM(p.rows) FROM sys.dm_db_partition_stats p …से लेने का प्रयास करने पर列名 'rows' が無効です(Invalid column name 'rows')के साथ गिर जाता है। - कारण:
sys.dm_db_partition_stats(DMV = डायनामिक मैनेजमेंट व्यू। सर्वर की आंतरिक स्थिति लौटाने वाला सिस्टम-व्यू) का पंक्ति-संख्या कॉलमrow_countहै,rowsनहीं।rowsकॉलम तोsys.partitionsके पास है। गड़बड़ा गया था। - उपाय: संख्या-एग्रीगेशन के लिए
sys.partitionsका उपयोग करें (यही प्रचलित है)।
SELECT t.name, ISNULL(SUM(p.rows), 0) AS rows
FROM sys.tables t
LEFT JOIN sys.partitions p ON p.object_id = t.object_id AND p.index_id IN (0,1)
WHERE t.is_ms_shipped = 0
GROUP BY t.name ORDER BY t.name;
5. सफ़ाई — मामूली पर स्वचालन को रोकने वाली अड़चनें #
⑩ Get-ChildItem -Exclude कुछ नहीं लौटाता, और सफ़ाई आगे नहीं बढ़ती #
- लक्षण:
Get-ChildItem 'C:\work' -Exclude 'keep.sql' | Remove-Item -Forceनिष्पादित करने पर भी 1 भी फ़ाइल नहीं मिटती (एरर भी नहीं आता)। - कारण: डायरेक्टरी-निर्दिष्ट
-Exclude, पथ के अंत में\*लगाए बिना या-Recurseसाथ उपयोग किए बिना, अपेक्षानुसार सूचीबद्ध न होकर खाली लौटा सकता है (PowerShell की ज्ञात आदत)।Remove-Itemको मिलने वाला इनपुट खाली होने के कारण, कुछ नहीं होता। - उपाय: आलस्य छोड़कर, लक्ष्य को स्पष्ट रूप से सूचीबद्ध करके
Remove-Item -LiteralPath -Force।
foreach ($f in 'a.exe','b.iso','c.ps1') {
Remove-Item -LiteralPath (Join-Path 'C:\work' $f) -Force -ErrorAction SilentlyContinue
}
निष्कर्ष — अड़चनें "प्रतिभा" में नहीं, "जोड़ों" पर निकलती हैं #
एक साथ रखकर देखें तो, AI एजेंट ने स्वयं जहाँ गलती की वह स्थान लगभग नहीं हैं। जहाँ अटका वह लगभग पूरा का पूरा जोड़ था।
- AI और शेल का जोड़ (cmd की क्वोट-व्याख्या, होस्ट-कुंजी प्रॉम्प्ट का इनपुट-मार्ग)
- शेल और OS का जोड़ (CP932, BOM)
- कोड और टूल का जोड़ (SqlClient
GOको नहीं जानता, DMV का कॉलम-नाम, ARITHABORT का डिफ़ॉल्ट मान)
उलटकर देखें तो, जोड़ों को जितना मानकीकृत करें, स्वचालन उतना स्थिर होता है (बेशक दूसरे जोड़ बचे रह सकते हैं)। इस कार्य का व्यावहारिक हल निम्नलिखित 3 बिंदुओं में सिमटा।
- कनेक्शन को
plink/pscp+ होस्ट-कुंजी पिनिंग +-batchसे हैंड्स-फ़्री करें - जटिल प्रोसेस को इनलाइन न लिखकर,
.ps1भेजकर-Fileसे निष्पादित करें (इस कार्य केRemoteSignedपरिवेश में स्थापित। संगठन की पॉलिसी के अनुसार पुष्टि आवश्यक) - वर्ण-कोड को "आउटपुट = chcp 65001 / इनपुट (स्क्रिप्ट) = UTF-8 BOM" से अलग करें
ध्यान दें, इन्हें सुलझाने की प्रक्रिया में, AI एजेंट-पक्ष के सुरक्षा-तंत्र ने AI के अपने ऑपरेशन को 2 बार ब्लॉक करने का प्रसंग भी हुआ। एक्ज़ीक्यूशन-पॉलिसी कमज़ोर करने का प्रयास और प्रमाणीकरण-जानकारी को फ़ाइल में लिखने का प्रयास। यह "अड़चन" से अधिक गवर्नेंस की बात है, इसलिए विशेषांक भाग 2 "AI गार्डरेल" में संभाला है।