Using Powershell with the SCCM SDK

Practical Blogging

Blogging is an art form much like public speaking. I once heard a wise man say that a blog like a presentation should have an awesome beginning an even better ending and the beginning and end should be as close together as possible. With a topic like Powershell and SCCM brevity is not an option, so to make this a good blog I created quick setup instructions at the bottom. This blog is meant to be practical and applicable to SCCM administrators but in a world where service packs take longer to install that the original operating systems, sometimes I lose my way. If you think I am missing the point you are probably right so post a comment. The Powershell examples posted are used in at least one of the Microsoft SCCM environments and meet a completely arbitrarily usefulness bar I created a few minutes ago.  The code in the scripts below are written for readability not brevity so many improvements can be made in terms of line count.  I will try to update the SCCM powershell module once a week but I might miss a few weeks here and there.  If you have suggestions for SCCM Powershell Modules don’t hesitate to post them below.

Using Powershell with the SCCM SDK

Powershell is my favorite feature from Microsoft. If there is one thing I can’t stand it is doing simple tasks the long way and doing them more than once is out of the question. Some Microsoft product user interfaces are intentionally designed to be “click deep” meaning that one “NEXT” button leads to another until the wizard ends with “Are you sure?” FINISH -> NEXT -> OK. Some times this is necessary considering that most powerful and dangerous person in an IT organization is the SCCM guy. OK well maybe the SCCM guy’s wife but let’s say she is visiting her mom in Japan this week. ???????????? A SCCM administrator that has full administrative access to SCCM literally has the power to shutdown the business. He could format every drive on every workstation or server where he has a client. He could make the motherboard siren blast on every computer for as long as he likes, or at least until his boss gives him a raise (From experience I can tell you this won’t work). A part of UI design principal is to prevent mistakes in configuration that are dangerous.  The SCCM click NEXT marathon is an example of just such a UI. For those of us that have learned hard lessons with SCCM, I wanted to share some Powershell automation modules that I use to prevent wear and tear on my mouse.

Setting up a SCCMPack module in Powershell V2

To see these techniques in action lets setup your Powershell environment for a SCCMPack Powershell module we are going to create. There are many things to consider about Powershell modules which are not the focus of this blog so if you want a Powershell Module deep dive I would start here. For the purpose of this blog,  I will show you how to create a module so as this blogs grows so does your functionality. We need to do the following things to get the ball rolling:

1.) Setup our Powershell module directory.

2.) Add a PS Module Manifest (PSD1) file.

3.) Add the PS Module (PSM1) file.

4.) Add a PS Module load script (PS1) file.

5.) Set our execution policy (Optional).

Setting up the Powershell module directory is simple. Powershell looks for Powershell Modules in 2 places by default, the “%userprofile%\windowspowershell\modules” and the %systemdrive%\Windows\System32\WindowsPowerShell\v1.0\Modules directories. So let’s open up Powershell and get started. Use the following command to create the Powershell module directory where our module will live.

 mkdir $env:UserProfile\Documents\WindowsPowerShell\Modules\SCCMPack

 

Then let’s use another command to create an empty Powershell module file. This file will contain all of our SCCM Powershell module function code.

 $null > $env:UserProfile\Documents\WindowsPowerShell\Modules\SCCMPack\SCCMPack.PSM1

 

We are going to need one more file so let’s create it as well.

 $null > $env:UserProfile\Documents\WindowsPowerShell\Modules\SCCMPack\SCCMPack.PS1

Now we create the Powershell Module Manifest file (.PSD1). Powershell module manifest files are processed by the built in Import-Module cmd-let and serve several different purposes. The two we are interested in are prerequisite checking and environment preparation.  When you run the command below Powershell will ask you some additional questions. No additional information is required so you can leave them blank.  Don’t forget to change the <Your Company> and <Your Name> tags.  The example below is meant to run on a single line so you will need to make some adjustments before you try to run it.

 New-ModuleManifest -ScriptsToProcess SCCMPack.PS1 
-PowerShellVersion 2.0 

-ModuleToProcess SCCMPack.PSM1

-ModuleVersion 1.0 

-CompanyName "<Your Company>" 

-Author "<Your Name>" 

-Guid $([guid]::NewGuid().ToString()) 

-Path $env:UserProfile\Documents\WindowsPowerShell\Modules\SCCMPack\SCCMPack.PSD1

Setting the right execution policy

Powershell is secure by default which means that it only allows signed scripts to run. We need to configure it to allow our PS modules to run unsigned. This step is not necessary if we sign the scripts include in this module.

The command below allows Powershell to run local unsigned scripts.  You will need to use an elevated powershell console for this command.

 Set-ExecutionPolicy Unrestricted

Creating the PS Module load Script

The PS module load script executes before the module is loaded and is referenced by the Powershell Module manifest we just created. It is used to configure your Powershell runspace and in this case we need it to add .net types from some of the DLL files referenced in the SCCM SDK documentation. There are other ways to accomplish this such as profile scripts but I like this method because the types are only loaded into the Powershell runspace when I need the module. I have commented the code for readability so please take a minute to look at what’s going on.  I have added some code to the script to get the DLLs from a remote location if the SCCM console in not installed on the local machine.  We need to add this code to the SCCMPack.ps1 file we created earlier.  I use powershell_ise.exe for editing.

 #These are the names of the DLLs we need put into a string array
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

$libs = @("microsoft.configurationmanagement.managementprovider.dll","adminui.wqlqueryengine.dll")

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#This registry key is created by the SCCM console installation.

#If the console is installed this key will contain the path to one of the files we need.

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

$SCCMConsoleReg = "HKLM:SOFTWARE\Microsoft\ConfigMgr\AdminUI\QueryProcessors\WQL" 

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
#We then use Get-ItemProperty to get the reg property value and put any error into the "e" error 

#Variable

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

$ItemProperty = Get-ItemProperty -Path $SCCMConsoleReg -ErrorAction SilentlyContinue -ErrorVariable e

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#If e was created we assume there is no local installation ask for a remote path to a console installation

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If($e)
    {
       #This line prompts for a path to the server console installation
       #So that if you dont want the console on you workstation the script will copy
       #Only the DLLs you need.
       #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       $adminlocation =  Read-Host -Prompt "Local or Remote Path to SCCM Console Installation"
       #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
       # Loop through our DLLs and make sure they are found
       #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       $libs | %{ 
                    
                    # If the files are not found in the path specified by the user
                    # throw a file not found exception
                    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    If($(Test-Path $(Join-Path -Path $adminlocation -ChildPath $_)) -eq $false)
                    {
                       throw [System.IO.FileNotFoundException]
                    }
                    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    
                    #This try catch block is a fast way to catch a file in use error.  If the 
                    #module is loaded twice into the same runspace the DLLs will be locked by
                    #powershell.  This is a quick workaround for that problem.
                    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                    try
                    {
                        Copy-Item -Path $(Join-Path -Path $adminlocation -ChildPath $_) -Destination $env:temp
                    }
                    
                    catch [System.IO.IOException]
                    {
                        
                    }
                    
                    catch [Exception]
                    {
                        Throw $_
                    }
                    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                }
      
      # Add the path of our copied files
      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      $adminui = get-item -Path $env:temp
      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~     
    }
    
    #If we enter this code block the console is installed locally
    #so we will load the local files
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    Else
    {
        $adminui = (get-item -Path $(($ItemProperty )."Assembly Path")).DirectoryName
    }
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    # Load each DLL using the add-type CMD-LET
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    $libs | %{
    
        $configmgrprov = get-item -Path $(Join-Path -Path $adminui -ChildPath $_)
        Add-Type -path $configmgrprov
    
    }
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Our powershell runspace is ready to go!

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The code above is used to add the SCCM SDK types to your Powershell environment using the Add-Type Cmd-Let. The code checks to see if the SCCM console is installed. If true,  it reads the location from the registry and uses the local DLL files. If the SCCM console is not installed it will prompt you for a location of the files and copy them to your local temp directory for use.

Let’s use the Test-ModuleManifest to make sure module works and our files are in the right place.

 Test-ModuleManifest $env:UserProfile\Documents\WindowsPowerShell\Modules\SCCMPack\SCCMPack.PSD1

If this runs without error we are ready to start adding code to our module.

The SCCM Connection object

The SCCM connection object is used for doing just about everything in the SCCM SDK examples.  Add the code below to the SCCMPack.PSM1 file we created earlier and save the file.  Each function you add to SCCMPack.PSM1 module will reuse this code to connect to the SCCM server.

 Function New-SCCMConnection 
{
param(
         
         [parameter(ValueFromPipeline=$True,
            HelpMessage="Please Provide the server name of the SCCM site server")]
         [String]
         $ServerName = ("localhost")
     )
     Try {
        
            $namedValues = New-Object Microsoft.ConfigurationManagement.ManagementProvider.SmsNamedValuesDictionary
            $connection =  New-Object Microsoft.ConfigurationManagement.ManagementProvider.WqlQueryEngine.WqlConnectionManager($namedValues)
        
            [void]$connection.Connect($serverName);
            }
       
       Catch [System.SystemException] 
            {
                throw $_
            }
            
       return $connection
}
Export-ModuleMember New-SCCMConnection

SCCM SDK Powershell Technique

One of the providers used by SCCM is WMI. Since this is the case you can query WMI directly for lots of information about SCCM. The code below grabs the list of collections just using WMI. You will need to change the server and namespace path if you want to try it out.

 gwmi -ComputerName <Primary Site Server> -Namespace root\sms\site_<SiteCode> -Class sms_collection

One thing to notice is that for this query to work you need to know the server name and the site code.  When we use the SCCM SDK DLLs only the server name is required for making a connection.

Accomplishing difficult tasks with vbscript is like making bricks without straw. It’s not impossible but requires a miracle. With Powershell we can get Pharaoh in the headlock, noogie his bald head until straw is delivered gift wrapped with a nice snow flake wrapping paper, and o ya... Let my people go!

I know you die hard VBScripters are a bit uneasy at this point so I offer this rational. There are 3 main reasons to use Powershell with the SCCM SDK DLLs instead of Vbscript/WMI:

1.) You cannot reasonably get 100% functionality going directly to WMI or using VBScript. With Powershell and the SDK DLLs you can do anything you can do in the console.

2.) Actions taken via the SDK DLLs honor the SCCM object security model. If you have invested any time (which I would recommend; blog here ) in SCCM object security you want your scripts to share those restrictions.

3.) Whether you like it or not Wscript is at end of life. Getting to know Powershell is a good thing.

 

Back to our Module - Loading and reloading the module

Loading our new module is simple.

 Import-Module sccmpack -force

 

Let’s add something useful

Getting the SCCM site server status via the SCCM console is one of those things that take a while especially in large environments. In fact it takes so much time that we often forget how useful it is. Open the SCCMPack.PSM1 and add the following code to the end of the file and save it.

 Function Get-SiteSystemStatus
{
param(
        [parameter(ValueFromPipeline=$True,
        HelpMessage="Please Provide the server name of the SCCM site server")]
        [String]
        $ServerName = ("localhost"),
        $Connection = $(New-SCCMConnection -ServerName $ServerName)
)
$result = $connection.QueryProcessor.ExecuteQuery("SELECT * FROM SMS_SiteSystemSummarizer");

$ar = @()
foreach ($item in $result.GetEnumerator())
{
   $ar +=  $item.propertylist
}
$psr = @()
$ar | %{
    
        $pso = new-object PSObject
        
        foreach ($item in $_.keys)
            {
               
              $pso | add-member -name $item -Value $_[$item] -MemberType NoteProperty -force
              
              $pso | add-member -name SiteReSource -MemberType ScriptProperty -Value {
                                            $ummm = $this.siteobject.split("=")
                                            $hmmm = $ummm[1].split([char]34)
                                            $surveysays = $hmmm[0]
                                            $ummm = $null
                                            $hmmm = $null
                                            $surveysays
                                           } -force
             
              $pso | add-member -name SiteColor -MemberType ScriptProperty -Value {
                                            If ($this.Status -eq "0")
                                            {"Green"}
                                            elseif ($this.Status -eq "1")
                                            {"Yellow"}
                                            else
                                            {"Red"} 
                                            
                                           } -force
                                        
              $pso | add-member -name ServerName -MemberType ScriptProperty -Value {
                                            $ummm = $this.sitesystem.split("=")
                                            $hmmm = $ummm[1].split([char]34)
                                            $surveysays = $hmmm[0]
                                            $ummm = $null
                                            $hmmm = $null
                                            $surveysays
                                           } -force 
                                                    
                
              } 
        $psr += $pso
        }
$outs = $psr | select-object -Property SiteColor,ServerName,SiteCode,Role,SiteResource,DownSince,PrecentageFree,BytesFree,BytesTotal
$result.dispose()

$connection.Close()

$connection.dispose()

$connection = $null

[GC]::Collect()

return $outs
}
Export-ModuleMember Get-SiteSystemStatus

Reload the SCCMPack module so we can use the new function.

 Import-Module SCCMPack -Force

Quick Setup

All you really need to do to get to this point create a folder in your profile documents directory call WindowsPowershell inside that create another directory called Modules and inside that create one last directory call SCCMPack.  Download these files to the the SCCMPack directory.  If you have not already done so set the powershell execution policy “Set-ExecutionPolicy Unrestricted” and then import the module using the “Import-Module SCCMPack –Force”.  You are all set.

 

Lets run it!

 Get-SiteSystemStatus –ServerName <Your SCCM Primary Site Server Name> | out-gridview

With our new SCCM Powershell Module we can get a nice gridview of roughly the same view you get in the SCCM console under site system status in just a few seconds.  Using the built in Out-Gridview CMD-Let we can search, sort, and filter to find our problem site systems.

More Importantly

We now have an organized framework for our  SCCM Powershell Modules and some cool first module functions to get started. If you are an SCCM administrator that has been using the console for a while, the potential of an SCCM Powershell Module will role your socks up and down. In my next post I was considering a couple of different topics. Please post below what you would like to see.

 

-Automated Patch Approvals by Category

-Duplicated Hardware ID detection

-Management Point configuration verification

-Purpose Driven Software Deployment

-User to Machine Mapping

-Server Role Usage Statistics

Technorati Tags: Powershell Modules,SCCM SDK