DCSIMG
December 2010 - Posts - Shay Levy

Shay Levy

If you repeat it, PowerShell it!

December 2010 - Posts

פוסט אורח - משככי כאבים למעברי שעון

כמו בכל שנה, (פעמיים בשנה!) מגיע הרגע בו אנו צריכים להתקין את עדכוני התוכנה לשעון קיץ/חורף על מחשבי הארגון. כמידי חורף, אנו נאלצים להתמודד עם שפעת ה DST שמכה בנו במלוא כוחה וגורמת לנו לכאבי ראש רבים. במיוחד, מכיוון שהיא פוגעת בלקוחות ה-VIP  של הארגון, וכשהם צועקים, כולנו יודעים שזה כואב.

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

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

ההתקנה היא רק השלב הראשון שכן אנו צריכים גם לוודא שהיא עברה בהצלחה. אני שמח לארח כאן בבלוג את דולב הדס (Dolav Hadas, המכונה במחוזותינו - "קוברה". ולא, הוא לא מכיש), חבר וקולגה ואיש סיסטם מהמבריקים שהכרתי! בין יתר הדברים, דולב אחראי על מעבר השעון אצלנו בארגון והוא יצר פתרון שאני מקווה שתוכלו לעשות בו שימוש מוצלח.

בפוסטים הבאים, דולב יכתב על עולם תוכן נוסף הקרוב לליבו, ה-Management וירחיב על הפתרונות אותם כתב וחיבר עבור ארגוננו, אשר לקחו אותו לחלל :) דוגמא אחת היא ....

 

לאחרונה השתתפתי בכנס של מיקרוסופט בנושא Daylight Saving ונתבקשתי על ידי מספר משתתפים לפרסם ADM שכתבתי לצורך הגדרת ה-DST במערכת ההפעלה. המטרה פשוטה, לאכוף את הגדרת ה-DST על כל המחשבים בארגון ולסמן את ה-√ להפעלת ה-Daylight Saving.

 

הגדרת GPO

את קובץ ה- ADM יש לשמור בספרית INF של מערכת ההפעלה (WINDIR%\Inf%) ולאחר מכן לצרפו ל-GPO

 

מאחר וה-Policy הנ"ל הינו שינוי Registry ולא הגדרת GPO אמיתית (אינה הגדרה שמאוחסנת ב- Policies Key) יש צורך לשנות את הגדרות התצוגה של ה-GPO

 

יש להסיר את ה-√ מ- ("Only show policy settings that can be fully managed")

 

עכשיו ניתן לראות בחלון הימני את גם את הגדרת ה-DST

 

ההגדרה ה"כחולה" הינה הגדרת Policy מלאה (רגילה) ולכן היא מסומנת בכחול. הגדרה זו מאפשרת לבטל את הצגת האפשרות למשתמש להזיז את פגישותיו (באאוטלוק) באופן עצמאי. ההגדרה ה"אדומה" הינה הגדרת “Registry” ולכן היא מסומנת באדום (ניתן לראותה רק לאחר הסרת ה-√ בשלב הקודם). הגדרה זו מאפשרת ומבטלת את ה-DST.

אם ברצונכם להכיל באופן מיידי את ה-Policy על כל הארגון, כדאי לכם לבחון את התוסף הקטן (חינמי) מבית SpecOps . זהו תוסף לממשק ה-AD שמאפשר לבצע GPUpdate על OU שלם (מבוסס PowerShell).

 

הפצה

למי שאין מנגנון הפצה, כגון SCCM, ניתן להפיץ את העדכון באמצעות PsExec. על-מנת להקל על התהליך בניתי Self-Extract ,שמריץ סקריפט לבדיקת סוג מערכת ההפעלה ומפיץ את העדכון המתאים לה.

DSTInstall.exe – קובץ זה מכיל סקריפט אשר בודק את סוג מערכת ההפעלה, Service Pack וארכיטקטורה של מחשב היעד, לפי תוצאות בדיקה זו, מותקן העדכון המתאים. חבילה זו מתאימה למערכות ההפעלה הבאות :

  • Windows 2000 – עדכון Registry, אין Patch.
  • Windows XP SP3
  • Windows XP SP 2 – עדכון של דצמבר 2009 (העדכון החדש לא תומך ב-SP2)
  • x86/x64 - Windows 2003
  • x86/x64 - Windows 2008
  • Windows2008 R2
  • ב- Windows 2000 ,Windows XP ו Windows 2003 Serverבתום ההתקנה, יופיע ה-Application Event הבא :

 

(במערכות הפעלה Windows 7 / Windows 2008 ומעלה כבר אין צורך להריץ Registry Update Refresh)

 

במערכות ההפעלה Windows XP ומעלה מתווסף ה-Application Event הבא :

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

psexec.exe -s -i -c –f path\DSTInstall.exe @Computers.txt

 

בסוף שורת הפקודה ניתן לציין נתיב לקובץ המכיל רשימה של מחשבים להם יופץ העדכון. ניתן להשתמש ב- RunHiddenConsole.exe על-מנת להחביא את ה-Command Console

RunHiddenConsole.exe psexec.exe -s -i -c –f path\DSTInstall.exe @ Computers.txt

 

 

חשוב לבדוק את המנגנון הנ"ל בסביבת טסט לפני הפעלתו בסביבת הייצור.

"למשועממים" שבינינו ,ניתן לעטוף את הפקודה הנ"ל (להוסיף בדיקות כגון בדיקת PING, בדיקה שהעדכון אכן הותקן וכדומה) בעזרת Powershellו- Windows Forms כדוגמת הממשק הבא (דוגמא בלבד, לא זמין להורדה):

9

 

 

בדיקה

באמצעות פקודת Get-Hotfix  של Powershell 2 ניתן לבדוק האם העדכון אכן הותקן, מקומית או על מחשב מרוחק. תחילה יש ליצור קובץ המכיל את שמות כל מחשבי הלקוח ולאחר מכן לנתב את הפלט ל- Get-Hotfix 

 

Get-Content D:\Computers.txt | Foreach-Object { Get-Hotfix -Id KB2443685 -ComputerName $_ }

 

על-מנת לבדוק את ההגדרות עצמן כתבתי פונקציה בשם Get-DSTSettings שבודק את הגדרות ה-DST על כל מחשב ומחשב. בנוסף לבדיקה זו ניתן להוסיף את המתג -Hotfix על-מנת לבדוק האם העדכון הותקן על המחשב הנבדק. דוגמאות לשימוש בפקודה :

הרצת הפקודה לבדיקת המחשב המקומי,Get-DSTSettings

 

מספר דוגמאות להרצת הפקודה עבור מספר מחשבים

'Server1','Server5','Server2','Server3 '| Get-DSTSettings | Format-Table –Property *

Get-Content D:\ComputersList.txt | Get-DSTSettings | Format-Table *

Get-DSTSettings - ComputerName Server1, Server2 | FT *

 

 

 

 

 

 

 

חשוב לזכור, אליבא דה צחי קולבר, אם תריצו את הפקודה עבור מחשבי Windows7,Windows2008 ומעלה התוצאות שיתקבלו יהיו תוצאות עבור 2010 ולא עבור 2011 היות ומערכות אלו פועלות בהתאם לכללי ה- Dynamic DST (רק מינואר יתקבלו התוצאות עבור 2011).

 

טעינת הפונקציה

ניתן ל"טעון" את הפונקציה לזיכרון במספר דרכים להלן שתיים:

אפשרות ראשונה היא להעתיק את הפונקציה (תוכן הסקריפט המצורף) ולהדביקה לפרופיל המשתמש של PowerShell. מעתה ואילך הפונקציה תהא זמינה בכל הפעלה של PowerShell.

אפשרות נוספת להריץ את ה-Script המצורף בעזרת Dot Source באופן הבא (יש להריץ שורת פקודה זו בכל פעם שפותחים את הקונסול) :

. D:\Get-DSTSettings.ps1

 

 

הקבצים המצורפים ניתנים להורדה מכאן

מומלץ בחום , לבצע בדיקות בסביבת טסט לפני שמריצים את הפקודות בסביבת הייצור.

 

TechEd Eilat 2010 Interview

During the TechEd Eilat event last month I had the honor to be interviewed (Hebrew) by Michal Gonen, IT Pro & DEV Audience Marketing at Microsoft Israel. Enjoy!

Invoking WMI methods in PowerShell

In PowerShell V1 if we wanted to invoke a method, the Win32_Volume WMI Format method in the following example, we used a syntax like:

PS > $vol = Get-WmiObject -Class Win32_Volume -Filter "DriveLetter='R:'"
PS > $vol.Format(...)

Note: The Win32_Volume class exists on Windows Server 2003 and above, on Windows XP this class is not available.

 

To determine the values we need to pass to the Format method we pipe the volume object to the Get-Member cmdlet and look at the method definition:

PS > $vol | Get-Member Format -MemberType Method | Format-List Definition

Definition : System.Management.ManagementBaseObject Format(System.String FileSystem, System.Boolean QuickFormat, System.UInt32 ClusterSize, System.String Label, System.Boolean EnableCompression, System.UInt32 Version)

 

We can see that the Format method expects six values: FileSystem, QuickFormat, ClusterSize, Label, EnableCompression and Version. Let's say we want the format the drive as NTFS, enable quick format, the Cluster disk size will be 4096, set the label to "New Volume", disable disk compression and lastly we pass a NULL value to the Version parameter (this will ignore the value of the parameter). When the method is executed the volume is formatted based on the values we supplied.

PS > $vol.Format('NTFS',$true,4096,'New Volume',$false,$null)

__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0

We get a ReturnValue of 0 meaning that the command completed successfully.

 

In PowerShell V2 we have a new way to invoke methods with the Invoke-WmiMethod cmdlet. Invoke-WmiMethod enables us to call WMI methods in a pipeline.

PS > Get-WmiObject -Class Win32_Volume -Filter "DriveLetter='R:'" | `
Invoke-WmiMethod -Name Format -ArgumentList 'NTFS',$true,4096,'New Volume',$false,$null

Nice, the syntax look much cleaner! We feed the name of the method we want to invoke to the Name parameter and supply the same list of arguments we used above (delimited by a comma) to the ArgumentList parameter. Alas, if we execute that command it will fail:

Invoke-WmiMethod : Input string was not in a correct format.

Ouch! What was that? Let's take a look at the help for the ArgumentList parameter:

PS > Get-Help Invoke-WmiMethod -Parameter ArgumentList

-ArgumentList <[Object[]]>
Specifies the parameters to pass to the called method. The value of this parameter must be an array of objects and they must appear in the order required by the called method.
...

 

The problem is the underlying WMI method expects to get the values we pass in a specific order and somehow the order we used doesn't satisfies the method, as if we passed a Boolean value to a parameter that expects a String. We can determine the order of values with the following command:

PS >  ([wmiclass]'Win32_Volume').GetMethodParameters('Format')

__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 6
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ClusterSize : 0
EnableCompression : False
FileSystem : NTFS
Label :
QuickFormat : False
Version : 0

 

Again, we need to send six values to the method but take a closer look at the order of the properties (bold). The order is different than the one we used in the V1 example. Lets re-arrange the values and try again:

PS > Get-WmiObject -Class Win32_Volume -Filter "DriveLetter='R:'" | `
Invoke-WmiMethod -Name Format -ArgumentList 4096,$false,'NTFS','New Volume',$true,$null



Invoke-WmiMethod : Unable to cast object of type 'System.Management.Automation.PSObject'
to type 'System.IConvertible'.


Now we get another error, different than the first one. There's a casting problem, according to the help of the ArgumentList parameter the value of the parameter must be an array of objects, so lets try to explicitly cast the values to an array (i.e with the Array '@()' notation):
PS > Get-WmiObject -Class Win32_Volume -Filter "DriveLetter='R:'" | `
Invoke-WmiMethod -Name Format -ArgumentList @(4096,$false,'NTFS','New Volume',$true,$null)


__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0

 

We get a ReturnValue of 0 and we know the formatting process was successful. The Format method is just an example and you can use the parameters discovery process outlined above for any WMI instance methods. I filed a connect bug so you might want to add your vote if you want the Invoke-WmiMethod to be fixed.

https://connect.microsoft.com/PowerShell/feedback/details/624263/invoking-wmi-methods-with-invoke-wmimethod

 

Now, if you decide to test the Format method make sure you use it on a test drive. For this post I created a new Virtual Hard Disk (VHD) on my Windows 7 machine and gave it the drive letter 'R' (Right Click the Disk Management node in the Computer Management MMC and then click the 'Create VHD' menu item).

Quick Tip: Out-File and IOPs – Position matters

Take a look at the following two examples. Both commands gets a list of log files, selects a subset of each file properties and logs them to a file.

 

Get-ChildItem -Filter *.log -Recurse | ForEach-Object {
    $_ | Select-Object Name,Length,FullName | Out-File -FilePath D:\LogFilesReport.txt -Append
}

Get-ChildItem -Filter *.log -Recurse | ForEach-Object {
    $_ | Select-Object Name,Length,FullName
} | Out-File -FilePath D:\LogFilesReport.txt –Append


At first glance both commands looks very similar but under the surface one of them is very inefficient and is generating A LOT of IO activity. Let’s assume that the results from the Get-ChildItem cmdlet is 1000 objects. To test this I used Sysinternals Process Monitor, ran each command and exported the results to a csv file. Let’s look at the numbers.

PS > $cmd1 = Import-Csv .\cmd1.CSV
PS > $cmd2 = Import-Csv .\cmd2.CSV

PS > ($cmd1 | Measure-Object).Count
5276

PS > ($cmd2| Measure-Object).Count
1203



The first command generates 5276 IO operations vs 1203 of the second one, that’s a HUGE difference. Here’s some more info from the csv files:


PS >  $cmd1 | Group-Object Operation -NoElement | Sort-Object Count -Descending

Count Name
----- ----
1051 QueryOpen
1026 CreateFile
1023 CloseFile
1000 QueryStandardInformati...
1000 WriteFile
   78 QuerySecurityFile
   54 QueryDirectory
   20 QueryBasicInformationFile
   19 QueryNameInformationFile
    2 QueryAttributeTagFile
    2 SetDispositionInformat...
    1 SetRenameInformationFile


PS > $cmd2 | Group-Object Operation –NoElement | Sort-Object Count -Descending

Count Name
----- ----
1078 WriteFile
   42 QuerySecurityFile
   18 CreateFile
   15 CloseFile
   14 QueryOpen
   11 QueryBasicInformationFile
   10 QueryNameInformationFile
    9 QueryDirectory
    2 QueryAttributeTagFile
    2 SetDispositionInformat...
    1 SetRenameInformationFile
    1 QueryStandardInformati...

 

Based on the above you clearly want to pipe your result to the Out-File cmdlet (or any other file related cmdlets) after the ForEach-Object cmdlet. Doing so will improve the performance of your scripts and will give your disk spindle some rest :)

Using variables in EMS Filter parameter

The Filter parameter is one of my favorite parameters in EMS (Exchange Management Shell). It is one of the most robust parameters in EMS as it enables us to filter objects on the server (using a PowerShell expression) instead of using the Where-Object cmdlet, which filters objects on the client.

In Exchange 2007, we could use a simple filter expression to get all mailbox objects which have the Office attribute set to 'Sales':

[PS] > Get-Mailbox -Filter {Office -eq 'Sales'} | Format-Table Name,Office -AutoSize

Name Office
---- ------
User1 Sales
User2 Sales
User3 Sales
...


We could also use a variable to hold the value and use the variable in the filter:

[PS] $Office = ‘Sales’ 
[PS] > Get-Mailbox -Filter {Office -eq $Office} | Format-Table Name,Office -AutoSize

Name Office
---- ------
User1 Sales
User2 Sales
User3 Sales
...


Now let’s try the same command (using the variable assignment) in EMS 2010:

[PS] $Office = ‘Sales’ 
[PS] > Get-Mailbox -Filter {Office -eq $Office} | Format-Table Name,Office -AutoSize

Name Office
---- ------
DiscoverySearchMailbox {D919BA05-46A6-415f-80AD-7E09334BB852}
Shay Levy

 

As you can see the results are not the same, instead we get all mailbox objects that do not have a value in the Office property, as if the value of $Office is $null. Adding the Verbose switch parameter to the command reveals that the value does not evaluated properly (thanks Michael):

[PS] > Get-Mailbox -Filter {Office -eq $Office} –Verbose 

VERBOSE: [18:29:10.013 GMT] Get-Mailbox : Searching objects of type "ADUser" with filter
"(&((|((RecipientTypeDetails Equal RoomMailbox)(RecipientTypeDetails Equal EquipmentMailbox)
(RecipientTypeDetails Equal LegacyMailbox)(RecipientTypeDetails Equal LinkedMailbox)
(RecipientTypeDetails Equal UserMailbox)(RecipientTypeDetails Equal DiscoveryMailbox)
(RecipientTypeDetails Equal SharedMailbox)))(!((Exists(Office))))))", scope "SubTree" under the
root "$null".

 

It seems that the variable is passed to the EMS remote session without expanding it's value first. That gave me an idea, what if use double quotes instead of braces to force the variable to expand?

[PS] > Get-Mailbox -Filter "Office -eq $Office"
Invoke-Command : Cannot bind parameter 'Filter' to the target. Exception setting "Filter":
"Invalid filter syntax. For a description of the filter parameter syntax see the command help.
"office -eq sales" at position 12."

Now we get an error that the filter is invalid but look at the error, the variable was expanded, it is invalid because the string we are comparing against is not wrapped in quotes. Since we used double quotes to enable variable substitution lets try to wrap the variable in single quotes:

 

[PS] > Get-Mailbox -Filter "Office -eq '$Office'" | Format-Table Name,Office -AutoSize

Name Office
---- ------
User1 Sales
User2 Sales
User3 Sales
User4 Sales
...

 

And it works! PowerShell expands the variable locally before it passes on to the remote EMS session.