Calculate Percent on Compliance SCCM Powershell by Device collection

Copper Contributor

 

I have a function that pulls compliance status of a software update deployment (assignment ID). I want to configure this to calculate percent of the status types (Success, Error, In Progress, Unknown) and I would also like to pull compliance status based on device collection name instead of the deployment ID as it is shown below.

 

What I want to accomplish is automatically take a list of device collection names and run a software update deployment which organizes each collection by compliance percent (for each status), and then receive it by email every morning. 

 

I have several locations based on device collection, and I want to be able to differentiate which locations and devices are the problematic ones. 

 

This method makes tracking and reporting much easier and SCCM console will not have to be launched each individual time to get reporting. 

 

 

function Get-SCCMSoftwareUpdateStatus {

 

    [CmdletBinding()]

 

    param(

        [Parameter()]

        [switch]  $DeploymentIDFromGUI,

 

        [Parameter(Mandatory = $false)]

        [Alias('ID', 'AssignmentID')]

        [string]   $DeploymentID,

         

        [Parameter(Mandatory = $false)]

        [ValidateSet('Success', 'InProgress', 'Error', 'Unknown')]

        [Alias('Filter')]

        [string]  $Status

 

 

    )

 

    BEGIN {

        $Site_Code   = 'ABC'

        $Site_Server = 'SYSTEMCENTERSERVERNAME'

        $HasErrors   = $False

 

        if ($Status -eq 'Success') {

            $StatusType = 1

        }

 

        if ($Status -eq 'InProgress') {

            $StatusType = 2

        }

 

        if ($Status -eq 'Unknown') {

            $StatusType = 4

        }

 

        if ($Status -eq 'Error') {

            $StatusType = 5

        }

 

    }

 

    PROCESS {

        try {

            if ($DeploymentID -and $DeploymentIDFromGUI) {

                Write-Error "Select the DeploymentIDFromGUI or DeploymentID Parameter. Not Both"

                $HasErrors   = $True

                throw

            }

 

            if ($DeploymentIDFromGUI) {

                $ShellLocation = Get-Location

                Import-Module (Join-Path $(Split-Path $env:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1)

                 

                #Checking to see if module has been imported. If not abort.

                if (Get-Module ConfigurationManager) {

                        Set-Location "$($Site_Code):\"

                        $DeploymentID = Get-CMSoftwareUpdateDeployment | select AssignmentID, AssignmentName | Out-GridView -OutputMode Single -Title "Select a Deployment and Click OK" | Select -ExpandProperty AssignmentID

                        Set-Location $ShellLocation

                    } else {

                        Write-Error "The SCCM Module wasn't imported successfully. Aborting."

                        $HasErrors   = $True

                        throw

                }

            }

 

            if ($DeploymentID) {

                    $DeploymentNameWithID = Get-WMIObject -ComputerName $Site_Server -Namespace root\sms\site_$Site_Code -class SMS_SUMDeploymentAssetDetails -Filter "AssignmentID = $DeploymentID" | select AssignmentID, AssignmentName

                    $DeploymentName = $DeploymentNameWithID.AssignmentName | select -Unique

                } else {

                    Write-Error "A Deployment ID was not specified. Aborting."

                    $HasErrors   = $True

                    throw  

            }

 

            if ($Status) {

                   $Output = Get-WMIObject -ComputerName $Site_Server -Namespace root\sms\site_$Site_Code -class SMS_SUMDeploymentAssetDetails -Filter "AssignmentID = $DeploymentID and StatusType = $StatusType" | `

                    select DeviceName, CollectionName, @{Name = 'StatusTime'; Expression = {$_.ConvertToDateTime($_.StatusTime) }}, @{Name = 'Status' ; Expression = {if ($_.StatusType -eq 1) {'Success'} elseif ($_.StatusType -eq 2) {'InProgress'} elseif ($_.StatusType -eq 5) {'Error'} elseif ($_.StatusType -eq 4) {'Unknown'}  }}

 

                } else {      

                    $Output = Get-WMIObject -ComputerName $Site_Server -Namespace root\sms\site_$Site_Code -class SMS_SUMDeploymentAssetDetails -Filter "AssignmentID = $DeploymentID" | `

                    select DeviceName, CollectionName, @{Name = 'StatusTime'; Expression = {$_.ConvertToDateTime($_.StatusTime) }}, @{Name = 'Status' ; Expression = {if ($_.StatusType -eq 1) {'Success'} elseif ($_.StatusType -eq 2) {'InProgress'} elseif ($_.StatusType -eq 5) {'Error'} elseif ($_.StatusType -eq 4) {'Unknown'}  }}

            }

 

            if (-not $Output) {

                Write-Error "A Deployment with ID: $($DeploymentID) is not valid. Aborting"

                $HasErrors   = $True

                throw

                 

            }

 

        } catch {

             

         

        } finally {

            if (($HasErrors -eq $false) -and ($Output)) {

                Write-Output ""

                Write-Output "Deployment Name: $DeploymentName"

                Write-Output "Deployment ID:   $DeploymentID"

                Write-Output ""

                Write-Output $Output | Sort-Object Status

            }

        }

    }

 

    END {}

 

}

10 Replies

@NAN2019 

Hi,

I had a small question just to confirm that I understand your question, so what you need is to get the result for all the deployment packages which listed in the following line

$DeploymentID = Get-CMSoftwareUpdateDeployment | select AssignmentID, AssignmentName | Out-GridView -OutputMode Single -Title "Select a Deployment and Click OK" | Select -ExpandProperty AssignmentID

 and get the percentage of how many deployment was success, failure ...etc

so the output will look like

Success: 40%

Error: 2%

Unknown: 50%

and so on, Yes?!

@farismalaeb 

 

1) Yes. I would like to show a clear percentage of each status (for example: Success 12% , 40% failed), for the Software update deployment. 

 

2) The 2nd request was to take the specific software update deployment and run it against device collection names. Maybe the device collection could either be in a for-each statement or if its possible to take the device collection name itself and do it. So in a sense I would have: 

 

Chicago - 75% success, 25% failed, 0% In progress, 0% failed

Cleveland - 25% success, 25% failed, 50% In progress, 0% failed

Houston - 75% success, 25% failed, 0% In progress, 0% failed

Miami - 0% success, 25% failed, 25% In progress, 50% failed

 

And I would like it to be in a csv as well as in the txt body of an email sent every morning. 

@NAN2019 

 

I wrote a quick update for script, review the change and test it, I don't have a big SCCM env, but this seems to be as you want.

Function Get-AllPackages{
$AllPack = Get-CMSoftwareUpdateDeployment | select AssignmentID, AssignmentName
return $AllPack
} 

$Packages= Get-AllPackages

foreach ($SinglePack in $Packages) {
Write-Host "--------------"
    Write-Host $SinglePack.AssignmentName
    $packinfo=Get-SCCMSoftwareUpdateStatus -DeploymentID $SinglePack.AssignmentID 
        $Status=$packinfo.status | Get-Unique
        foreach ($sinlgestatus in $Status){
        Write-Host "The Status of $($sinlgestatus) for $($SinglePack.AssignmentName)" 
            ((($packinfo | where {$_.status -like $sinlgestatus})).status.count / ($packinfo.Count -4)).tostring("P")
        

            }
} 

 

There is no change on the main function, I added extra function and some foreach loop to get the status. 

Hope it helps

 

 

---------------

If the above reply answers your question, please don't forget to click on Best Response.

 

Here is what I get. See error output below. 

So seems like this grabs all assignment ID’s, which would be every software update deployment. May be too much data even if I get is working. 

How about a specific Assignment ID?

 

At line:1 char:173
+ ... $AllPack } $Packages= Get-AllPackages foreach ($SinglePack in $Packag ...
+                                                                ~~
Unexpected token 'in' in expression or statement.
At line:1 char:172
+ ... rn $AllPack } $Packages= Get-AllPackages foreach ($SinglePack in $Pac ...
+                                                                  ~
Missing closing ')' in expression.
At line:1 char:185
+ ... $Packages= Get-AllPackages foreach ($SinglePack in $Packages) { Write ...
+                                                                 ~
Unexpected token ')' in expression or statement.
At line:1 char:394
+ ... tatus=$packinfo.status | Get-Unique foreach ($sinlgestatus in $Status ...
+                                                                ~~
Unexpected token 'in' in expression or statement.
At line:1 char:393
+ ...  $Status=$packinfo.status | Get-Unique foreach ($sinlgestatus in $Sta ...
+                                                                  ~
Missing closing ')' in expression.
At line:1 char:187
+ ... ackages= Get-AllPackages foreach ($SinglePack in $Packages) { Write-H ...
+                                                                 ~
Missing closing '}' in statement block or type definition.
At line:1 char:404
+ ... ckinfo.status | Get-Unique foreach ($sinlgestatus in $Status){ Write- ...
+                                                                 ~
Unexpected token ')' in expression or statement.
At line:1 char:593
+ ... inlgestatus})).status.count / ($packinfo.Count -4)).tostring("P") } }
+                                                                         ~
Unexpected token '}' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParseException

 

@farismalaeb 

Tried it again and Thanks Faris. This works.

 

How can I limit this to only a specific Deployment ID? 

How could I run this against my list of Device Collection Names perhaps from a txt file? 

 

 

@NAN2019 

For the first question you can add a parameter to your script SinlgeDeploymentID, and check if the value is present the parse this value, otherwise do it all :)

I did this

NOTE: I try to make the script as simple as possible to make it easier for others too.

Add this parameter on the top of your script

[CmdletBinding()]
param(
$GetonlyOne
)

in the script body add

if ($GetonlyOne -notlike $null){
Get-SCCMSoftwareUpdateStatus -DeploymentID $GetonlyOne
return
}

For the second one, its just like the first one but change the filter to match the collection Name you want

 

 

 

--------------------------------------

Thanks

 

If this answer fit your question, please mark this as Best Response and give Like :)

@farismalaeb 
thanks. 

Where does my specific Assignment ID number go or how should it fit within your script above?

 
Can you please show me an example of one? 

Also how would my List of device collection Names fit into your script if they are being pulled from a text file? 

@NAN2019 

I did another small update to provide a better output for a single Assignment ID

if ($GetonlyOne -notlike $null){

 Write-Host $SinglePack.AssignmentName
    $packinfo=Get-SCCMSoftwareUpdateStatus -DeploymentID $GetonlyOne
        $Status=$packinfo.status | Get-Unique
        foreach ($sinlgestatus in $Status){
        Write-Host "The Status of $($sinlgestatus) for $($GetonlyOne)" 
            ((($packinfo | where {$_.status -like $sinlgestatus})).status.count / ($packinfo.Count -4)).tostring("P")
        

            }
return
}

$Packages= Get-AllPackages

foreach ($SinglePack in $Packages) {
Write-Host "--------------"
    Write-Host $SinglePack.AssignmentName
    $packinfo=Get-SCCMSoftwareUpdateStatus -DeploymentID $SinglePack.AssignmentID 
        $Status=$packinfo.status | Get-Unique
        foreach ($sinlgestatus in $Status){
        Write-Host "The Status of $($sinlgestatus) for $($SinglePack.AssignmentName)" 
         ((($packinfo | where {$_.status -like $sinlgestatus})).status.count / ($packinfo.Count -4)).tostring("P")
        

            }
} 

 The call should be like this

.\ScriptName -GetonlyOne 16777338

and the output should be similar to this

WindowsServer 2016_2_2020-10-15 17:03:49
The Status of InProgress for 16777338
9.09 %
The Status of Success for 16777338
90.91 %

 

For the second one, I really did not understand how you want to fit the Collection Name here. can you explain a requirement to understand it.

 

@farismalaeb 

Thanks Faris. For some reason your Original script is still pulling all deployments. Still not limiting it to one deployment. I Have the new code in another Powershell file. I run your above command with ID and it still pulls all assignments. 

Any ideas? 

For Device collection what I would like to do is pull the compliance status for one Assignment ID but for a list of device collections Inside a text file. So basically if I can take that Assignment ID and have it check all devices (Likely from SCCM’s database) and then show a percent. For example So again: 

 

Chicago Devices.... Success 80% Error 20% 

Los Angeles Devices ... Success 15% Error 85% 

Denver Devices .... In Progress 50% Error 50% 

Cleveland Devices And so on... 

@NAN2019 

If you are calling the script without passing the

-GetonlyOne

parameter with its value (which you can get from the following line)

Get-CMSoftwareUpdateDeployment | select AssignmentID, AssignmentName | Out-GridView -OutputMode Single -Title "Select a Deployment and Click OK" | Select -ExpandProperty AssignmentID

then it will get all the software deployment package with their overall status, depend on what status is available in the Deploymentpackage state.
Can you please share the screenshot of the output you are getting