PowerShell Invoke-Command: מדריך מקיף

מקצוענים בתחום הטכנולוגיה לעיתים קרובות אינם עובדים רק על המחשב המקומי שלנו. באמצעות הפקודה Invoke-Command של PowerShell, אנו לא צריכים לעשות זאת! פקודה זו מאפשרת לנו לכתוב קוד בצורה שוטפת כאילו אנו עובדים על המחשב המקומי שלנו.

על ידי שימוש בתכונת Remoting של PowerShell, פקודת Invoke-Command היא פקודת PowerShell נפוצה המאפשרת למשתמש לבצע קוד בתוך PSSession. ה- PSSession יכול להיות אחד שנוצר מראש באמצעות פקודת New-PSSession, או שהוא יכול ליצור ולהפריד במהירות גם מושב זמני.

קשור: Remoting של PowerShell: מדריך המתקדם

חשבו על Invoke-Command כמו על ה־psexec של PowerShell. אף על פי שהם מיושמים בדרכים שונות, המושג הוא אותו הדבר. לקחו קוד או פקודה והריצו אותו "מקומית" על המחשב המרוחק.

כדי ש- Invoke-Command יעבוד, עליכם לוודא שה־PowerShell Remoting מופעל וזמין על המחשב המרוחק. כברירת מחדל, כל מכונת Windows Server 2012 R2 או מאוחר יותר מצוידות בו ובחריגי הגנה מתאימים. אם יש לכם מכונות Server 2008 עדיין, קיימות דרכים מרובות להגדיר Remoting אך דרך פשוטה היא על ידי הרצת winrm quickconfig או Enable-PSRemoting על המחשב המרוחק.

כדי להדגים איך Invoke-Command עובד עם "פקודה רגעית" שאינה דורשת יצירת PSSession חדש, נניח שיש לך מחשב מרוחק מצורף לדומיין של Windows Server 2012 R2 או מאוחר יותר. הדברים מתערערים קצת כשעובדים עם מחשבים בקבוצת עבודה. אני אפתח את חלון הפקודה של PowerShell שלי, אכתוב Invoke-Command ואז אני אלחץ Enter.

PS> Invoke-Command
cmdlet Invoke-Command at command pipeline position 1
Supply values for the following parameters:
ScriptBlock:

I’m immediately asked to provide a scriptblock. The scriptblock is the code that we’re going to run on the remote computer.

כדי שנוכל להוכיח שהקוד בתוך ה-scriptblock מתבצע על המחשב המרוחק, בוא נריץ פשוט את הפקודה hostname. פקודה זו תחזיר את שם המחשב עליו היא מופעלת. ריצת הפקודה hostname על המחשב המקומי שלי מחזירה את שמו.

PS> hostname
MACWINVM

בוא נעביר עכשיו scriptblock עם אותו הקוד בתוך scriptblock ל-Invoke-Command. אך לפני שנעשה זאת, אנחנו שוכחים פרמטר חובה: ComputerName. אנחנו חייבים לספר ל-Invoke-Command איזה מחשב מרוחק להריץ פקודה זו עליו.

PS> Invoke-Command -ScriptBlock { hostname } -ComputerName WEBSRV1 WEBSRV1

שים לב שפלט הפקודה hostname כעת הוא שם המחשב המרוחק WEBSRV1. הרצת קוד פשוט בתוך scriptblock והעברה למחשב מרוחק יחיד היא האפליקציה הקלה ביותר של Invoke-Command אך היא יכולה לעשות הרבה יותר.

מעבר משתנים מקומיים ל-scriptblocks מרוחקים

אתה לא תמצא הפניה אחת בלבד ל-Invoke-Command בתוך סקריפט. הסקריפט שלך כנראה יהיה מלא בעשרות שורות, יהיו לו משתנים שמוגדרים במקומות שונים, ופונקציות שמוגדרות במודולים וכו '. אפילו אם תכסה קוד מסוים במספר סוגריים מסולסלים, אתה למעשה משנה את הטווח של כל הקוד הזה שרץ בו. לסוף סוף, אתה שולח את הקוד הזה למחשב מרוחק. למחשב המרוחק אין מושג על כל הקוד המקומי במכונה שלך חוץ ממה שנמצא בבלוק הסקריפט.

לדוגמה, אולי יש לך פונקציה עם שם מחשב ופרמטר של נתיב קובץ. תפקידה של פונקציה זו הוא להפעיל מתקין תוכנה מסוים במחשב המרוחק. אתה יכול למסור את שם המחשב ואת נתיב הקובץ "המקומי" למתקין שכבר נמצא במחשב המרוחק.

הפונקציה למטה נראית סבירה, נכון? ננסה להריץ אותה.

function Install-Stuff {
    param(
    	[Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ComputerName,
        
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$InstallerFilePath
	)
    Invoke-Command -ComputerName $ComputerName -ScriptBlock { & $InstallerFilePath }
}

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
The expression after '&' in a pipeline element produced an object that was not valid. It must result in a command name, a script block, or a CommandInfo object.
+ CategoryInfo          : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : BadExpression
+ PSComputerName        : websrv1

הרץ נכשל עם הודעת שגיאה מעורפלת עקב שימוש שלי באופרטור ampersand. הקוד לא היה שגוי, אבל נכשל כי $InstallerFilePath היה ריק למרות שמסרת ערך עם פרמטר הפונקציה. אנו יכולים לבדוק זאת על ידי החלפת ה-ampersand עם Write-Host.

שורת הפונקציה החדשה: Invoke-Command -ComputerName $ComputerName -ScriptBlock { Write-Host "נתיב המתקין הוא: $InstallerFilePath" }

PS> Install-Stuff -ComputerName websrv1 -InstallerFilePath 'C:\install.exe'
Installer path is:
PS>

שים לב שערך ה־$InstallerFilePath הוא של ריק. המשתנה לא התרחב כי לא הועבר למכונה המרוחקת. כדי להעביר משתנים מקומיים ל־scriptblock הרחוק, יש לנו שתי אפשרויות; אפשר להקדים את שם המשתנה בתוך $using: בתוך ה־scriptblock או להשתמש בפרמטר ArgumentList של Invoke-Command. בואו נסתכל על שניהם.

הפרמטר ArgumentList

דרך אחת להעביר משתנים מקומיים ל־scriptblock רחוק היא להשתמש בפרמטר ArgumentList של Invoke-Command. פרמטר זה מאפשר לך להעביר משתנים מקומיים לפרמטר ולהחליף הפניות למשתנים מקומיים ב־scriptblock עם מצייני מקום.

העברת המשתנים המקומיים לפרמטר ArgumentList קלה.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $InstallerFilePath } -ArgumentList $InstallerFilePath

החלק שמביא לקושי אצל חלק מהאנשים הוא איך למבנה את המשתנים בתוך ה־scriptblock. במקום להשתמש ב־{ & $InstallerPath }, עלינו לשנות אותו כך שיהיה או { & $args[0] } או {param($foo) & $foo }. שתי הדרכים עובדות באותו אופן אך איזו מהן כדאי להשתמש?

הפרמטר ArgumentList הוא אוסף אובייקטים. אוספי אובייקטים מאפשרים לך להעביר ערכים אחדים או יותר בכל פעם. במקרה זה, אני מעביר רק אחד.

כאשר בוצע, cmdlet ה-Invoke-Command לוקח את האוסף הזה ואז מכניס אותו לתוך ה- scriptblock, שבעצם משנה אותו למערך הנקרא `$args`. זכור ש- `$args -eq ArgumentList`. בנקודה זו, תתייחס לכל איבר באוסף בדיוק כמו שתעשה עם מערך. במקרה שלנו, הייתה לנו רק רכיב אחד באוסף (`$InstallerFilePath`), ש "תורגם" ל- `$args[0]`, כלומר האינדקס הראשון באוסף זה. אם יש לך יותר, תתייחס אליהם עם `$args[1]`, `$args[2]` וכו'

בנוסף, אם תרצה להקצות שמות משתנים טובים יותר למשתני scriptblock, תוכל גם להוסיף פרמטרים לתוך ה- scriptblock בדיוק כמו בפונקציה. לאחר הכתיבה, תתייחס לפרמטר הזה ב- scriptblock כמו שמוצג למטה.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { param($foo) & $foo } -ArgumentList $InstallerFilePath

במקרה זה, האיברים באוסף `ArgumentList` "ממופים" לפרמטרים שהוגדרו לפי הסדר. שמות הפרמטרים לא חשובים; הסדר הוא החשוב. `Invoke-Command` יקח את האיבר הראשון באוסף `ArgumentList`, יחפש את הפרמטר הראשון וימפה את הערכים, יעשה אותו דבר עבור השני, השלישי וכן הלאה.

ה-$Using Construct

המבנה $using הוא דרך פופולרית נוספת להעברת משתנים מקומיים לתוך בלוק קוד רחוק. מבנה זה מאפשר לך להשתמש מחדש במשתנים המקומיים הקיימים פשוט על ידי הוספת קידומת של $using: לשם המשתנה. אין צורך לדאוג לאוסף $args או להוסיף בלוק פרמטרים.

Invoke-Command -ComputerName WEBSRV1 -ScriptBlock { & $using:InstallerFilePath }

המבנה $using בפוֹוּרֶשֶׁל הכוח של PowerShell הוא הרבה פשוט יותר, אך אם תתחיל ללמוד Pester, תגלה ש־ArgumentList יהיה החבר שלך.

Invoke-Command ו־New-PSSession

מבחינה טכנית, הפוסט הזה הוא רק על Invoke-Command אך כדי להדגיש את תועלתו, אנו צריכים להגיע בקצרה אל פקודת New-PSSession גם. זכור שכבר קדמתי ש־Invoke-Command יכול להשתמש בפקודות "אד-הוק" או להשתמש בהפעלת הסשן הקיים.

במהלך הפוסט הזה, אנו פשוט מריצים פקודות "אד-הוק" על מחשבים מרוחקים. אנו מקימים סשן חדש, מריצים קוד ומתפרקים אותו. זה תקין למצבים חד פעמיים אך לא כל כך לעת שבה אתה מבצע עשרות פקודות על אותו מחשב. במקרה זה, עדיף להשתמש מחדש בסשן PSSession קיים על ידי יצירתו מראש באמצעות New-PSSession.

לפני הרצת כל פקודות, תצטרך ליצור PSSession עם New-PSSession. נוכל לעשות זאת פשוט על ידי הרצת $session = New-PSSession -ComputerName WEBSRV1. זה יוצר סשן רחוק בשרת וגם הפניה אל הסשן הזה במכשיר המקומי שלי. בנקודה זו, אני יכול להחליף את ההפניות של ComputerName עם Session ולהפנות את Session למשתנה שמור שלי, $session.

Invoke-Command -Session $session -ScriptBlock { & $using:InstallerFilePath }

כאשר מורץ, תשימו לב שהביצועים מהירים יותר מכיוון שהסשן כבר נבנה. לאחר השלמת הפעולות, חשוב להסיר את הסשן הפתוח עם Remove-PSSession.

סיכום

Cmdlet של PowerShell Invoke-Command הוא אחד מה-Cmdlets הכי נפוצים והכי עוצמתיים שקיימים. זהו אחד מהם שאני אישית משתמש בו הכי הרבה. קלות השימוש שלו ויכולתו להריץ כל קוד על מחשבים רחוקים היא עוצמה נפלאה וזו פקודה שאני ממליץ ללמוד מעל לתחתית!

Source:
https://adamtheautomator.com/invoke-command/