Manufacturer / Model Collections with hierarchy

The last post made a flat structure of collections with “Manufacturer – Model”, in this post the script creates a hierarchy with (almost) the same collections.


The Model-collections queries are limited to the parent Manufacturer-collection.

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#                                                                                                                            Rikard Ronnkvist / snowland.se
#  Usage:
#   Download and install https://snowland.se/2010/03/10/sccm-module-for-powershell/
#   Save the file as CreateMM-collections-Hierarchy.ps1
#   PS:>.\CreateMM-collections-Hierarchy.ps1 -rootCollectionName "Name Of Some Collection"
#
#  2010-03-24   Rikard Ronnkvist    First snowland.se release
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PARAM (
        [string] $rootCollectionName = $(throw "rootCollectionName required."),
        [string] $hostName = (Get-Content env:computername),
        [switch] $Verbose,
        [Switch] $WhatIf
)

if ($verbose.IsPresent) {
        $VerbosePreference = 'Continue'
} Else {
        $VerbosePreference = 'SilentlyContinue'
}

Import-Module SCCM\SCCM-Functions -Force

Write-Verbose "Connect to SCCM-server $($hostName)"
$sccm = Connect-SCCMServer -HostName $hostName

Write-Host "Get root collection: ""$($rootCollectionName)"""
$rootCollection = Get-SCCMCollection -filter "Name='$($rootCollectionName)'" -sccmserver $sccm
if (!$rootCollection) {
        throw "Cant find ""$($rootCollectionName)"""
}
Write-Host "Found collection: $($rootCollection.CollectionID)"

Function checkAndCreate ($CollectionName, $ParentCollectionID, $wql, $limit = $null) {
        Write-Host "Checking ""$($CollectionName)""" -ForegroundColor Cyan
        $newCollection = Get-SCCMCollection -filter "Name='$($CollectionName)'" -sccmserver $sccm

        if (!$newCollection) {
                if (!$WhatIf.IsPresent) {
                        Write-Host "Creating collection: ""$($CollectionName)"""
                        $newCollection = New-SCCMCollection -name "$($CollectionName)" -SccmServer $sccm -parentCollectionID $ParentCollectionID -refreshDays 1 -Verbose
                } else {
                        Write-Host "What if: Creating collection: ""$($CollectionName)""" -ForegroundColor Red
                }

                if (!$WhatIf.IsPresent) {
                        Write-Verbose "Adding rule with WQL: $wql"
                        Add-SCCMCollectionRule -queryExpression $wql -Server $sccm -collectionID $newCollection.CollectionId -queryRuleName $CollectionName -limitToCollectionId $limit
                } else {
                        Write-Host "What if: Adding collection rule to new collection with wql: $($wql)" -ForegroundColor Red
                }
        } else {
                Write-Host "Found collection ""$($CollectionName)"""
        }

        return $newCollection
}

Write-Host "Lookup Manufacturer and Model"
$Manufacturer = Get-wmiobject -query "SELECT DISTINCT Manufacturer FROM SMS_G_System_COMPUTER_SYSTEM" -computername $Sccm.Machine -namespace $Sccm.Namespace | Sort-Object Manufacturer, Model
$Manufacturer | ForEach-Object {
        $wql = "SELECT * FROM SMS_R_System inner join SMS_G_System_COMPUTER_SYSTEM on SMS_G_System_COMPUTER_SYSTEM.ResourceId = SMS_R_System.ResourceId where SMS_G_System_COMPUTER_SYSTEM.Manufacturer = '$($_.Manufacturer)'"
        $ManufacturerCollection = checkAndCreate -collectionName $_.Manufacturer -ParentCollectionID $rootCollection.CollectionId -wql $wql -limit $null

        $Model = Get-wmiobject -query "SELECT DISTINCT Model FROM SMS_G_System_COMPUTER_SYSTEM WHERE Manufacturer = '$($ManufacturerCollection.Name)'" -computername $Sccm.Machine -namespace $Sccm.Namespace | Sort-Object Manufacturer, Model
        $Model | ForEach-Object {
                $wql = "SELECT * FROM SMS_R_System inner join SMS_G_System_COMPUTER_SYSTEM on SMS_G_System_COMPUTER_SYSTEM.ResourceId = SMS_R_System.ResourceId where SMS_G_System_COMPUTER_SYSTEM.Model = '$($_.Model)'"
                $ModelCollection = checkAndCreate -collectionName $_.Model -ParentCollectionID $ManufacturerCollection.CollectionId -wql $wql -limit $ManufacturerCollection.CollectionId
        }
}

Manufacturer / Model Collections

You have probably created one or two collections that points to a specific Manufacturer and/or Model.

Well, this script will look in to your SCCM-database and create that kind of collections for you.

First you need my SCCM Module for PowerShell
Then I created a collection named “000 – Manufacturer – Model”
Copy and paste the code below to a file, save it as CreateMM-collections.ps1
Run with at least one param, -rootCollectionName
Example: .\CreateMM-collections.ps1 -rootCollectionName “000 – Manufacturer – Model”

The script support the -WhatIf and -Verbose parameters… might be good to have when testing.

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#                                                                                                                            Rikard Ronnkvist / snowland.se
#  Usage:
#   Download and install https://snowland.se/2010/03/10/sccm-module-for-powershell/
#   Save the file as CreateMM-collections.ps1
#   PS:>.\CreateMM-collections.ps1 -rootCollectionName "Name Of Some Collection"
#
#  2010-03-23   Rikard Ronnkvist    First snowland.se release
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PARAM (
        [string] $rootCollectionName = $(throw "rootCollectionName required."),
        [string] $hostName = (Get-Content env:computername),
        [switch] $Verbose,
        [Switch] $WhatIf
)

if ($verbose.IsPresent) {
        $VerbosePreference = 'Continue'
} Else {
        $VerbosePreference = 'SilentlyContinue'
}

Import-Module SCCM\SCCM-Functions -Force

Write-Verbose "Connect to SCCM-server $($hostName)"
$sccm = Connect-SCCMServer -HostName $hostName

Write-Host "Get root collection: ""$($rootCollectionName)"""
$rootCollection = Get-SCCMCollection -filter "Name='$($rootCollectionName)'" -sccmserver $sccm
if (!$rootCollection) {
        throw "Cant find ""$($rootCollectionName)"""
}
Write-Host "Found collection: $($rootCollection.CollectionID)"

Write-Host "Lookup Manufacturer and Model"
$ManufacturerModel = Get-wmiobject -query "SELECT DISTINCT Manufacturer, Model FROM SMS_G_System_COMPUTER_SYSTEM" -computername $Sccm.Machine -namespace $Sccm.Namespace | Sort-Object Manufacturer, Model
$ManufacturerModel | ForEach-Object {
        $mmCollectionName = "$($_.Manufacturer) - $($_.Model)"
        Write-Host "Checking ""$($mmCollectionName)""" -ForegroundColor Cyan

        $mmCollection = Get-SCCMCollection -filter "Name='$($mmCollectionName)'" -sccmserver $sccm

        if (!$mmCollection) {
                if (!$WhatIf.IsPresent) {
                        Write-Host "Creating collection: ""$($mmCollectionName)"""
                        $newMmCollection = New-SCCMCollection -name "$($mmCollectionName)" -SccmServer $sccm -parentCollectionID $rootCollection.CollectionID -refreshDays 1
                } else {
                        Write-Host "What if: Creating collection: ""$($mmCollectionName)""" -ForegroundColor Red
                }

                $wql = "SELECT * FROM SMS_R_System inner join SMS_G_System_COMPUTER_SYSTEM on SMS_G_System_COMPUTER_SYSTEM.ResourceId = SMS_R_System.ResourceId where SMS_G_System_COMPUTER_SYSTEM.Manufacturer = '$($_.Manufacturer)'  AND SMS_G_System_COMPUTER_SYSTEM.Model = '$($_.Model)'"
                if (!$WhatIf.IsPresent) {
                        Write-Verbose "Adding rule with WQL: $wql"
                        Add-SCCMCollectionRule -queryExpression $wql -Server $sccm -collectionID $newMmCollection.CollectionId -queryRuleName $mmCollectionName
                } else {
                        Write-Host "What if: Adding collection rule to new collection with wql: $($wql)" -ForegroundColor Red
                }
        } else {
                Write-Host "Found collection"
        }
}

SCCM Module for PowerShell

In a post a few days ago I mentioned “some slightly modified functions from Michael Niehaus“.

Well… Why not share them.

Save this as a module, load it and play around.

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#                                                                                                                            Rikard Ronnkvist / snowland.se
#  Usage:
#   Save the file as SCCM-Commands.psm1
#   PS:>Import-Module SCCM-Commands
#   PS:>Get-SCCMCommands
#
#  2009-04-07   Michael Niehaus     Original code posted at http://blogs.technet.com/mniehaus/
#  2010-03-10   Rikard Ronnkvist    Major makeover and first snowland.se release
#  2010-03-23   Rikard Ronnkvist    Fixed some small bugs and added limitToCollectionId in Add-SCCMCollectionRule
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Function Get-SCCMCommands {
        # List all SCCM-commands
        [CmdletBinding()]
        PARAM ()
        PROCESS {
                return Get-Command -Name *-SCCM* -CommandType Function  | Sort-Object Name | Format-Table Name, Module
        }
}

Function Connect-SCCMServer {
        # Connect to one SCCM server
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$false,HelpMessage="SCCM Server Name or FQDN",ValueFromPipeline=$true)][Alias("ServerName","FQDN","ComputerName")][String] $HostName = (Get-Content env:computername),
                [Parameter(Mandatory=$false,HelpMessage="Optional SCCM Site Code",ValueFromPipelineByPropertyName=$true )][String] $siteCode = $null,
                [Parameter(Mandatory=$false,HelpMessage="Credentials to use" )][System.Management.Automation.PSCredential] $credential = $null
        )

        PROCESS {
                # Get the pointer to the provider for the site code
                if ($siteCode -eq $null -or $siteCode -eq "") {
                        Write-Verbose "Getting provider location for default site on server $HostName"
                        if ($credential -eq $null) {
                                $sccmProviderLocation = Get-WmiObject -query "select * from SMS_ProviderLocation where ProviderForLocalSite = true" -Namespace "root\sms" -computername $HostName -errorAction Stop
                        } else {
                                $sccmProviderLocation = Get-WmiObject -query "select * from SMS_ProviderLocation where ProviderForLocalSite = true" -Namespace "root\sms" -computername $HostName -credential $credential -errorAction Stop
                        }
                } else {
                        Write-Verbose "Getting provider location for site $siteCode on server $HostName"
                        if ($credential -eq $null) {
                                $sccmProviderLocation = Get-WmiObject -query "SELECT * FROM SMS_ProviderLocation where SiteCode = '$siteCode'" -Namespace "root\sms" -computername $HostName -errorAction Stop
                        } else {
                                $sccmProviderLocation = Get-WmiObject -query "SELECT * FROM SMS_ProviderLocation where SiteCode = '$siteCode'" -Namespace "root\sms" -computername $HostName -credential $credential -errorAction Stop
                        }
                }

                # Split up the namespace path
                $parts = $sccmProviderLocation.NamespacePath -split "\\", 4
                Write-Verbose "Provider is located on $($sccmProviderLocation.Machine) in namespace $($parts[3])"

                # Create a new object with information
                $retObj = New-Object -TypeName System.Object
                $retObj | add-Member -memberType NoteProperty -name Machine -Value $HostName
                $retObj | add-Member -memberType NoteProperty -name Namespace -Value $parts[3]
                $retObj | add-Member -memberType NoteProperty -name SccmProvider -Value $sccmProviderLocation

                return $retObj
        }
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Function Get-SCCMObject {
        #  Generic query tool
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipelineByPropertyName=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$true, HelpMessage="SCCM Class to query",ValueFromPipeline=$true)][Alias("Table","View")][String] $class,
                [Parameter(Mandatory=$false,HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                if ($Filter -eq $null -or $Filter -eq "")
                {
                        Write-Verbose "WMI Query: SELECT * FROM $class"
                        $retObj = get-wmiobject -class $class -computername $SccmServer.Machine -namespace $SccmServer.Namespace
                }
                else
                {
                        Write-Verbose "WMI Query: SELECT * FROM $class WHERE $Filter"
                        $retObj = get-wmiobject -query "SELECT * FROM $class WHERE $Filter" -computername $SccmServer.Machine -namespace $SccmServer.Namespace
                }

                return $retObj
        }
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Function Get-SCCMPackage {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_Package" -Filter $Filter
        }
}

Function Get-SCCMCollection {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_Collection" -Filter $Filter
        }
}

Function Get-SCCMAdvertisement {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_Advertisement" -Filter $Filter
        }
}

Function Get-SCCMDriver {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_Driver" -Filter $Filter
        }
}

Function Get-SCCMDriverPackage {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_DriverPackage" -Filter $Filter
        }
}

Function Get-SCCMTaskSequence {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_TaskSequence" -Filter $Filter
        }
}

Function Get-SCCMSite {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_Site" -Filter $Filter
        }
}

Function Get-SCCMImagePackage {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_ImagePackage" -Filter $Filter
        }
}

Function Get-SCCMOperatingSystemInstallPackage {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_OperatingSystemInstallPackage" -Filter $Filter
        }
}

Function Get-SCCMBootImagePackage {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Optional Filter on query")][String] $Filter = $null
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_BootImagePackage" -Filter $Filter
        }
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Function Get-SCCMComputer {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Filter on SCCM Resource ID",ValueFromPipelineByPropertyName=$true)][int32] $ResourceID = $false,
                [Parameter(Mandatory=$false, HelpMessage="Filter on Netbiosname on computer",ValueFromPipeline=$true)][String] $NetbiosName = "%",
                [Parameter(Mandatory=$false, HelpMessage="Filter on Domain name",ValueFromPipelineByPropertyName=$true)][Alias("Domain", "Workgroup")][String] $ResourceDomainOrWorkgroup = "%",
                [Parameter(Mandatory=$false, HelpMessage="Filter on SmbiosGuid (UUID)")][String] $SmBiosGuid = "%"
        )

        PROCESS {
                if ($ResourceID -eq $false -and $NetbiosName -eq "%" -and $ResourceDomainOrWorkgroup -eq "%" -and $SmBiosGuid -eq "%") {
                        throw "Need at least one filter..."
                }

                if ($ResourceID -eq $false) {
                        return Get-SCCMObject -sccmServer $SccmServer -class "SMS_R_System" -Filter "NetbiosName LIKE '$NetbiosName' AND ResourceDomainOrWorkgroup LIKE '$ResourceDomainOrWorkgroup' AND SmBiosGuid LIKE '$SmBiosGuid'"
                } else {
                        return Get-SCCMObject -sccmServer $SccmServer -class "SMS_R_System" -Filter "ResourceID = $ResourceID"
                }
        }
}

Function Get-SCCMUser {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Filter on SCCM Resource ID",ValueFromPipelineByPropertyName=$true)][int32] $ResourceID = $false,
                [Parameter(Mandatory=$false, HelpMessage="Filter on unique username in form DOMAIN\UserName",ValueFromPipelineByPropertyName=$true)][String] $UniqueUserName = "%",
                [Parameter(Mandatory=$false, HelpMessage="Filter on Domain name",ValueFromPipelineByPropertyName=$true)][Alias("Domain")][String] $WindowsNTDomain = "%",
                [Parameter(Mandatory=$false, HelpMessage="Filter on UserName",ValueFromPipeline=$true)][String] $UserName = "%"
        )

        PROCESS {
                if ($ResourceID -eq $false -and $UniqueUserName -eq "%" -and $WindowsNTDomain -eq "%" -and $UserName -eq "%") {
                        throw "Need at least one filter..."
                }

                if ($ResourceID -eq $false) {
                        return Get-SCCMObject -sccmServer $SccmServer -class "SMS_R_User" -Filter "UniqueUserName LIKE '$UniqueUserName' AND WindowsNTDomain LIKE '$WindowsNTDomain' AND UserName LIKE '$UserName'"
                } else {
                        return Get-SCCMObject -sccmServer $SccmServer -class "SMS_R_User" -Filter "ResourceID = $ResourceID"
                }
        }
}

Function Get-SCCMCollectionMembers {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="CollectionID", ValueFromPipeline=$true)][String] $CollectionID
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_CollectionMember_a" -Filter "CollectionID = '" + $CollectionID + "'"
        }
}

Function Get-SCCMSubCollections {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$true, HelpMessage="CollectionID",ValueFromPipeline=$true)][Alias("parentCollectionID")][String] $CollectionID
        )

        PROCESS {
                return Get-SCCMObject -sccmServer $SccmServer -class "SMS_CollectToSubCollect" -Filter "parentCollectionID = '$CollectionID'"
        }
}

Function Get-SCCMParentCollection {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$true, HelpMessage="CollectionID",ValueFromPipeline=$true)][Alias("subCollectionID")][String] $CollectionID
        )

        PROCESS {
                $parentCollection = Get-SCCMObject -sccmServer $SccmServer -class "SMS_CollectToSubCollect" -Filter "subCollectionID = '$CollectionID'"

                return Get-SCCMCollection -sccmServer $SccmServer -Filter "CollectionID = '$($parentCollection.parentCollectionID)'"
        }
}

Function Get-SCCMSiteDefinition {
        # Get all definitions for one SCCM site
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer
        )

        PROCESS {
                Write-Verbose "Refresh the site $($SccmServer.SccmProvider.SiteCode) control file"
                Invoke-WmiMethod -path SMS_SiteControlFile -name RefreshSCF -argumentList $($SccmServer.SccmProvider.SiteCode) -computername $SccmServer.Machine -namespace $SccmServer.Namespace

                Write-Verbose "Get the site definition object for this site"
                return get-wmiobject -query "SELECT * FROM SMS_SCI_SiteDefinition WHERE SiteCode = '$($SccmServer.SccmProvider.SiteCode)' AND FileType = 2" -computername $SccmServer.Machine -namespace $SccmServer.Namespace
        }
}

Function Get-SCCMSiteDefinitionProps {
        # Get definitionproperties for one SCCM site
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer
        )

        PROCESS {
                return Get-SCCMSiteDefinition -sccmServer $SccmServer | ForEach-Object { $_.Props }
        }
}

Function Get-SCCMIsR2 {
        # Return $true if the SCCM server is R2 capable
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer
        )

        PROCESS {
                $result = Get-SCCMSiteDefinitionProps -sccmServer $SccmServer | ? {$_.PropertyName -eq "IsR2CapableRTM"}
                if (-not $result) {
                        return $false
                } elseif ($result.Value = 31) {
                        return $true
                } else {
                        return $false
                }
        }
}

Function Get-SCCMCollectionRules {
        # Get a set of all collectionrules
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="CollectionID", ValueFromPipeline=$true)][String] $CollectionID
        )

        PROCESS {
                Write-Verbose "Collecting rules for $CollectionID"
                $col = [wmi]"$($SccmServer.SccmProvider.NamespacePath):SMS_Collection.CollectionID='$($CollectionID)'"

                return $col.CollectionRules
        }
}

Function Get-SCCMInboxes {
        # Give a count of files in the SCCM-inboxes
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server",ValueFromPipeline=$true)][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$false, HelpMessage="Minimum number of files in directory")][int32] $minCount = 1
        )

        PROCESS {
                Write-Verbose "Reading \\$($SccmServer.Machine)\SMS_$($SccmServer.SccmProvider.SiteCode)\inboxes"
                return Get-ChildItem \\$($SccmServer.Machine)\SMS_$($SccmServer.SccmProvider.SiteCode)\inboxes -Recurse | Group-Object Directory | Where { $_.Count -gt $minCount } | Format-Table Count, Name -AutoSize
        }
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Function New-SCCMCollection {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$true, HelpMessage="Collection Name", ValueFromPipeline=$true)][String] $name,
                [Parameter(Mandatory=$false, HelpMessage="Collection comment")][String] $comment = "",
                [Parameter(Mandatory=$false, HelpMessage="Refresh Rate in Minutes")] [ValidateRange(0, 59)] [int] $refreshMinutes = 0,
                [Parameter(Mandatory=$false, HelpMessage="Refresh Rate in Hours")] [ValidateRange(0, 23)] [int] $refreshHours = 0,
                [Parameter(Mandatory=$false, HelpMessage="Refresh Rate in Days")] [ValidateRange(0, 31)] [int] $refreshDays = 0,
                [Parameter(Mandatory=$false, HelpMessage="Parent CollectionID")][String] $parentCollectionID = "COLLROOT"
        )

        PROCESS {
                # Build the parameters for creating the collection
                $arguments = @{Name = $name; Comment = $comment; OwnedByThisSite = $true}
                $newColl = set-wmiinstance -class SMS_Collection -arguments $arguments -computername $SccmServer.Machine -namespace $SccmServer.Namespace

                # Hack - for some reason without this we don't get the CollectionID value
                $hack = $newColl.PSBase | select * | out-null

                # It's really hard to set the refresh schedule via set-wmiinstance, so we'll set it later if necessary
                if ($refreshMinutes -gt 0 -or $refreshHours -gt 0 -or $refreshDays -gt 0)
                {
                        Write-Verbose "Create the recur interval object"
                        $intervalClass = [wmiclass]"\\$($SccmServer.Machine)\$($SccmServer.Namespace):SMS_ST_RecurInterval"
                        $interval = $intervalClass.CreateInstance()
                        if ($refreshMinutes -gt 0) {
                                $interval.MinuteSpan = $refreshMinutes
                        }
                        if ($refreshHours -gt 0) {
                                $interval.HourSpan = $refreshHours
                        }
                        if ($refreshDays -gt 0) {
                                $interval.DaySpan = $refreshDays
                        }

                        Write-Verbose "Set the refresh schedule"
                        $newColl.RefreshSchedule = $interval
                        $newColl.RefreshType=2
                        $path = $newColl.Put()
                }   

                Write-Verbose "Setting the new $($newColl.CollectionID) parent to $parentCollectionID"
                $subArguments  = @{SubCollectionID = $newColl.CollectionID}
                $subArguments += @{ParentCollectionID = $parentCollectionID}

                # Add the link
                $newRelation = Set-WmiInstance -Class SMS_CollectToSubCollect -arguments $subArguments -computername $SccmServer.Machine -namespace $SccmServer.Namespace

                Write-Verbose "Return the new collection with ID $($newColl.CollectionID)"
                return $newColl
        }
}

Function Add-SCCMCollectionRule {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true,  HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(Mandatory=$true,  HelpMessage="CollectionID", ValueFromPipelineByPropertyName=$true)] $collectionID,
                [Parameter(Mandatory=$false, HelpMessage="Computer name to add (direct)", ValueFromPipeline=$true)] [String] $name,
                [Parameter(Mandatory=$false, HelpMessage="WQL Query Expression", ValueFromPipeline=$true)] [String] $queryExpression,
                [Parameter(Mandatory=$false, HelpMessage="Limit to collection (Query)", ValueFromPipeline=$false)] [String] $limitToCollectionId = $null,
                [Parameter(Mandatory=$true,  HelpMessage="Rule Name", ValueFromPipeline=$true)] [String] $queryRuleName
        )

        PROCESS {
                # Get the specified collection (to make sure we have the lazy properties)
                $coll = [wmi]"$($SccmServer.SccmProvider.NamespacePath):SMS_Collection.CollectionID='$collectionID'"

                # Build the new rule
                if ($queryExpression -ne $null) {
                        # Create a query rule
                        $ruleClass = [wmiclass]"$($SccmServer.SccmProvider.NamespacePath):SMS_CollectionRuleQuery"
                        $newRule = $ruleClass.CreateInstance()
                        $newRule.RuleName = $queryRuleName
                        $newRule.QueryExpression = $queryExpression
                        if ($limitToCollectionId -ne $null) {
                                $newRule.LimitToCollectionID = $limitToCollectionId
                        }

                        $null = $coll.AddMembershipRule($newRule)
                } else {
                        $ruleClass = [wmiclass]"$($SccmServer.SccmProvider.NamespacePath):SMS_CollectionRuleDirect"

                        # Find each computer
                        foreach ($n in $name) {
                                foreach ($computer in Get-SCCMComputer -sccmServer $SccmServer -Filter "Name = '$n'") {
                                        # See if the computer is already a member
                                        $found = $false
                                        if ($coll.CollectionRules -ne $null) {
                                                foreach ($member in $coll.CollectionRules) {
                                                        if ($member.ResourceID -eq $computer.ResourceID) {
                                                                $found = $true
                                                        }
                                                }
                                        }
                                        if (-not $found) {
                                                Write-Verbose "Adding new rule for computer $n"
                                                $newRule = $ruleClass.CreateInstance()
                                                $newRule.RuleName = $n
                                                $newRule.ResourceClassName = "SMS_R_System"
                                                $newRule.ResourceID = $computer.ResourceID

                                                $null = $coll.AddMembershipRule($newRule)
                                        } else {
                                                Write-Verbose "Computer $n is already in the collection"
                                        }
                                }
                        }
                }
        }
}

Function Add-SCCMDirUserCollectionRule {
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true, HelpMessage="SCCM Server")][Alias("Server","SmsServer")][System.Object] $SccmServer,
                [Parameter(ValueFromPipelineByPropertyName=$true)][String] $CollectionID,
                [Parameter(ValueFromPipeline=$true)][String] $UserName
        )

        PROCESS {
                $coll = [wmi]"\\$($SccmServer.Machine)\$($SccmServer.Namespace):SMS_Collection.CollectionID='$CollectionID'"
                $ruleClass = [wmiclass]"\\$($SccmServer.Machine)\$($SccmServer.Namespace):SMS_CollectionRuleDirect"

                $RuleClass
                $UserRule=Get-User "userName='$UserName'"
                $NewRuleName=$UserRule.name
                $NewRuleResourceID = $UserRule.ResourceID
                $newRule = $ruleClass.CreateInstance()

                $newRule.RuleName = $NewRuleName
                $newRule.ResourceClassName = "SMS_R_User"
                $newRule.ResourceID = $NewRuleResourceID

                $null = $coll.AddMembershipRule($newRule)
                $coll.requestrefresh()
                Clear-Variable -name oldrule -errorAction SilentlyContinue
                Clear-Variable -name Coll -errorAction SilentlyContinue
        }
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# EOF

Some examples on what you can do:

# List all available SCCM commands
Get-SCCMCommands

# Create an SCCM-Connection to the local server
$sccm = Connect-SCCMServer -Verbose

# Create a new collection with a collection rule
$newCollection = New-SCCMCollection -SccmServer $sccm -name "Some Collection Name" -Verbose
Add-SCCMCollectionRule -Server $sccm -collectionID $newRoot.CollectionId -queryExpression "SELECT * FROM SMS_R_System" -queryRuleName "All Systems" -Verbose

# Count files in the inboxes
$sccm | Get-SCCMInboxes

# Get a package
$MyPackage = Get-SCCMPackage -server $sccm -filter "Name = 'Some Package Name'"

If you have some comments, ideas and things to add… Comment this post or shoot me an .


Add text to images with PowerShell

Been working on some server deployment lately and had some problems with the WinPE… so to not get to confused when using different PE’s to boot up the servers we added some image-information on the background.

Did that in MS Paint… but how fun is that? :-P

Here is a cool function that add some text to a image (Original code from http://www.ravichaganti.com/blog/?p=1012)

Function AddTextToImage {
        # Orignal code from http://www.ravichaganti.com/blog/?p=1012
        [CmdletBinding()]
        PARAM (
                [Parameter(Mandatory=$true)][String] $sourcePath,
                [Parameter(Mandatory=$true)][String] $destPath,
                [Parameter(Mandatory=$true)][String] $Title,
                [Parameter()][String] $Description = $null
        )

        Write-Verbose "Load System.Drawing"
        [Reflection.Assembly]::LoadWithPartialName("System.Drawing") | Out-Null

        Write-Verbose "Get the image from $sourcePath"
        $srcImg = [System.Drawing.Image]::FromFile($sourcePath)

        Write-Verbose "Create a bitmap as $destPath"
        $bmpFile = new-object System.Drawing.Bitmap([int]($srcImg.width)),([int]($srcImg.height))

        Write-Verbose "Intialize Graphics"
        $Image = [System.Drawing.Graphics]::FromImage($bmpFile)
        $Image.SmoothingMode = "AntiAlias"

        $Rectangle = New-Object Drawing.Rectangle 0, 0, $srcImg.Width, $srcImg.Height
        $Image.DrawImage($srcImg, $Rectangle, 0, 0, $srcImg.Width, $srcImg.Height, ([Drawing.GraphicsUnit]::Pixel))

        Write-Verbose "Draw title: $Title"
        $Font = new-object System.Drawing.Font("Verdana", 24)
        $Brush = New-Object Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(255, 0, 0,0))
        $Image.DrawString($Title, $Font, $Brush, 10, 10)

        if ($Description -ne $null) {
                Write-Verbose "Draw description: $Description"
                $Font = New-object System.Drawing.Font("Verdana", 12)
                $Brush = New-Object Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(120, 0, 0, 0))
                $Image.DrawString($Description, $Font, $Brush, 10, 50)
        }

        Write-Verbose "Save and close the files"
        $bmpFile.save($destPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
        $bmpFile.Dispose()
        $srcImg.Dispose()
}

With that piece of code and some slightly modified functions from Michael Niehaus you can do some cool stuff…

$taskSequence = "Server Deployment Task Sequence"

Import-Module Misc\Image-Functions.psm1
Import-Module SCCM\SCCM-Functions.psm1

$sccm = Connect-SCCMServer
$taskSequence = Get-TaskSequencePackage -SccmServer $sccm -filter "Name = '$taskSequence'"
$BootImage = Get-BootImagePackage -SccmServer $sccm -filter "PackageID = '$($taskSequence.BootImageID)'"
$DescriptionText = "Version:`t$($BootImage.Version)`nPackageID:`t$($BootImage.PackageID)`n`n$($BootImage.Description)"

AddTextToImage -sourcePath "X:\Path\WinPE-Background-Source.bmp" -destPath "X:\Path\WinPE-Background-To-Inject.bmp" -Title $BootImage.Name -Description $DescriptionText

Shouldn’t be that hard to auto-inject the BMP to the WIM file…


PSOL: Count files in SCCM-inboxes – Version 3

A bit more simple version of the last script… this time as a PowerShell oneliner.

Get-ChildItem \\MYSERVER\SMS_C01\inboxes -Recurse | Group-Object Directory | Where { $_.Count -gt 1 }  | Sort-Object Count -Descending | Format-Table Count, Name -AutoSize

SCCM Console Extensions – Parameters

OK, so now you know the GUID for the right-click tool… but what about passing parameters?

There are a few standard SUB’s (parameters) that you can use, some are listed in this post: https://snowland.se/2008/05/28/sccm-console-extensions/

But if you take the example of GUID 5fb29b42-5d11-4642-a6c9-24881a7d317e that you can find under Software Distribution Packages / Packages / Some package / Package Status / Site Server / Right click on a distribution point

Say that you want to pass the server-name or the path to the package…

First off, open the E:\Program Files\Microsoft Configuration Manager\AdminUI\XmlStorage\ConsoleRoot\AdminConsole.xml in some editor.

Then search for the GUID and you will find something like this.


  
    
      
        
          
            SMS_PackageStatusDetailSummarizer
          
          SELECT * FROM SMS_PackageStatusDistPointsSummarizer WHERE PackageID='##SUB:PackageID##' AND SiteCode='##SUB:SiteCode##'
          SMS_PackageStatusDistPointsSummarizer
        
      
    
  

A few lines below the GUID you find SELECT * FROM SMS_PackageStatusDistPointsSummarizer WHERE Packa… Copy that line and replace/clean it up so that it is a valid WMI-query.
Will look something like:

SELECT * FROM SMS_PackageStatusDistPointsSummarizer WHERE PackageID='XYZ00123' AND SiteCode='XYZ'

Next step is to start some WMI-browser and connect to root\SMS\site_XYZ and run the query and take a look at the columns.
(I like to use WMI Explorer)

In the query above you will have columns like ServerNALPath, SourceNALPath, SourceVersion this is what you are looking for. :-)

Use them in your extensions like this:


        myScript.vbs
        ##SUB:ServerNALPath## ##SUB:SourceNALPath## ##SUB:SourceVersion##


SCCM Console Extensions – Find the GUID

I wrote some about this topic in a post a while ago… did some more scripting around this today.

This VBScript will read the AdminConsole.xml and look for NameSpaceGuid’s, when it find one it will create a subdirectory (from where it is started) with the GUID and after that it will create a XML-file within that directory.
The XML-file then points to an VBS-file with a couple of parameters. (Look further down for an example of a nice VBScript to use)

Tip: Backup AdminUI\XmlStorage\Extensions\Actions before you start to play around with this.

Const cVbsFile = "testExtension.vbs" ' The file to call on right-click
Const cHKEY_LOCAL_MACHINE = &H80000002
Set oReg=GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\default:StdRegProv")
oReg.GetStringValue cHKEY_LOCAL_MACHINE,"SOFTWARE\Microsoft\ConfigMgr\Setup", "UI Installation Directory", sSccmPath
Set oReg = Nothing

sSourcePath = Replace(WScript.ScriptFullName, WScript.ScriptName, "")

Set oShell = CreateObject("WScript.Shell")
Set oFso = CreateObject("Scripting.FileSystemObject")
Set oFile = oFso.OpenTextFile(sSccmPath & "\XmlStorage\ConsoleRoot\AdminConsole.xml", 1)
Do While oFile.AtEndOfStream <> True
    sText = Trim(uCase(oFile.ReadLine))
    If InStr(sText, "NAMESPACEGUID=") Then

                ' Read the GUID from NameSpaceGuid param
                sGuid = sText
                sGuid = Right(sGuid, Len(sGuid) - InStr(sGuid, "NAMESPACEGUID=") - 14)
                sGuid = Left(sGuid, InStr(sGuid, """")-1)

                if not oFso.FolderExists(sSourcePath & sGuid) Then
                        WScript.Echo sSourcePath & sGuid

                        ' Create the GUID folder
                        oFso.CreateFolder sSourcePath & sGuid

                        ' Create the XML-file with current Guid, Name & ResourceID as parameter to source-VBScript
                        Set oXmlFile = oFso.CreateTextFile(oShell.ExpandEnvironmentStrings("%TEMP%\snowland-guid-locator.xml"), True)
                        oXmlFile.WriteLine ""
                        oXmlFile.WriteLine ""
                        oXmlFile.WriteLine "" & sSourcePath & cVbsFile & ""
                        oXmlFile.WriteLine "" & sGuid & " ##Sub:Name## ##Sub:ResourceID## ##SUB:ItemName## ##SUB:NetworkOSPath## ##SUB:value##"
                        oXmlFile.WriteLine ""
                        oXmlFile.WriteLine ""
                        oXmlFile.Close

                        ' Copy XML to GUID-directory with name "snowland-GUID.xml" as name
                        oFso.CopyFile oShell.ExpandEnvironmentStrings("%TEMP%\snowland-guid-locator.xml"), sSourcePath & sGuid & "\snowland-" & sGuid & ".xml"
                End if
        End If
Loop
oFile.Close

So… when you restarted the console you will se GUID’s showing up. To get those GUID’s to the clipboard use a testExtension.vbs like this

Set oFso = CreateObject("Scripting.FileSystemObject")
Set oShell = CreateObject("WScript.Shell")

' Create a temporary file
Set oFile = oFso.CreateTextFile(oShell.ExpandEnvironmentStrings("%TEMP%\sccmXmlReader.tmp"), True)

' Loop thru arguments
For i = 0 to WScript.Arguments.Count-1
        sOut = sOut & Wscript.Arguments(i) & VbCrLf
        ' Write to file
        oFile.WriteLine Wscript.Arguments(i)
Next
' Close the file
oFile.Close

' Type the file to the clipboard
oShell.Run oShell.ExpandEnvironmentStrings("%SystemRoot%\System32\cmd.exe /c type %TEMP%\sccmXmlReader.tmp | %SystemRoot%\System32\clip.exe"), 1, True

' Delete the file
oFso.DeleteFile oShell.ExpandEnvironmentStrings("%TEMP%\sccmXmlReader.tmp"), True

' Send a message to the user
MsgBox sOut, vbOKOnly, "Copied to clipboard"

Will try to do a post about how to find the SUB’s…


SCCM Status Message Query for Advertismet

Made a small combination of a couple of queries, so this will show “Clients That Ran, Received, Rejected or Started a Specific Advertised Program

SELECT stat.*, ins.*, att1.*, att1.AttributeTime
FROM SMS_StatusMessage AS stat
LEFT JOIN SMS_StatMsgInsStrings AS ins ON stat.RecordID = ins.RecordID
LEFT JOIN SMS_StatMsgAttributes AS att1 ON stat.RecordID = att1.RecordID
INNER JOIN SMS_StatMsgAttributes AS att2 ON stat.RecordID = att2.RecordID

WHERE
        stat.ModuleName = "SMS Client" AND
        (
                (stat.MessageID = 10005 AND att2.AttributeID = 401)  OR
                (stat.MessageID >= 10018 AND stat.MessageID <=10019 and att2.AttributeID = 401) OR
                (stat.MessageID = 10002 AND att2.AttributeID = 401) OR
                (stat.MessageID >= 10008 AND stat.MessageID <= 10009)
        )
        AND att2.AttributeValue = ##PRM:SMS_StatMsgAttributes.AttributeValue##
        AND att2.AttributeTime >= ##PRM:SMS_StatMsgAttributes.AttributeTime##
ORDER BY att1.AttributeTime DESC

Bulk update commandlines in SCCM-programs

Just a small script to update the commandline of a large number of programs in SCCM.

This script will change from “/q” to “/qb!”…

Set oLocator = CreateObject("WbemScripting.SWbemLocator")
Set oSccmWmi = oLocator.ConnectServer(".", "root\sms\site_C01", "", "")

Set oPrograms = oSccmWmi.ExecQuery("select * from SMS_Program where CommandLine LIKE '%msiexec%/q %'")

For Each oProgram In oPrograms
        WScript.Echo "Package: " & oProgram.PackageID & "  (" & oProgram.ProgramName & ")"
        WScript.Echo "Orginal: " & oProgram.CommandLine

        sNewCmd = Replace(oProgram.CommandLine, "/q", "/qb!")
        WScript.Echo "    New: " & sNewCmd

        Set oModProgram = oSccmWmi.Get("SMS_Program.PackageID='" & oProgram.PackageID & "'" & ",ProgramName='" & oProgram.ProgramName & "'")
        oModProgram.CommandLine = sNewCmd
        oModProgram.Put_ ' Comment out this line if you want to test
        Set oModProgram = Nothing
Next

Trigger SCCM-client actions

If you need to trigger some SCCM-client actions via a script, this is a easy way to do it.

This script will trigger actions with “Request & Evaluate” in the name.

Option Explicit
Dim oCpApplet, oActions, oAction

On Error Resume Next
Set oCpApplet = CreateObject("CPAPPLET.CPAppletMgr")
If Err.Number <> 0 Then
    WScript.Echo "Missing SCCM-client"
    Err.Clear
Else
    WScript.Echo "Found following actions:"
    Set oActions = oCpApplet.GetClientActions
    For Each oAction In oActions
        If Instr(lCase(oAction.Name),"request & evaluate") Then
            WScript.Echo "  " & oAction.Name & " - Starting"
            oAction.PerformAction
        Else
            WScript.Echo "  " & oAction.Name
        End If
    Next
End If

Next Page »