MSEndpointMgr

CMOperations PowerShell Module 1.0.0.4

Recently I’ve found myself constantly creating or copying little one or two line PowerShell scripts out of my one note to perform what I perceive as basic client action tasks. Sometimes those tasks have even required me to connect to a machine just to look at a registry key or read a log file. However, as I’ve been preparing an internal training course for my organization I came to the realization that there was no reason to hand out a bunch of links or one-liner scripts to do tasks if I just took the time to write out and maintain a PowerShell Module. Therefore I decided to create the CMOperations PowerShell Module. A module dedicated to the mindset of keeping it simple and leveraging WMI/CIM and client actions/information wherever possible to avoid any annoying pre-requisites like having the ConfigMgr console installed.

This module will be available in the ConfigMgr Repository for Major version updates and from my personal repository if you are looking for real time edits across the development process. I will also be taking suggestions from the community for new functions/features.

Major Versions: https://github.com/MSEndpointMgr/ConfigMgr/tree/master/Modules/CMOperations

Minor Versions: https://github.com/JordanTheITGuy/CMOperations

Currently supported in the module are the following functions:

Get Client Information

Functions within this region are geared around gathering information directly from the client and leverage either WMI or CIM sessions.

Get-NextAvailableMW

This function will get the next available maintenance window and supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
  • ConnectionTest – Optional – Will trigger the connection test found in the helper function section prior to querying the client.
  • SoftwareMW – Optional – Will look specifically for the next available Software Updates Maintenance Window
  • AllProgramsMW – Optional – will look specifically for the next available ‘All Deployments’ Update maintenance Window.
  • ProgramsMW – Optional – Will look specifically for the next available ‘program’ Maintenance Window.
Write-Verbose -Message "Attempting to connect to $ComputerName and retrieve next available MAINTENANCE WINDOW OF ANY TYPE"
$Window = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\clientsdk -ClassName CCM_ServiceWindow | Where-Object{ $_.type -eq 2 -or $_.Type -eq 1 -or $_.Type -eq 4 } | ForEach-Object{ [Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime) } | Sort $_.StartTime | Select-Object -First 1
#Gets the next available Programs Maintenance window from WMI and converts it to a datetime object
$Message = "Next available MAINTEANNCE window of any type for $ComputerName is " + $Window
$Message

Example: Get-NextAvailableMW -Computername $ENV:ComputerName -ConnectionTest -Verbose

Since I’m a workstation and my organization doesn’t use maintenance windows for workstations and I don’t care about user defined windows, I got what I expected. No result.

Get-LastSoftwareUpdateScan

This function will get the last time a software update scan was run, and the server it was run against. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
  • ConnectionTest – Optional – Will trigger the connection test found in the helper function section prior to querying the client.
Write-Verbose -Message "Attempting to gather Last Scanned WSUS Server name and time"
        $LastScanTime = Get-WmiObject -ComputerName $ComputerName -ErrorAction Stop -Namespace "Root\ccm\SCanAgent" -ClassName CCM_ScanUpdateSourceHistory | ForEach-Object { [Management.ManagementDateTimeConverter]::ToDateTime($_.LastCompletionTime) }
        #Connects to WMI And returns the date time object of the last time a WSUS scan was run. 
        $LastServerScanned = Get-WmiObject -computer $ComputerName -ErrorAction Stop -Namespace root\ccm\softwareupdates\wuahandler -Class CCM_updatesource | Select-Object ContentLocation
        #Connects to WMI and returns the last server that a software update scan was run against. 
        $Message = "Your computer $Computername last scanned at " + $LastScanTime + " against the server " + $LastServerScanned.ContentLocation
        $Message

Example: Get-LastSoftwareUpdateSCan -Computername $Env:ComputerName -ConnectionTest -Verbose

Get-LastHardwareScan

This function will get the last time a Hardware Scan was run. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
  • ConnectionTest – Optional – Will trigger the connection test found in the helper function section prior to querying the client.
Write-Verbose -Message "Attempting to connect and retrieve the instance for Hardware Inventory Information"
$obj = Get-WmiObject -computername $ComputerName -Namespace "root\ccm\invagt" -Class InventoryActionStatus -ErrorAction Stop | Where-Object { $_.InventoryActionID -eq "{00000000-0000-0000-0000-000000000001}" } | select PsComputerName, LastCycleStartedDate, LastReportDate
#Get the WMI Instance for the hardware scan information. 
Write-Verbose -Message "Retrieved WMI Instance for Hardware Scan Information"
$LastHWRun = $ComputerName + " last attempted Hardware inventory on " + [Management.ManagementDateTimeConverter]::ToDateTime($obj.LastCycleStartedDate)
#Convert the instance information into a date time object and send the data back to the screen.
$LastHWRun

Example: Get-LastHardwareScan -ComputerName $Env:ComputerName -ConnectionTest -verbose

Trigger Client Actions

Start-HardwareInventoryScan

This function will start a hardware scan on a remote client. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
  • ConnectionTest – Optional – Will trigger the connection test found in the helper function section prior to querying the client.
Write-Verbose -Message "Attempting to invoke a hardware inventory cycle"
Invoke-WMIMethod -ComputerName $ComputerName -ErrorAction Stop -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-000000000001}" | Out-Null
#Uses the invoke command to start scheduled action 001 - Hardware Inventory Scan Cycle
Write-Verbose -Message "The computer has started a hardware inventory cycle."
#When verbose flag is triggered notifies the user that the cycle has been started.

Example: Start-HardwareInventoryScan -Computername $ENV:Computername -ConnectionTest -Verbose

Start-SoftwareUpdateScan

This function will start a software update scan cycle on a remote client. It Supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
  • ConnectionTest – Optional – Will trigger the connection test found in the helper function section prior to querying the client.
Write-Verbose -message "Attempting to start a Software Update Scan Cycle"
Invoke-WmiMethod -ComputerName $ComputerName -ErrorAction Stop -Namespace root\ccm -Class SMS_CLIENT -Name TriggerSchedule "{00000000-0000-0000-0000-000000000113}" | Out-Null
#Uses the invoke command to start scheduled action 113 - Software Update Scan Cycle
Write-Verbose -Message "The computer has started a software update scan cycle."
#When verbose flag is triggered notifies the user that the cycle has been started.

Example: start-softwareUpdateScan -computername $env:computername -connectiontest -verbose

Software Update Actions

Get-UpdatesInSoftwareCenter

This function will get the currently available software updates in Software Center for a remote client. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
  • ConnectionTest – Optional – Will trigger the connection test found in the helper function section prior to querying the client.
Write-Verbose "Connecting to $ComputerName to query for updates in Software Center"
Get-WmiObject -ComputerName $ComputerName -ErrorAction Stop -Query 'SELECT * FROM CCM_SoftwareUpdate' -Namespace ROOT\ccm\ClientSDK -Verbose:$false | ft ArticleID, Name -AutoSize
#Connects to the WMI Client SDK namespace and returns teh updates that are currently pending installation or are displayed in software 
center.

Example: Get-UpdatesInSoftwareCenter -Computername $Env:computername -verbose -connectiontest

Install-UpdatesInSoftwareCenter

This function will install updates that are available within Software Center for a remote client. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
  • ConnectionTest – Optional – Will trigger the connection test found in the helper function section prior to querying the client.
  • AllUpdates – Optional – Will instruct the computer to install all updates in software center.
  • ArticleID – Optional – will instruct the computer to install a list of updates provided in an array format – ‘401223’,’4022365′
Write-Verbose "All Updates was selected for $ComputerName attempting to install all available updates in software center"
$Updates = [System.Management.ManagementObject[]](Get-WmiObject -ComputerName $ComputerName -ErrorAction Stop -Query 'SELECT * FROM CCM_SoftwareUpdate' -Namespace ROOT\ccm\ClientSDK -Verbose:$false)
#build an array in the variable $Updates that contains all udpate articles
([wmiclass]"\\$ComputerName\ROOT\ccm\ClientSDK:CCM_SoftwareUpdatesManager").InstallUpdates([System.Management.ManagementObject[]]$Updates) | Out-Null
#Using the older WMI methodology run the install updates wmi method to install allupdates in the $updates object.
Write-Verbose "$ArticleID were selected for $ComputerName attempting to install updates"
$Updates = [System.Management.ManagementObject[]](Get-WmiObject -ComputerName $ComputerName -ErrorAction Stop -Query 'SELECT * FROM CCM_SoftwareUpdate' -Namespace ROOT\ccm\ClientSDK -Verbose:$false) | Where-Object { $_.ArticleID -in $ArticleID }
#build an array in the variable $Updates that contains all udpate articles that are in the articleID list provided by the user.
([wmiclass]"\\$ComputerName\ROOT\ccm\ClientSDK:CCM_SoftwareUpdatesManager").InstallUpdates([System.Management.ManagementObject[]]$Updates) | Out-Null
#Using the older WMI methodology run the install updates wmi method to install allupdates in the $updates object.

Example: install-UpdatesinsoftwareCenter -ComputerName $Env:ComputerName -ConnectionTest -Verbose -AllUpdates

Example: Install-UpdatesInSoftwareCenter -ComputerName $env:computerName -connectiontest -verbose -ArticleID ‘401267’

Helper Functions

Test-Connectivity

This function is a helper function and is not exported as an available cmdlet/function. It is leveraged to perform network tests to a remote client before attempting to take action. If  one of the connection tests should not be used comment them out from the try statement. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
Try
#Try each connection test. If there is a connection test that you do not want to use remove it by commenting out the line.
{
  Test-Ping -ComputerName $ComputerName -ErrorAction Stop
  Test-AdminShare -ComputerName $ComputerName -ErrorAction Stop
  Test-WinRM -ComputerName $ComputerName -ErrorAction Stop
  Write-Verbose -Message "$ComputerName has passed all connection tests"
  return $true
}
CATCH
{
  $ConnectionStatus = $false
  Write-Verbose "$ComputerName failed a connection test."
  return $false
}
Test-Ping

This function is a helper function and is not exported as an available cmdlet/function. It is leveraged by the test-connectivity function. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
$PingTest = Test-Connection -ComputerName $ComputerName -BufferSize 8 -Count 1 -Quiet
If ($PingTest)
{
  Write-Verbose "The Ping test for $ComputerName has PASSED"
}
Else
{
  Write-Verbose "$ComputerName failed ping test"
  throw [System.Net.NetworkInformation.PingException] "$ComputerName failed ping test."
}
Test-AdminShare

This function is a helper function and is not exported as an available cmdlet/function. It is leveraged by the test-connectivity function. This will in the future test so that C$ is available to connect or read logs remotely. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
$AdminShare = "\\" + $ComputerName + "\C$"
$AdminAccess = Test-Path -Path $AdminShare -ErrorAction Stop
if ($AdminAccess)
{
  Write-Verbose "The admin share connection test $ComputerName has PASSED"
  $ConnectionStatus = $true
}
Else
{
  Write-Verbose "$ComputerName admin share not found"
  throw [System.IO.FileNotFoundException] "$ComputerName admin share not found"
  
}
Test-WinRM

This function is a helper function and is not exported as an available cmdlet/function. It is leveraged by the test-connectivity function. It supports the following parameters:

  • ComputerName – Required – Name of the device you would like to connect to.
Try
{
  Test-WSMan -computername $ComputerName -ErrorAction Stop
  Write-Verbose "The WINRM check for $ComputerName has PASSED"
}
Catch
{
  throw [System.IO.DriveNotFoundException] "$ComputerName cannot be connected to via WINRM"
}

Jordan Benzing

Jordan has been working in the Industry since 2009. Since starting he’s worked with Active Directory, Group Policy, SCCM, SCOM and PowerShell. Jordan most recently worked in the healthcare industry as an SCCM Infrastructure Team lead supporting over 150,000 endpoints. Jordan currently works as a Senior consultant for TrueSec Inc in the U.S. Most recently his focus has been in SQL Reporting for SCCM, creation of PowerShell scripts to automate tasks and PowerBI.

3 comments

  • The MWs times aren’t converting to the correct times. CCM_ServiceWindow is using a UInt32 data type for Start/End times which alludes me in this case. Splitting the line at the “.” and moving forward with a [datetime]::parseexact does give the correct time. In testing, I didn’t see a difference with the +/-, but didn’t dig deep to see if that affected DLS. Should be an easy fix is that is the case. Here is the code I switched over for line 50 #timeconvert. Note, the HH is case sensitive to convert 24-hour times.

    # Time convert
    $StartTimes = @()
    ForEach ($Start in $Window.StartTime)
    {
    $StartTimes += $($start.split(“.”)[0])
    }
    $Start = $StartTimes | ForEach-Object {[datetime]::ParseExact($_,’yyyyMMddHHmmss’,$Null)} | Sort-Object $_ | Select -First 1

    $EndTimes = @()
    ForEach ($End in $Window.EndTime)
    {
    $EndTimes += $($End.split(“.”)[0])
    }
    $End = $EndTimes | ForEach-Object {[datetime]::ParseExact($_,’yyyyMMddHHmmss’,$Null)} | Sort-Object $_ | Select -First 1

    $start
    $end

    PS. No time for GitHub stuff.

    • I will update this in the next revision will also likely include a switch to convert to UTC.

  • Jordan – lot’s of awesome functions here!!! Like you, I just have all mine saved in files randomly and go find them when I need them. As a minor improvement, next time you’re making updates, maybe add a prefix unique to your cmdlet names, so they’re named like “Get-CMOUpdatesInSoftwareCenter” and “Start-CMOSoftwareUpdateScan” just so that 1) They’re all easily identified as being in this module and 2) You don’t clash with some other name later on. Just my $0.02!

Sponsors

Categories

MSEndpointMgr.com use cookies to ensure that we give you the best experience on our website.