Get-World | ConvertTo-PowerShell
Posts tagged WMI
Find path to specific node in SMS console
Jan 12th
Have you ever heard this: “Hey, someone is on holiday so please take care of his work. I need to find info about specific (collection|package|advertisement). Can you please send it to me?” Sometimes it could be a nightmare. Especially if you are looking for collection named This test is what I am looking for and the structure looks like:
Not so funny. Just for this case I created small tool.
Note: Actually I have two separate tools which I want to merge to one. First is written in C# and the screenshot is taken from this one.
It’s now working only for collections and you can filter out collections live – as you are writing requested name to filter field. Second are just functions in PowerShell and I use them for queries against IDs of packages and advertisements. I will talk only about scripts in this article.
WMI behind the code
For work with hierarchy in SMS admin console there are two useful classes: SMS_ObjectContainerItem and SMS_ObjectContainerNode (based on SDK it looks a bit different for SMS2003 and SCCM2007 – of course as it uses different MMC version – so I pointed to newer version – even the script below works with SMS). This code can’t be used for collections as they used different method for storing it’s hierarchy (which is clear if you will think how it works). More info related to collections can be found in SMS_Collection and SMS_CollectToSubCollect MSDN articles.
Get-SMSConsolePath
This is the entry point to functionality. It accepts ID of package or advertisement to look for.
function Get-SMSConsolePath <# .SYNOPSIS Displays path in SMS Console. .DESCRIPTION For given SMS object (package or advertisement) shows full path to this object as it's in SMS Console. .PARAMETER id Id of the object to check .EXAMPLE Get-SMSConsolePath tst00123 Packages/Test/Testing package #> { param ( [Parameter( Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )] [ValidatePattern("tst[02]0[a-f0-9]{3}")] [string] $id ) BEGIN { $Script:ocn = Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_ObjectContainerNode -Filter "ObjectType IN (2,3)" | Select ContainerNodeID, Name, ObjectType, ParentContainerNodeID } PROCESS { $Script:SMSConsolePath = "" $Script:SMSConsoleItem = "" $container = Get-SMSConsoleContainer $id Get-SMSConsoleParent -currNode $container Write-Host "$Script:SMSConsolePath$Script:SMSConsoleItem" } END { Remove-Variable ocn -Scope Script Remove-Variable SMSConsolePath -Scope Script Remove-Variable SMSConsoleItem -Scope Script } }
You see that in begin section I load data from SMS_ObjectContainerNode and store it for future use. I am filtering the results to have just data for packages and advertisements (ObjectType=2,3). Then call two other support functions in process part and will remove some variables at the end.
Get-SMSConsoleContainer
This is the part where I’ll check actual name of the package/advertisement. I will receive it by linking result from SMS_ObjectContainerItem class and respective class of SMS_Package or SMS_Advertisement.
function Get-SMSConsoleContainer { <# .SYNOPSIS Returns containerID for given object. .DESCRIPTION For given object (based on ID) return its containerID. #> param ( $id ) $filter = "InstanceKey = '" + $id + "' AND ObjectType IN (2,3)" $tmp = Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_ObjectContainerItem -Filter $filter | Select ContainerNodeID, InstanceKey, ObjectType if ($($tmp.ObjectType) -eq '2') { $Script:SMSConsolePath += "Packages" $filter = "PackageID = '" + $tmp.InstanceKey + "'" $Script:SMSConsoleItem = (Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_Package -Filter $filter).Name return $($tmp.ContainerNodeID) } else { $Script:SMSConsolePath += "Advertisements" $filter = "AdvertisementID = '" + $tmp.InstanceKey + "'" $Script:SMSConsoleItem = (Get-WmiObject -ComputerName $Global:SMSServer -Namespace $Global:SMSWmiNamespace ` -Class SMS_Advertisement -Filter $filter).AdvertisementName return $($tmp.ContainerNodeID) } }
Get-SMSConsoleParent
And this is the function which actually resolves parent names. It works recursively from bottom to root folder in console. It uses data stored in $ocn variable and creates final string.
function Get-SMSConsoleParent { <# .SYNOPSIS Returns parent of given object. .DESCRIPTION For current node return it's parent. Used for recursion. #> param ( $currNode ) $parNode = ($Script:ocn |? {$_.ContainerNodeID -eq $currNode}).parentContainerNodeID if ($parNode -ge 0) { Get-SMSConsoleParent $parNode } $Script:SMSConsolePath += "$(($Script:ocn |? {$_.ContainerNodeID -eq $currNode}).Name)/" }
Put it together
As we have everything now (I have all three functions in one module) we can try to do some test.
PS C:\> Get-SmsConsolePath tst00035
Packages/_Global/Windows 7/Adobe/Adobe Reader X – for test
I use this code mainly for documentation purposes. Every time I am reporting to our change request system I just run another function – which uses those mentioned – and it will generate nice table for me. It’s “similar” to this:
PS C:\> remedyTable ‘Adobe Reader X’
Package:
<package info> #here is the path in console also
Collection:
<here is collection info>
Advertisement:
<same as for package>
…
So I am not spending whole day in console looking for settings are other annoying things.
Check refresh time for collections
Jan 7th
It happened to me that during the day I found one of our SMS servers a bit unresponsive. After short investigation I found out that a lot of collections is refreshing at the same time and therefore my refresh was in the queue. As the refresh is by default set for everyday, to the time when the collection was created, it can happen that there are some peaks when most of our collections are refreshing. So I decided to check this.
Looking into SMS SDK I found necessary information
and was able to check RefreshSchedule with Get-WmiObject cmdlet.
__GENUS : 2
__CLASS : SMS_ST_RecurInterval
__SUPERCLASS : SMS_ScheduleToken
__DYNASTY : SMS_ScheduleToken
__RELPATH :
__PROPERTY_COUNT : 8
__DERIVATION : {SMS_ScheduleToken}
__SERVER :
__NAMESPACE :
__PATH :
DayDuration : 1
DaySpan : 1
HourDuration : 0
HourSpan : 0
IsGMT : False
MinuteDuration : 0
MinuteSpan : 0
StartTime : 20040614175400.000000+***
As you can see, StartTime is in standard obscure WMI format and we are able to convert it to standard DateTime format easily (if you want to see more, you can check nice post from Shay Levy).
We have all info so can build short function:
function Get-SMSCollectionRefreshTime { $colls = Get-WmiObject -ComputerName vmsms01 -Namespace root\sms\site_xxx -Class SMS_Collection foreach ($coll in $colls) { $([wmi]"$($coll.__PATH)").RefreshSchedule } }
It will produce long list of refresh schedules for all our collections.
Note: You can see that in the list are some other properties and if you are interested, you can find more at SMS_ST_RecurInterval MSDN page. Most of our collections are set to refresh daily (in case of direct membership we are not refreshing at all) but we have few (three) to refresh more often. In the following code I will count all collections as if they are refreshed daily. If you have more frequently updated collections, you can think about modifying the code.
After some experimenting I came with following code:
Get-SMSCollectionRefreshTime | ` Select @{l="Hour";e={"{0:d2}" -f $([System.Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime)).Hour}} | ` Group Hour | Select Name, Count, @{l="Graph";e={"*"*$($_.Count/2)}} | Sort Name | ft -AutoSize -Wrap
which produced following result:
Name Count Graph
—- —– —–
00 16 ********
02 2 *
03 2 *
04 6 ***
05 10 *****
06 66 *********************************
07 22 ***********
08 94 ***********************************************
09 125 **************************************************************
10 163 **********************************************************************************
11 141 **********************************************************************
12 79 ****************************************
13 86 *******************************************
14 118 ***********************************************************
15 166 ***********************************************************************************
16 101 **************************************************
17 99 **************************************************
18 61 ******************************
19 33 ****************
20 12 ******
21 2 *
22 1
23 12 ******
So based on this result, most of our collections are refreshed during 10AM-11AM and 3PM-4PM intervals. Let me describe the code used.
- First Select is pretty big construction. I used computed value where the conversion from WMI time is running ([System.Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime)), from the result I then used just the hour and format it using –f operator (“{0:d2}” -f $(…)).
- Then Group it based on computed hour so can see frequency during the day. At the first time I stopped here as I already saw requested data (number of collection refreshed at specific time). But to have more fun I continued…
- Select hour, number of collections and show “graph” represented by stars. I found out that because of big peaks it’s better to multiply result by two to have all stars at one screen (“*”*$($_.Count/2)).
- Then I Sort results by hour,
- and Format result as table.
Whole work didn’t take longer than five (OK – maybe 10) minutes and I was able to confirm my first thoughts. Next task is to check collections refreshed in peaks and decide what can be changed to different time (preferably overnight). But – it will be (maybe) another post.
Note: I have small piece of paper hung at my screen in the office. It contains this citation:
I am looking to it during the day and today I confirmed it again.
