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:

image

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.

image

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.