Code from my Session at the Northwest System Center User Group

Big thanks to the Northwest System Center User Group for hosting me at their March meeting. We had a great demo session to show how to use PowerShell with ConfigMgr.

To enable ease of search, I’m posting the PowerShell code into this post. If you want all the code, you can download here.

Demo 1 – Samples of using PowerShell to quickly pull info from ConfigMgr, and to see the ‘guts’ behind a ConfigMgr cmdlet (see the WMI that is being called):

#http://www.gregramsey.net
#Test, test, test  - test everything you - safety First!
#general overview of using Powershell with configMgr - examples of show-command, debug, etc.

#region Import CM Module
$CMModulePath = `
    $Env:SMS_ADMIN_UI_PATH.ToString().SubString(0,$Env:SMS_ADMIN_UI_PATH.Length - 5) `
    + "\ConfigurationManager.psd1" 
Import-Module $CMModulePath -force
cd GMR:
#endregion

#get CM cmdlets
get-command -Module ConfigurationManager | Out-GridView

#show commands for set-cmapplication
show-command Set-CMApplication

#Show Devices
Get-CMDevice | Out-GridView

#List all members
Get-CMDevice | Get-Member

#select Specific Objects
Get-CMDevice | Select-Object Name, SiteCode, ADSiteName, DeviceOS, 
    Domain, IsActive, LastActiveTime, LastClientCheckTime, LastDDR, 
    LastHardwareScan, LastMPName, LastPolicyReqeust | Out-GridView
#Group-By
Get-CMDevice | Select-Object Name, SiteCode, ADSiteName, DeviceOS, 
    Domain, IsActive, LastActiveTime, LastClientCheckTime, LastDDR, 
    LastHardwareScan, LastMPName, LastPolicyReqeust | Group-Object DeviceOS | Out-GridView

#Show Debug
Get-CMDevice -Debug | Out-GridView

#grab wmi query and run it.

Demo 2 – Samples of manipulating collecftions, handling lazy properties, etc.

#http://www.gregramsey.net
#Samples of manipulating collecftions, handling lazy properties, etc.


#region Import CM Module
$CMModulePath = `
    $Env:SMS_ADMIN_UI_PATH.ToString().SubString(0,$Env:SMS_ADMIN_UI_PATH.Length - 5) `
    + "\ConfigurationManager.psd1" 
Import-Module $CMModulePath -force
cd GMR:
#endregion

#quick display of Client info for a collection
get-cmdevice -CollectionName "Test Coll" | select Name, ADLastLogonTime, ADSiteName, `
    ClientVersion, DeviceOwner,Domain,LastActiveTime,LastHardwareScan, LastMPServerName, `
    SiteCode | Out-GridView

#write to .csv
get-cmdevice -CollectionName "Test Coll" | select Name, ADLastLogonTime, ADSiteName, `
    ClientVersion, DeviceOwner,Domain,LastActiveTime,LastHardwareScan, LastMPServerName, `
    SiteCode | export-csv c:\logs\ClientInfo.csv -NoTypeInformation
notepad c:\logs\ClientInfo.csv

#View Collection info
get-cmdevicecollection -name "Test Coll"

#look at schedule
show-command New-CMSchedule

#create a collection, with schedule
$Sched = New-CMSchedule -DayOfWeek Saturday
$MyColl = New-CMDeviceCollection -Name "Test Coll2" -LimitingCollectionName `
    "All Desktop and Server Clients" -RefreshSchedule $Sched -RefreshType Periodic

#Add rule to exclude my "All DCs Collection"
Add-CMDeviceCollectionExcludeMembershipRule -CollectionName "Test Coll2" `
    -ExcludeCollectionName "All DCs"

#now, update the schedule (dipping into wmi) - #Chris Mosby Special 🙂
$Sched = New-CMSchedule -RecurCount 7 -RecurInterval Days -Start ([datetime] "2/27/2015 02:00")
$Coll = Get-WmiObject -Class SMS_Collection -Namespace "Root\SMS\Site_gmr" `
    -Filter "Name='Test Coll2'"
$Coll.RefreshType = 6  #1=manual, 2=periodic, 4=constant, 6=periodic+constant
$Coll.RefreshSchedule = $Sched.psbase.ManagedObject
$Coll.Put()

#look at the schedule with gwmi
$Coll = Get-WmiObject -Class SMS_Collection -Namespace "Root\SMS\Site_gmr" `
    -Filter "Name='Test Coll2'"
$Coll.refreshschedule    # (Refreshschedule is blank...)

#you see refreshschedule info here:
$coll = Get-CMDeviceCollection -name "Test Coll2"
$coll.RefreshSchedule

#why is refreshschedule missing when get-wmiobject?

#need to handle lazy property..... (check sms_collection Class for lazy props)
$Coll = Get-WmiObject -Class SMS_Collection -Namespace "Root\SMS\Site_gmr" `
    -Filter "Name='Test Coll2'"
$coll.__path
$coll2 = [wmi]$coll.__path  #get specific instance using the [wmi] and __Path 
$coll2.RefreshSchedule

#Create new Maintenance Window
#Create new MW Schedule - every Saturday at 2:00AM for 3 Hours
$mwSched = New-CMSchedule -DayOfWeek Saturday -Start ([datetime]"2:00 am") `
    -End ([datetime]"2:00 am").addhours(3) 
$CollID = (Get-CMDeviceCollection -name "Test Coll2").CollectionID
New-CMMaintenanceWindow -CollectionID $CollID -ApplyToSoftwareUpdateOnly `
    -Name "Test Coll2 MW" -Schedule $mwsched

#show existing mw
Get-CMMaintenanceWindow -CollectionID $CollID

#Add Direct Membership Rule
Add-CMDeviceCollectionDirectMembershipRule -CollectionName $Coll2.Name `
    -ResourceId (get-CMDevice -name CM2012R2).ResourceID

#Add Multiple Direct Membership Rules
notepad c:\logs\systems.txt
get-content c:\logs\systems.txt | foreach-object {
    Add-CMDeviceCollectionDirectMembershipRule -CollectionName $Coll2.Name `
        -ResourceId (get-CMDevice -name $PSItem).ResourceID
}

#Create Query Rule
$WQLQuery = `
    "select * from sms_r_system Where OperatingSystemNameAndVersion like '%Workstation%'"
Add-CMDeviceCollectionQueryMembershipRule -CollectionName $Coll.Name `
    -RuleName "All Workstations" -QueryExpression $WQLQuery


#perform surgery - remove one rule
Remove-CMDeviceCollectionDirectMembershipRule -CollectionName "Test Coll2" `
    -ResourceName "W81x64"  #use -force toskip confirmation

#Remove all direct membership rules
$CollectionName = "Test Coll2"
$coll = Get-CMDeviceCollection -name $CollectionName 
$coll.CollectionRules | where-object {$PSItem.ObjectClass -eq "SMS_CollectionRuleDirect"} |
    foreach-object { 
        Remove-CMDeviceCollectionDirectMembershipRule -collectionname $CollectionName `
        -resourceID $_.ResourceID -force
    }

#Update Collection membership now
Invoke-CMDeviceCollectionUpdate -Name "Test Coll2"

#Remove Collection use -force to not get prompted
Remove-CMDeviceCollection -name "Test Coll2" -force

Demo 3 – examples of manipulating packages, programs, programflags, etc.

#http://www.gregramsey.net
#examples of manipulating packages, programs, programflags, etc.


#region Import CM Module
$CMModulePath = `
    $Env:SMS_ADMIN_UI_PATH.ToString().SubString(0,$Env:SMS_ADMIN_UI_PATH.Length - 5) `
    + "\ConfigurationManager.psd1" 
Import-Module $CMModulePath -force
cd GMR:
#endregion

Get-CMPackage | select Manufacturer, Name, PackageID, LastRefreshTime, PkgSourcePath | Out-GridView
Get-CMPackage | export-csv c:\logs\Allpackages.csv -NoTypeInformation


#Explore Program
$Program = Get-CMProgram -ProgramName "Cumulative update 1 - console update install" `
    -PackageId "GMR00006"
#Look at ProgramFlags
$Program
$Program.ProgramFlags  # should be 135307264
$oldPFlag = $Program.ProgramFlags
$oldbinary = [Convert]::ToString($Program.ProgramFlags, 2)
$oldbinary

#now manually change it in the gui - 'allow this program to install...'
$Program = Get-CMProgram -ProgramName "Cumulative update 1 - console update install" `
    -PackageId "GMR00006"
$Program.ProgramFlags  # should be 135307265
$binary = [Convert]::ToString($Program.ProgramFlags, 2)
$binary
$oldbinary

# List all Programs with this enabled:
# "Allow this program to be installed from the Install 
#  Package task sequence action without being deployed."
Get-CMProgram | foreach {
    if ($_.ProgramFlags -band 0x1) {
    "{0} {1} {2}" -f $_.ProgramName, $_.PackageID, "Enabled"
    }
}

#Enable this setting on all Programs
Get-CMProgram | foreach {
    if  ($_.ProgramFlags -band 0x1) {
        #Already Enabled
    }
    else {
        #currently disabled, enable now
        "{0} {1} {2}" -f $_.ProgramName, $_.PackageID, "Enabling"
        $_.ProgramFlags = $_.ProgramFlags -bxor 0x1
        $_.Put()
    }
}

#Disable this setting on all Programs
Get-CMProgram | foreach {
    if  ($_.ProgramFlags -band 0x1) {
        #currently enabled, disable now
        "{0} {1} {2}" -f $_.ProgramName, $_.PackageID, "Disabling"
        $_.ProgramFlags = $_.ProgramFlags -bxor 0x1
        $_.Put()
    }
    else {
        #Already Disabled
    }
}


#List all disabled programs   
Get-CMProgram | foreach {
    if  ($_.ProgramFlags -band 0x1000) {
        #disabled
        "{0} {1} {2}" -f $_.ProgramName, $_.PackageID, "Disabled"
    }
    else {
        #enabled
    }
}

#Update Supported OperatingSystems - Need to step into WMI
$prg = gwmi sms_program -namespace root\sms\site_gmr `
    -filter "ProgramName='Fake Program' and PackageID='GMR00017'"
$prg = [wmi]$prg.__PATH
$prg.SupportedOperatingSystems

#look at example supported OS (to make sure correct min/max version
$pkgID = (Get-CMPackage -name "MDT Settings").PackageID
$prg = Get-CMProgram -ProgramName "fp2" -PackageId $pkgID
$prg.SupportedOperatingSystems

#use WMI to grab the program
$prg = gwmi sms_program -namespace root\sms\site_gmr -filter "ProgramName='Fake Program' and PackageID='GMR00017'"
#neet to getinstance to pull the lazy properties
$prg = [wmi]$prg.__PATH
$prg.SupportedOperatingSystems

#create a new instance of SMS_OS_Details, so we can add it to Supported OS (Win8.1 x64)
$OS = ([wmiclass]"\\CM2012R2.GMR.net\root\sms\site_GMR:SMS_OS_Details").Createinstance()
$OS.MaxVersion = "6.30.9999.9999"
$OS.MinVersion = "6.30.0000.0"
$OS.Name = "Win NT"
$OS.Platform = "x64"
$OS

$prg.SupportedOperatingSystems = $prg.SupportedOperatingSystems + $OS
$prg.Put()

#List all packages configured to "Copy the content in this package 
# to a package share on distribution points"
#PackageFlag = 0x80
Get-CMPackage | foreach {
   if  ($_.PkgFlags -band 0x80) {
        #Enabled
        $_.Packageid + " - " + $_.Name

    }
    else {
        #disabled
    }

}

Demo 4 – add 8.1 support to all package/programs that currently have support for 8.0.

#http://www.gregramsey.net
#add 8.1 support to all package/programs that currently have support for 8.0.

#create a new instance of SMS_OS_Details, so we can add it to Supported OS (Win8.1 x64)
$OS = ([wmiclass]"\\CM2012R2.GMR.net\root\sms\site_GMR:SMS_OS_Details").Createinstance()
$OS.MaxVersion = "6.30.9999.9999"
$OS.MinVersion = "6.30.0000.0"
$OS.Name = "Win NT"
$OS.Platform = "x64"

Get-WmiObject sms_program -Namespace root\sms\site_gmr | foreach-object {
    $prg = [wmi]$PSItem.__Path
    #supported platform is set, and Win8x64 is supported platform
    if (($prg.ProgramFlags -band 0x8000000) -eq 0 -and `
        ($prg.SupportedOperatingSystems.minversion -contains "6.20.0000.0") ) { 
            #check if Win81x64 is a supported platform
            if ($prg.SupportedOperatingSystems.minversion -notcontains "6.30.0000.0") {
                #no. .so add it
                "Updating {0}" -f $prg.programname
                $prg.SupportedOperatingSystems = $prg.SupportedOperatingSystems + $OS
                $prg.Put()
            }
        }
    #}
}

Happy Scripting!

Greg

ramseyg@hotmail.com

This post first appeared on http://www.gregramsey.net

How to: Extract Status Message Information from ConfigMgr 2012 R2

During MMS 2014, Wally Mead and I presented a session about state and status messages. This post describes one of the scripts that I demonstrated, which uses PowerShell to display status messages in a human-readable-friendly manner. This is similar to using the ConfigMgr status message viewer or SSRS reports, in that it displays the full sentence and description of the status message information. The value that this script adds, is that you can generate the information outside of the ConfigMgr Console (and outside of the SSRS reports).

First, the code (viewed best with Chrome browser): download GetStatusMessages.ps1

#https://gregramsey.net/?p=882
$StatMsgs = @()
$cnstring='Provider=SQLOLEDB;Integrated Security=SSPI;Persist Security Info=False;' + `
 'Initial Catalog=CM_LAB;Data Source=mylab.lab.com;User ID=;Password='
Add-Type -Path `
 "D:\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\bin\srsresources.dll"

$cmdtext = @"
select top 1000 smsgs.RecordID,
CASE smsgs.Severity
WHEN -1073741824 THEN 'Error'
WHEN 1073741824 THEN 'Informational'
WHEN -2147483648 THEN 'Warning'
ELSE 'Unknown'
END As 'SeverityName',
smsgs.MessageID, smsgs.Severity, modNames.MsgDLLName, smsgs.Component,
smsgs.MachineName, smsgs.Time, smsgs.SiteCode, smwis.InsString1,
smwis.InsString2, smwis.InsString3, smwis.InsString4, smwis.InsString5,
smwis.InsString6, smwis.InsString7, smwis.InsString8, smwis.InsString9,
smwis.InsString10
from v_StatusMessage smsgs
join v_StatMsgWithInsStrings smwis on smsgs.RecordID = smwis.RecordID
join v_StatMsgModuleNames modNames on smsgs.ModuleName = modNames.ModuleName
--where smsgs.MessageID = 10803 and smsgs.MachineName in
-- (select name from _res_coll_CEN00018)
--where smsgs.MachineName = 'mycomputername'
Order by smsgs.Time DESC
"@

$sql

$cn = New-Object System.Data.OleDb.OleDbConnection $cnstring
$cn.Open()

$cmd = New-Object System.Data.OleDb.OleDbCommand $cmdtext, $cn
$cmd.CommandTimeout = 0
$reader = $cmd.ExecuteReader()
while ($reader.read())
{
$FullMsgString = `
 [SrsResources.Localization]::GetStatusMessage([int]$reader["MessageID"],
 [int]$reader["Severity"], $reader["MsgDllName"].ToString(), "en-US",
 $reader["InsString1"].ToString(), $reader["InsString2"].ToString(),
 $reader["InsString3"].ToString(), $reader["InsString4"].ToString(),
 $reader["InsString5"].ToString(), $reader["InsString6"].ToString(),
 $reader["InsString7"].ToString(), $reader["InsString8"].ToString(),
 $reader["InsString9"].ToString(), $reader["InsString10"].ToString())
$FullMsgString = $FullMsgString -replace '([\.][\r])[\n]','.'
$FullMsgString = $FullMsgString -replace '([\.][\r])[\n]','.'
$obj = @{
SeverityName = $reader["SeverityName"].ToString()
MessageID = $reader["MessageID"].ToString()
Severity = $reader["Severity"].ToString()
MsgDLLName = $reader["MsgDLLName"].ToString()
Component = $reader["Component"].ToString()
MachineName = $reader["MachineName"].ToString()
Time = $reader["Time"].ToString()
SiteCode = $reader["SiteCode"].ToString()
InsString1 = $reader["InsString1"].ToString()
InsString2 = $reader["InsString2"].ToString()
InsString3 = $reader["InsString3"].ToString()
InsString4 = $reader["InsString4"].ToString()
InsString5 = $reader["InsString5"].ToString()
InsString6 = $reader["InsString6"].ToString()
InsString7 = $reader["InsString7"].ToString()
InsString8 = $reader["InsString8"].ToString()
InsString9 = $reader["InsString9"].ToString()
InsString10 = $reader["InsString10"].ToString()
FullMsgString = $FullMsgString
}

$statmsgs += new-object psobject -Property $obj
}
$cn.Close()

$wmidate = new-object -com Wbemscripting.swbemdatetime
$date = get-date -format g
$wmidate.SetVarDate($date,$true)
$csv = "C:\logs\Statmsgs_" + $wmidate.value.substring(0,12) + ".csv"

$statmsgs | select-object SeverityName, MessageID, Component,
 MachineName, Time, SiteCode, FullMsgString `
 | export-csv $csv -notypeinformation
$statmsgs | select-object SeverityName, MessageID, Component, MachineName,
 Time, SiteCode, FullMsgString `
 | out-gridview
$statmsgs | group-object component | sort count -descending | out-gridview

To run this code in your environment, update the following:

  • Line 4 – modify CM_LAB and mylab.lab.com to your server database and database server name.
  • Line 6 – modify path to your SSRS server dll.
  • Create c:\logs directory, or modify line 78.

The easiest location to run this code is on an active ConfigMgr Reporting Point (otherwise, you may have to copy/register some .dlls from your reporting point).

Here’s a breakdown of some of the code, and some ideas on how to customize it for your environment.

  • Lines 8-28 – this is the SQL code that’s run against your ConfigMgr database – you should be able to run it manually as well. Notice that as written, we are querying the last 1000 status messages. Modify the query to filter by machine name, systems in a collection, or specific status message IDs.
  • After executing the SQL query, (lines 32-37), we iterate through each result, create a custom object, and add it to an array (line 72). By creating the custom object for this data, we can perform other cool actions (as described next).
  • Lines 76-83 are used to easily create a .csv file (with a dynamic filename, based on the current date in wmi date-time format.
  • Lines 84-86 are used to display the information into a gridview – I find this view very handy, in that I can quickly look at data, filter, sort, etc. without much effort.
  • Line 87 also generates a gridview – but this time, I used it with the group-object cmdlet  to easily show how many times a specific message is received, grouped by component name.

Happy Scripting!

Greg

ramseyg@hotmail.com

This post first appeared on http://www.gregramsey.net