Overview

This script will allow you to take a standard SCCM SQL Report and output the report, automatically, to either a .CSV file or to an array for use in a pipeline.

Workflow

This standard SCCM SQL report can be run which would allow you to see all of the software updates that are required but not deployed via WSUS.

Software Updates – Updates required but not deployed

Simply running the report gives you an indication of what variables are required to run this report. When fed into this function, the report will run automatically with these values chosen and the results can be either output to the pipeline as an array or to a .CSV file for easy emailing.


Import-Module \\scriptserver\scripts\DMGSCCM\Get-DMGSCCMSQLReport\Get-DMGSCCMSQLReport.psm1 -Force


#Set Universal Parameters for this Report
$ReportServerUrl="http://sccmsqlrserver/ReportServer"
$ReportPath="/ConfigMgr_DGM/Software Updates - B Deployment Management/Management 2 - Updates required but not deployed" #Include your report path here - this is a sample only

#Create Array Of Data To Display in Report.
$OutputArrays = @()
$ProviderMachineName = "sccmsqlrserver.corp.corporation.com" #enter your sccm sql server here
$Sitecode = "DGM" #enter your site code here

Set-Location $Sitecode":"

#Array1
$inputParams = @{
    "CollID"="DGM00084"; #These are sample values
    "UpdateClass"="Security Updates";
    "Vendor"="Microsoft";
}

$array = Get-DMGSCCMSQLReport -inputParams $inputParams `
                               -ReportServerUrl $ReportServerUrl `
                               -ReportPath $ReportPath `
                               -ProviderMachineName $ProviderMachineName `
                               -Sitecode $Sitecode
Set-Location $Sitecode":"  

#Generate Array with All Production Servers with Maintenance Window (Security Updates): Required But Not Deployed                                        
$arrayresult = $array | %{Get-CMSoftwareUpdate -ArticleId $_.Details_Table0_Title -Fast| ?{$_.nummissing -ge 1 -and $_.IsExpired -eq $FALSE -and $_.isSuperseded -eq $FALSE -and $_.LocalizedDisplayName -notlike "*Security Only*"}} | `
                               Select ArticleID,LocalizedDisplayName,NumMissing,NumPresent,IsSuperseded,IsExpired -Unique | Sort-Object -Descending -Property NumMissing

As you can see in the example, we take the results from a standard SQL report, transform them into an array as $array, and simultaneously pipe them to Get-CMSoftwareUpdate for further processing as $arrayresult.

Logging Results

This function has standard capabilities to output its status a log file which is fully compatible with CMTrace.exe

PowerShell Function


<#
.SYNOPSIS
  Generates an array of a SCCM SQL Report
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-11
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------
 function Get-DMGSCCMSQLReport
 {
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        $inputParams,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        $ReportServerUrl,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
        $ReportPath,
        [Parameter(Position=3,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$ProviderMachineName,
        [Parameter(Position=4,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$Sitecode
    )


    #Set Logging Varibales
    $invocation = (Get-Variable MyInvocation -Scope 1).Value
    $ScriptDirectory = Split-Path $invocation.MyCommand.Path
    $ScriptName = ($MyInvocation.MyCommand.Name)+".psm1"
    $LogName = ($MyInvocation.MyCommand.Name)+".log"
    $LogFile = Join-Path $ScriptDirectory $LogName
    $ScriptFile = Join-Path $ScriptDirectory $ScriptName
    $ReportDate = Get-Date 

    #Set CSV Output Variables
    $CSVOutputName = ($MyInvocation.MyCommand.Name)+".csv"
    $CSVOutputFile = Join-Path $ScriptDirectory $CSVOutputName

    #Log Start of Function
    New-DMGCMTraceLog -message ("Starting Logging for $ScriptName") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

    #Connect to SCCM
    # Import the ConfigurationManager.psd1 module
    $module = "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    if((Get-Module ConfigurationManager) -eq $null) {
        Write-Host Importing $module ...
        Import-Module $module -Force
    }

    # Connect to the site's drive if it is not already present
    if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
        New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName @initParams
    }

    # Set the current location to be the site code.
    Set-Location "$($SiteCode):\"


    # add assembly 
    Add-Type -AssemblyName "Microsoft.ReportViewer.WinForms, Version=12.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91"

    # if the path exists, will error silently and continue 
    New-Item -ItemType Directory -Path $baseFolder -ErrorAction SilentlyContinue | Out-Null

    $rv = New-Object Microsoft.Reporting.WinForms.ReportViewer

    # report Server Properties 
    $rv.ServerReport.ReportServerUrl = $ReportServerUrl
    $rv.ServerReport.ReportPath = $ReportPath
    $rv.ProcessingMode = "Remote"

    # set up report parameters 
    $params = $null

    #create an array based on how many incoming parameters 
    $params = New-Object 'Microsoft.Reporting.WinForms.ReportParameter[]' $inputParams.Count

    $i = 0 
    foreach ($p in $inputParams.GetEnumerator()) 
    { 
        $params[$i] = New-Object Microsoft.Reporting.WinForms.ReportParameter($p.Name, $p.Value, $true) 
        $i++ 
    } 

    # set the parameters 
     Write-Host "Setting Parameters..."
    $rv.ServerReport.SetParameters($params) 
    $rv.ShowParameterPrompts = $false 
    $rv.RefreshReport() 
    $rv.ServerReport.Refresh()

    Write-Host "The Parameters Were Applied..."

    # set rendering parameters 
    $mimeType = $null 
    $encoding = $null 
    $extension = $null 
    $streamids = $null 
    $warnings = $null

    # render the SSRS report in CSV 
    $bytes = $null 
    $bytes = $rv.ServerReport.Render("CSV", 
    $null, 
    [ref] $mimeType, 
    [ref] $encoding, 
    [ref] $extension, 
    [ref] $streamids, 
    [ref] $warnings)

    Set-Location C:\

    # save the report to a file
    $fileStream = New-Object System.IO.FileStream($CSVOutputFile, [System.IO.FileMode]::OpenOrCreate) 
    $fileStream.Write($bytes, 0, $bytes.Length) 
    $fileStream.Close()

    New-DMGCMTraceLog -message ("File Exported`: $CSVOutputFile") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

    # Re-import file and remove first three lines

    get-content -LiteralPath $CSVOutputFile|
        select -Skip 3 |
        set-content "$CSVOutputFile-temp"
    move "$CSVOutputFile-temp" $CSVOutputFile -Force
    #Log Sent Email
    New-DMGCMTraceLog -message ("File Imported (First 3 Lines Removed)`: $CSVOutputFile") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

    $Finalvalues = Import-CSV -LiteralPath $CSVOutputFile

    #Log End Of Function
    New-DMGCMTraceLog -message ("End Logging for $ScriptName") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

    return $Finalvalues

}

Overview

At times it is important to determine if a client machine has a certain certificate installed from a certificate template. I developed this script, Get-DMGCertificateTemplateExistance, to to detect if a certificate was created from a particular template name. It could be run stand-alone or is also deployable as an SCCM configuration item/baseline and will return the desired results in a true / false fashion.

Instructions

Add the namme of the certificate template you would like to check within the Invoke-DMGCertificateTemplateExistance function and then run the PowerShell script. If the computer you run the script on has a certificate that was created from the template you provided, the script will return true, and it will return false otherwise.


function Invoke-DMGCertificateTemplateExistance{
    $CertificateName = 'Display Name of Certificate Template Certificate Was Created From'
    Get-DMGCertificateTemplateExistance -CertificateName $CertificateName
}

Detecting Certificate Template Compliance in SCCM

Deploy this to your required machines as a configuration baseline compliance item. Deployed to this sample machine, we have configured the function to look for a Cisco ISE certificate. Here we can see the machine reports compliance.

cid:image003.png@01D39C74.153A0660

PowerShell Script: Get-DMGCertificateTemplateExistance.ps1


<#

.SYNOPSIS
  Determines if a certificate exists on the local machine that matches the template name
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-02-02
  Purpose/Change: Initial script development

#>

function Get-DMGCertificateTemplateExistance{
    [CmdletBinding()]
    param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    [String]$CertificateName
    )

    #Set Certificate Template Existance Count
    $i = 0
    #Get All of the local machine certificates
    $cert = $null
    $certs = $null
    $certs = get-childitem cert:\localmachine\my

    #Loop through each certificate
    foreach ($cert in $certs){ 
        $temp = $null

        #See if certificate associated with Microsoft Chryptogrophy:  szOID_ENROLL_CERTTYPE_EXTENSION
        $temp = $cert.Extensions | Where-Object{$_.Oid.Value -eq "1.3.6.1.4.1.311.20.2"}
        if(!$temp){
            #Else see if certificate associated with Microsoft CertSrv Infrastructure: Certificate template extension (v2) szOID_CERTIFICATE_TEMPLATE
            $temp = $cert.Extensions | Where-Object{$_.Oid.Value -eq "1.3.6.1.4.1.311.21.7"}
         }
         
         #Create a New Value, Template, and see if it mateches the template name we are looking for
         if($temp){
            $cert | Add-Member -Name Template -MemberType NoteProperty -Value $temp.Format(1)
        
            #If the template name is found, incrememnt the Certificate Template Existance Count
            if ($cert.template.contains($CertificateName)){
                $i++
                break;
            }
        }
    }

    #If the Certificate Template Existance Count is greater than one, we found a certificate with our template
    if ($i -gt 0){return $true; break}else{return $false}
}

function Invoke-DMGCertificateTemplateExistance{
    $CertificateName = 'Display Name of Certificate Template Certificate Was Created From'
    Get-DMGCertificateTemplateExistance -CertificateName $CertificateName
}

Invoke-DMGCertificateTemplateExistance

Overview

At times, SCCM client machines may lose the ability to communicate properly with the SCCM site server due to Kerberos authentication errors. You will see these errors in Deployment Statuses or other times invoking PowerShell scripts on remote clients.

This script attempts to invoke a generic script remotely on an imported set of SCCM clients and determine if there is a Kerberos authentication error:

 Invoke-Command -ComputerName $h -ScriptBlock { Test-Connection SCCMSERVER -Quiet -Count 1 } -ErrorAction Stop 

If the invocation returns the exception error System.Management.Automation.Remoting.PSRemotingTransportException you can assume there is an error and we can remotely instruct a CLI through WinRM:

 $command = "C:\ADMIN`\PSTools\PsExec.exe \\$h /s /accepteula /nobanner cmd /c `"Winrm quickconfig /q`"" 

Therefor it is a prerequisite that you have PsExec.exe in the C:\ADMIN\PSTools\ directory of the PC before running the script, as shown above.

The script will also try to resolve DNS issues that may be related to the error.

Fixing Kerberos Errors on an Imported List of Computers

You can import a generic list of computers via a CSV that is in the following format:

 Fix-DGMKerberosError –CSV \\Pathto\the.csv 

Logging Output

The function outputs its progress to a log file and is fully compatible with CMTrace.exe.

PowerShell Function

 
<#
.Synopsis
   Fix SCCM Kerberos Errors
.DESCRIPTION
   The tool, Fix-DMGKerberosError.ps1 was written by David Maiolo which will attempt to fix Kerberos Authentican Issues on remote computers.
.EXAMPLE
   Fix-DMGKerberosError -CSVFile kerberos_error_import.csv
.EXAMPLE
   Fix-DMGKerberosError -Hostname LT061222
#>


function LogIt
{
  param (
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Global:Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($Global:ScriptStatus -ne 'Error')) { $Global:ScriptStatus = $type }
  if ($type -eq 'Error') { $Global:ScriptStatus = $type }

  if ((Get-Item $Global:LogFile).Length/1KB -gt $Global:MaxLogSizeInKB)
  {
    $log = $Global:LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $Global:LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

function GetScriptDirectory
{
  $invocation = (Get-Variable MyInvocation -Scope 1).Value
  Split-Path $invocation.MyCommand.Path
} 

function Fix-DMGKerberosError
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 1')]
                   [ValidateScript({(Test-Path $_)})]
                   $CSVFile,
        # Param2 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 2')]
                   [ValidateScript({(Get-ADComputer -Identity $_).objectclass -eq 'computer' })]
                   [String]$Hostname
    )

    Begin
    {
        $path = (get-item -Path .).FullName 
        
        if ($CSVFile -ne $null){
            Write-Host Importing $CSVFile...
            $csv = import-csv "$CSVFile"
        }else{
            $csv = [PSCustomObject]@{
                Hostname = $Hostname}
        }
        Write-Host ==========================================
        Write-Host SCCM Kerberos Remote Execution Fix Tool
        Write-Host ==========================================
        Write-Host "v0.1 (2017-12-20) by dmaiolo"
        LogIt -message ("Starting Logging for Fix-DMGKerberosError") -component "Main()" -type 1 
    }
    Process
    {
    

    $computers = @();
        
    $csv | foreach-object {
        $h = $_.Hostname
    

        if(Test-Connection -ComputerName $h -Count 1 -Quiet){

            Write-Host "$h`: Invoking Generic Command (Test-Connection SCCMSERVER -Quiet -Count 1)..."
            Try{
                Invoke-Command -ComputerName $h -ScriptBlock { Test-Connection SCCMSERVER -Quiet -Count 1 } -ErrorAction Stop
                LogIt -message ("$h`: Generic Command (Test-Connection SCCMSERVER -Quiet -Count 1) Ran Succesfully. No Fix Required.") -component "Main()" -type 1
            }
            Catch [System.Management.Automation.Remoting.PSRemotingTransportException]{
                LogIt -message ("$h`: Kerberos Issue Detected. Fix Required") -component "Main()" -type 2
                Write-Host "$h`: Attempting to Repair WinRM for Kerberos Authentication..."
                $command = "C:\ADMIN`\PSTools\PsExec.exe \\$h /s /accepteula /nobanner cmd /c `"Winrm quickconfig /q`""
                if($expression = Invoke-Expression -Command:"$command" -ErrorAction Stop) {
                    LogIt -message ("$h`: WinRM Repair Ran Succesfully for Kerberos Authentication") -component "Main()" -type 1
                    }
                else{
                    LogIt -message ("$h`: WinRM Repair NOT Succesfull for Kerberos Authentication") -component "Main()" -type 3
                    LogIt -message ("$h`: Attempting DNS Repair by running PsExec for ipconfig /registerdns against the IP Address... ") -component "Main()" -type 1
                    try{
                        $IPv4AddressPinged = (Test-Connection -ComputerName $h -Count 1 -ErrorAction SilentlyContinue).IPV4Address.IPAddressToString
                        LogIt -message ("$h`: Found a valid IP Address To Invoke-Expression against ($IPv4AddressPinged).") -component "Main()" -type 1
                        $RealHostName = (Resolve-DnsName $IPv4AddressPinged).NameHost
                        LogIt -message ("$h`: Your DNS has accociated $h to $IPv4AddressPinged, but it acually belongs to $RealHostName. This is part of your problem.") -component "Main()" -type 1
                        
                    }
                    catch{
                        LogIt -message ("$h`: No Valid IP Address for this host could be determined.") -component "Main()" -type 3
                        $RealHostName = $false
                        $IPv4AddressPinged = $false
                    }
                    if ($IPv4AddressPinged){
                        $command2 = "C:\ADMIN\PSTools\PsExec.exe \\$IPv4AddressPinged /s /accepteula /nobanner cmd /c `"ipconfig /registerdns`""
                        if(Invoke-Expression -Command:"$command2"){
                            LogIt -message ("$h`: ($RealHostName) Succesfully Ran $command2.") -component "Main()" -type 1
                            Write-Host "$h`: ($RealHostName) Attempting to Repair WinRM for Kerberos Authentication against IP Address ($IPv4AddressPinged)..."
                            $command3 = "C:\ADMIN`\PSTools\PsExec.exe \\$IPv4AddressPinged /s /accepteula /nobanner cmd /c `"Winrm quickconfig /q`""
                            if($expression2 = Invoke-Expression -Command:"$command3" -ErrorAction Stop) {
                                LogIt -message ("$h`: ($RealHostName) WinRM Repair Ran Succesfully for Kerberos Authentication against IP Address ($IPv4AddressPinged)") -component "Main()" -type 1
                            }
                            else{
                                LogIt -message ("$h`: ($RealHostName) WinRM Repair NOT Succesfull for Kerberos Authentication against IP Address ($IPv4AddressPinged)") -component "Main()" -type 3
                               }
                        }
                        else{
                            LogIt -message ("$h`: ($RealHostName) Unable to Run $command2.") -component "Main()" -type 3
                        }
                    }
                 }
            }
            Catch{
                LogIt -message ("$h`: Generic Command (Test-Connection SCCMSERVER -Quiet -Count 1) Ran Into a Non-Kerberos Issue. Fix Unknown.") -component "Main()" -type 3
            }

        }
        else{
            LogIt -message ("$h`: is offline.") -component "Main()" -type 2
        }
       }
    }
    End
    {
       Write-Host ===============================================================
       Write-Host Log File of Results Generated at $Global:LogFile VIEW WITH CMTRACE.EXE
       LogIt -message ("Ending Logging for Fix-DMGKerberosError") -component "Main()" -type 1
    }
}

$VerboseLogging = "true"
[bool]$Global:Verbose = [System.Convert]::ToBoolean($VerboseLogging)
#$Global:LogFile = Join-Path (GetScriptDirectory) "Fix-DMGKerberosError_$(Get-Date -Format dd-MM-yyyy).log"
$Global:LogFile = Join-Path (GetScriptDirectory) "Fix-DMGKerberosError.log"
$Global:MaxLogSizeInKB = 10240
$Global:ScriptName = 'Fix-DMGKerberosError.ps1' 
$Global:ScriptStatus = 'Success'

Overview

The Local Administrator Password Solution is a great tool to help you manage local passwords on your corporate network. However, due to the non-persistant nature of some environments, such as Citrix VDI, it can become a security vulnerability as the local password will always be set back the original password set when the VDI was persistent.

I created this script, Reset-LAPSPasswordExpiration.ps1, which aims to solve this problem by randomly generating a password upon either VDI startup or shutdown and synchronizing those results with the LAPS password store in Active Directory. This requires RSAT to be on the Citrix server because it uses Set-ADComputer.

How To Use This Script

The script can either be run remotely or on the Citrix machine. I recommend using the local start-up procedure as it is most effective. However, I did integrate other abilities if you wish to you modify its deployment in your environment. This script requires the proper version of RSAT be installed on the Citrix machine.

If run it locally on the Citrix server at startup (recommended way):


  Reset-LAPSPasswordExpiration -RunOnLocalHost -logic Startup 

If run local on the Citrix server at shutdown:


  Reset-LAPSPasswordExpiration -RunOnLocalHost -logic Shutdown 

To run against a batch of Citrix servers remotely:


  Reset-LAPSPasswordExpiration -CSVFile Reset-LAPSPasswordExpiration-Import.csv -Logic Shutdown 

To run against a single server remotely:


  Reset-LAPSPasswordExpiration -Hostname CITRIXSERVER001 -Logic Shutdown 

Sample Output:

Logging Output

The script has built in logging functionality which is fully compatible with CMTrace.exe

PowerShell Function: Reset-LAPSPasswordExpiration


<#
.Synopsis
   Reset the LAPS Password Expiration Date
.DESCRIPTION
   The tool, Reset-LAPSPasswordExpiration.ps1 was written by David Maiolo which will reset the LAPS Password expiration date. Useful on non-persistent VDIs
.EXAMPLE
   Reset-LAPSPasswordExpiration -CSVFile laps_computers_import.csv -Logic Startup
.EXAMPLE
   Reset-LAPSPasswordExpiration -Hostname LT061222 -Logic Shutdown
.EXAMPLE
   Reset-LAPSPasswordExpiration -RunOnLocalHost -logic Startup
#>


function New-DGMCMTraceLog
{
  param (
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Global:Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($Global:ScriptStatus -ne 'Error')) { $Global:ScriptStatus = $type }
  if ($type -eq 'Error') { $Global:ScriptStatus = $type }

  if ((Get-Item $Global:LogFile).Length/1KB -gt $Global:MaxLogSizeInKB)
  {
    $log = $Global:LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $Global:LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

function GetScriptDirectory
{
  $invocation = (Get-Variable MyInvocation -Scope 1).Value
  Split-Path $invocation.MyCommand.Path
} 

function Reset-LAPSPasswordExpiration
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='CSV File')]
                   [ValidateScript({(Test-Path $_)})]
                   $CSVFile,
        # Param2 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=1,
                   ParameterSetName='Single Computer')]
                   [ValidateScript({(Get-ADComputer -Identity $_).objectclass -eq 'computer' })]
                   [String]$Hostname,
        # Param3 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=2,
                   ParameterSetName='Local Host')]
                   [Switch]$RunOnLocalHost,
        # Param4 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=3)]
                   [ValidateSet("Shutdown","Startup")]
                   [String]$Logic
    )

    Begin
    {
        
        $path = (get-item -Path .).FullName 
        
        if ($RunOnLocalHost){
            $localhostname = $env:computername
            $csv = [PSCustomObject]@{
                Hostname = $localhostname}
        }
        elseif ($CSVFile -ne $null){
            Write-Host "Importing $CSVFile..."
            $csv = import-csv "$CSVFile"
        }else{
            $csv = [PSCustomObject]@{
                Hostname = $Hostname}
        }
        Write-Host "=========================================="
        Write-Host "LAPS Password Expiration Date Reset Tool"
        Write-Host "=========================================="
        Write-Host "v0.5 (2017-12-26) by dmaiolo"
        New-DGMCMTraceLog -message ("Starting Logging for Reset-LAPSPasswordExpiration") -component "Main()" -type 1 
    }
    Process
    {
    
    $computers = @();
        
    $csv | foreach-object {
        $h = $_.Hostname
    

        if(Test-Connection -ComputerName $h -Count 1 -Quiet){
            
            $comp = Get-ADComputer $h -Properties ms-MCS-AdmPwdExpirationTime
            
            try{
                $currentexpirationtime = $([datetime]::FromFileTime([convert]::ToInt64($comp.'ms-MCS-AdmPwdExpirationTime',10)))
                New-DGMCMTraceLog -message ("$h`: The current LAPS password expiration is $currentexpirationtime.") -component "Main()" -type 1
            }catch{
                New-DGMCMTraceLog -message ("$h`: The current LAPS password has an unknown expiration or is already clear.") -component "Main()" -type 2
            }

            Write-Host "$h`: Resetting ms-MCS-AdmPwdExpirationTime..."
            try{
                Set-ADComputer $h -Clear "ms-MCS-AdmPwdExpirationTime"
                New-DGMCMTraceLog -message ("$h`: ms-MCS-AdmPwdExpirationTime has been cleared succesfully.") -component "Main()" -type 1
            }catch{
                New-DGMCMTraceLog -message ("$h`: ms-MCS-AdmPwdExpirationTime could not be cleared.") -component "Main()" -type 3
            }
            if ($Logic -eq "Startup"){
                New-DGMCMTraceLog -message ("$h`: Startup Sequence Logic Was Initiated.") -component "Main()" -type 1
                Write-Host "$h`: Sleeping 3 seconds..."
                sleep 3

                Write-Host "$h`: Running GPUpdate /Force on $h ..."
                if ($RunOnLocalHost){
                    try{
                        $command = "GPUPdate /Target:Computer /Force"
                        Invoke-Expression -Command:"$command"
                        New-DGMCMTraceLog -message ("$h`: Group Policy Was Succesfully Updated.") -component "Main()" -type 1
                    }catch{
                        New-DGMCMTraceLog -message ("$h`: Group Policy Could Not Update.") -component "Main()" -type 3
                    }
                }else{
                    try{
                        Invoke-GPUpdate -Computer $h -Force
                        New-DGMCMTraceLog -message ("$h`: Group Policy Was Succesfully Updated.") -component "Main()" -type 1
                    }catch{
                        New-DGMCMTraceLog -message ("$h`: Group Policy Could Not Update.") -component "Main()" -type 3
                    }
                }
            }elseif ($Logic -eq "Shutdown"){
                New-DGMCMTraceLog -message ("$h`: Shutdown Sequence Logic Was Initiated. Skipping GPUpdate") -component "Main()" -type 1
            }
            Write-Host "$h`: Sleeping 5 Seconds..."
            sleep 5
            try{
                $currentexpirationtime = $([datetime]::FromFileTime([convert]::ToInt64($comp.'ms-MCS-AdmPwdExpirationTime',10)))
                New-DGMCMTraceLog -message ("$h`: The updated LAPS password expiration is $currentexpirationtime.") -component "Main()" -type 1
            }catch{
                New-DGMCMTraceLog -message ("$h`: The updated LAPS password has an unknown expiration.") -component "Main()" -type 3
            }
        }
        else{
            New-DGMCMTraceLog -message ("$h`: is offline.") -component "Main()" -type 2
        }
       }
    }
    End
    {
       Write-Host "==============================================================="
       Write-Host "Log File of Results Generated at $Global:LogFile VIEW WITH CMTRACE.EXE"
       New-DGMCMTraceLog -message ("Ending Logging for Reset-LAPSPasswordExpiration") -component "Main()" -type 1
    }
}

$VerboseLogging = "true"
[bool]$Global:Verbose = [System.Convert]::ToBoolean($VerboseLogging)
#$Global:LogFile = Join-Path (GetScriptDirectory) "Reset-LAPSPasswordExpiration_$(Get-Date -Format dd-MM-yyyy).log"
$Global:LogFile = "\\vendscr001prd\Scripts\Reset-LAPSPasswordExpiration\Reset-LAPSPasswordExpiration.log"
$Global:MaxLogSizeInKB = 10240
$Global:ScriptName = 'Reset-LAPSPasswordExpiration.ps1' 
$Global:ScriptStatus = 'Success'

Overview

I developed these PowerShell scripts to generate a daily email report of open tickets within the Software Desk Express software database. The scripts get the content via SQL queries against your SDE SQL database and are piped into my email reporting functions.

At the very bottom of the email a section which tallies up the selected and the other teams total open tickets and sorts them by which team has the most open. To make it a little more friendly to read, I created an array of the “SDE Group” name and the “Team” name which I somewhat made a few guesses on. If the team name is wrong somewhere let me know and I can easily change it.

Basic Functionality

Although the report may look complicated, at its core, each function is simply generating an array of data based off various SQL queries to the database.

There are two types of reports we can invoke. Either one for a specific team (where an example is attached below):


Import-Module "\\scriptserver\Scripts\Invoke-DMGSDEReport\Invoke-DMGSDEReport.psm1" -Force
#Generate The Team Report
$emailaddresses = @("ateam@emailaddress.com")
Invoke-DMGSDEReport -GroupName SDEGROUP2 -GroupDescription "Server Inf. Team" -emailaddresses $emailaddresses

Or a report that combines the functions in a useful way to provide a summary report that would be useful to management:


Import-Module "\\scriptserver\Scripts\Invoke-DMGSDEReport\Invoke-DMGSDEReport.psm1" -Force
#Generate The Manager Report
$emailaddresses = @("managers@emailaddress.com")
Invoke-DMGSDEReport -ManagerReport MANAGERGROUP1 -emailaddresses $emailaddresses

The arrays are combined into a single array of arrays and then passed onto my HTML E-Mail function which is discussed elsewhere on this site:


New-DMGEmailReport `
            -Arrays $OutputArrays `
            -ReportTitle "$GroupDescription Open Ticket Report" `
            -from "SDEReports@emailadress.com" `
            -To $emailaddresses `
            -subject "$GroupDescription SDE Open Ticket Report"

Sample Report

PowerShell Invocation: Invoke-DGMSDEServerReport.ps1


Import-Module "\\SCRIPTSERVER\scripts\New-DMGEmailReport\New-DMGEmailReport.psm1" -Force

Import-Module "\\SCRIPTSERVER\Scripts\Invoke-DMGSDEReport\New-DMGSDEOpenIncidents.psm1" -Force
Import-Module "\\SCRIPTSERVER\Scripts\Invoke-DMGSDEReport\New-DMGSDEOpenWorkOrders.psm1" -Force
Import-Module "\\SCRIPTSERVER\Scripts\Invoke-DMGSDEReport\New-DMGSDETeamsOpenHistory.psm1" -Force
Import-Module "\\SCRIPTSERVER\Scripts\Invoke-DMGSDEReport\New-DMGSDETeamTicketHistory2.psm1" -Force
Import-Module "\\SCRIPTSERVER\Scripts\Invoke-DMGSDEReport\New-DMGSDETeamUserHistory.psm1" -Force
Import-Module "\\SCRIPTSERVER\Scripts\Invoke-DMGSDEReport\New-DMGSDEAreasOfConcern.psm1" -Force

<#
.SYNOPSIS
  Generates an SDE Email Report
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-22
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

function Invoke-DMGSDEReport{
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='TeamReport')]
        [ValidateSet("SDEGROUP1","SDEGROUP2","SDEGROUP3","SDEGROUP4","SDEGROUP5","SDEGROUP6","SDEGROUP7","SDEGROUP8","SDEGROUP9","SDEGROUP10")]
        [String]$GroupName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='TeamReport')]
        [String]$GroupDescription,
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='TeamReport')]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        [String]$Color,
        [ValidateSet("ManagerGroup1","ManagerGroup2","ManagerGroup3","ManagerGroup4","ManagerGroup5","ManagerGroup6")]
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='ManagerReport')]
        [String]$ManagerReport,
        [Parameter(Position=3,Mandatory=$true,ValueFromPipeline=$true)]
        $emailaddresses
    )
    
    $OutPutArrays = @()

    if([bool]($MyInvocation.BoundParameters.Keys -contains 'ManagerReport')){

        #A Manager Report

        $colors = @("Lime","Blue","Cyan","Magenta","Gray","Maroon","Olive","Purple","Teal","Navy")
        $teams = @()

         switch ($ManagerReport) {
           "ManagerGroup1" {
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP1'; 'Description' = 'IT Governance'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP2'; 'Description' = 'DS User Request'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP3'; 'Description' = 'Directory Services Administrators'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP4'; 'Description' = 'Move / Add / Change'};
           }
           "ManagerGroup2" {
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP1'; 'Description' = 'Tech-Ops Channels'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP2'; 'Description' = 'Tech-Ops Core & IPS'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP3'; 'Description' = 'Tech-Ops Lending Credit'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP4'; 'Description' = 'Tech-IT Platform Managers'};
           }
           "ManagerGroup3" {
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP1'; 'Description' = 'Database Administrators'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP2'; 'Description' = 'Data Warehouse'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP3'; 'Description' = 'EDW Administration'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP4'; 'Description' = 'IBM Administration'};
           }
           "ManagerGroup4" {
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP1'; 'Description' = 'Enterprise App'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP2'; 'Description' = 'Enterprise Development'};
           }
           "ManagerGroup5" {
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP1'; 'Description' = 'Network Infr. Team'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP2'; 'Description' = 'Server Infrastructure Team'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP3'; 'Description' = 'Endpoint Admins Team'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP4'; 'Description' = 'Technology Help Desk'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP5'; 'Description' = 'Telecom Team'}
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP6'; 'Description' = 'Network - General'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP7'; 'Description' = 'Network - Imaging'};
           }
           "ManagerGroup6" {
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP1'; 'Description' = 'Branch Help Desk'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP2'; 'Description' = 'Corporate Security'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP3'; 'Description' = 'Information Security'};
            $teams += [PSCustomObject] @{'Group' = 'SDEGROUP4'; 'Description' = 'Xerox'};
           }
           default {}
        }

        $OutputArrays+= New-DMGSDETeamsOpenHistory -ShowAllTeams -Color "Blue"

        foreach ($team in $teams){
            #Create a New Random Color For Each Team
            $LastColor = $Color
            do{$Color = $Colors[(Get-Random -Maximum ([array]$Colors).count)]} while ($Color -eq $LastColor)

            #The reports for each team of the manager report
            $OutputArrays+= New-DMGSDETeamUserHistory -GroupName $team.Group -GroupDescription $team.Description -Color $Color
            $OutputArrays+= New-DMGSDEAreasOfConcern -GroupName $team.Group -GroupDescription $team.Description -MaximumNumberOfRowsToDisplay 5 -DaysUntilConcern 45 -Color $Color
            $OutputArrays+= New-DMGSDETeamTicketHistory2 -GroupName $team.Group -GroupDescription $team.Description -NumOfMonthsToShowTrends 8 -Color $Color
        }

        #Email Manager Report
        New-DMGEmailReport `
            -Arrays $OutputArrays `
            -ReportTitle "SDE Manager Report`: $ManagerReport" `
            -from "SDEReports@DMGhawaii.com" `
            -To $emailaddresses `
            -subject "SDE Manager Report`: $ManagerReport"
    }
    else{
        #A Team Report

        if([bool]($MyInvocation.BoundParameters.Keys -contains 'Color')){
            $DMGSDEAreasOfConcern = New-DMGSDEAreasOfConcern -GroupName $GroupName -GroupDescription $GroupDescription -MaximumNumberOfRowsToDisplay 5 -DaysUntilConcern 60 -Color $Color
            $DMGSDEOpenIncidents = New-DMGSDEOpenIncidents -GroupName $GroupName -GroupDescription $GroupDescription -MaximumNumberOfRowsToDisplay 50 -Color $Color
            $DMGSDEOpenWorkOrders = New-DMGSDEOpenWorkOrders -GroupName $GroupName -GroupDescription $GroupDescription -MaximumNumberOfRowsToDisplay 50 -Color $Color
            $DMGSDETeamsOpenHistory = New-DMGSDETeamsOpenHistory -ShowAllTeams -Color $Color
            $DMGSDETeamTicketHistory2 = New-DMGSDETeamTicketHistory2 -GroupName $GroupName -GroupDescription $GroupDescription -NumOfMonthsToShowTrends 12 -Color $Color
            $DMGSDETeamUserHistory = New-DMGSDETeamUserHistory -GroupName $GroupName -GroupDescription $GroupDescription -Color $Color
        }
        else{
            $DMGSDEAreasOfConcern = New-DMGSDEAreasOfConcern -GroupName $GroupName -GroupDescription $GroupDescription -MaximumNumberOfRowsToDisplay 5 -DaysUntilConcern 60
            $DMGSDEOpenIncidents = New-DMGSDEOpenIncidents -GroupName $GroupName -GroupDescription $GroupDescription -MaximumNumberOfRowsToDisplay 50
            $DMGSDEOpenWorkOrders = New-DMGSDEOpenWorkOrders -GroupName $GroupName -GroupDescription $GroupDescription -MaximumNumberOfRowsToDisplay 50
            $DMGSDETeamsOpenHistory = New-DMGSDETeamsOpenHistory -ShowAllTeams
            $DMGSDETeamTicketHistory2 = New-DMGSDETeamTicketHistory2 -GroupName $GroupName -GroupDescription $GroupDescription -NumOfMonthsToShowTrends 12
            $DMGSDETeamUserHistory = New-DMGSDETeamUserHistory -GroupName $GroupName -GroupDescription $GroupDescription
        }

        $OutputArrays+=$DMGSDEAreasOfConcern
        $OutputArrays+=$DMGSDEOpenIncidents
        $OutputArrays+=$DMGSDEOpenWorkOrders
        $OutputArrays+=$DMGSDETeamsOpenHistory
        $OutputArrays+=$DMGSDETeamTicketHistory2
        $OutputArrays+=$DMGSDETeamUserHistory


        if (($DMGSDEOpenIncidents -ne $null) -or ($DMGSDEOpenWorkOrders -ne $null)){
        #Email Multiple Arrays
        New-DMGEmailReport `
            -Arrays $OutputArrays `
            -ReportTitle "$GroupDescription Open Ticket Report" `
            -from "SDEReports@emailadress.com" `
            -To $emailaddresses `
            -subject "$GroupDescription SDE Open Ticket Report"
        }else{
            Write-Host "There were no open incidents or work orders, so the email was not sent."
        }
   }

    
}

PowerShell Module: New-DGMSDEAreasOfConcern.psm1


<#
.SYNOPSIS
  Generates New-DMGSDEOpenIncidents
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-22
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

function New-DMGSDEAreasOfConcern{
param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("SDEGROUP1","SDEGROUP2","SDEGROUP3","SDEGROUP4","SDEGROUP5","SDEGROUP6","SDEGROUP7","SDEGROUP8","SDEGROUP9","SDEGROUP10")]
        [String]$GroupName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$GroupDescription,
        [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true)]
        [Int]$MaximumNumberOfRowsToDisplay = 5,
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$true)]
        [Int]$DaysUntilConcern = 30,
        [Parameter(Position=5,Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        [String]$Color,
        [Parameter(Position=6,Mandatory=$false,ValueFromPipeline=$true)]
        [String]$ServerInstance = "SDESQLSERVER"
    )
    
    $OutputArrays =@()
    $sqlquery = $null
    $Array = $null

    $Currentyear = get-date -Format yyyy
    $Currentdate = get-date -Format MM/dd
    $Lastyear = ($Currentyear-1)
    $DateOfConcern=(Get-Date).AddDays(-($DaysUntilConcern)).ToString('yyyy/MM/dd')

    #========================================================================================
    #SQL Query To Find Open Work Orders
    #========================================================================================
    $sqlquery = "SELECT [Incident #],[First Name],[Last Name],[Login ID Assigned To],[Open Date & Time],[Incident Description] ,[LastModified]
                  FROM [SDE_PROD].[_SMDBA_].[Incident]
                  WHERE ([Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT' AND [Open Date & Time] <= '$DateOfConcern' AND [Group Name] = '$GroupName')
                  ORDER BY [Open Date & Time] ASC"

    Write-Host $sqlquery

    #Generate Incident Concern Count
    $IncidentConcern = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance  | `
                        Select `
                        @{L='Concern #';E={$_."Incident #"}},`
                        @{L='Customer';E={$_."First Name"+" "+$_."Last Name"}},`
                        @{L='Assigned To';E={$_."Login ID Assigned To"}},
                        @{L='Days Old';E={-((NEW-TIMESPAN –Start (GET-DATE) –End ([datetime]$_."Open Date & Time")).Days)}},`
                        @{L='Brief Description';E={($_."Incident Description").SubString(0,80)}},`
                        @{L='Days Ago Last Modified';E={-((NEW-TIMESPAN –Start (GET-DATE) –End ([datetime]$_."LastModified")).Days)}}
    [int]$IncidentConcernCount = $IncidentConcern.Count

    $sqlquery = "SELECT [Work Order #],[First Name],[Last Name],[Login ID Assigned To],[Open Date & Time],[Incident Description] ,[LastModified]
                  FROM [SDE_PROD].[_SMDBA_].[Work Orders]
                  WHERE ([Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT' AND [Open Date & Time] <= '$DateOfConcern' AND [Group Name] = '$GroupName')
                  ORDER BY [Open Date & Time] ASC"

    Write-Host $sqlquery

    #Generate Work Order Concern Count
    $WorkOrderConcern = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance  | `
                        Select `
                        @{L='Concern #';E={$_."Work Order #"}},`
                        @{L='Customer';E={$_."First Name"+" "+$_."Last Name"}},`
                        @{L='Assigned To';E={$_."Login ID Assigned To"}},`
                        @{L='Days Old';E={-((NEW-TIMESPAN –Start (GET-DATE) –End ([datetime]$_."Open Date & Time")).Days)}},`
                        @{L='Brief Description';E={($_."Incident Description").SubString(0,80)}},`
                        @{L='Days Ago Last Modified';E={-((NEW-TIMESPAN –Start (GET-DATE) –End ([datetime]$_."LastModified")).Days)}}
    [int]$WorkOrderConcernCount = $WorkOrderConcern.Count

    $AllConcerns = @()
    $AllConcerns += $IncidentConcern
    $AllConcerns += $WorkOrderConcern

    $AllConcernCount = $IncidentConcernCount+$WorkOrderConcernCount

    if([bool]($MyInvocation.BoundParameters.Keys -contains 'Color')){
        if ($AllConcernCount -le 2){$Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. You should take care of these soon, but good work keeping this number low. If you solve all of these, this section will be removed."}
        elseif ($AllConcernCount -le 5){$Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. All though this number is low, ideally it should be zero."}
        elseif ($AllConcernCount -le 10){$Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. The number of older concerns is becoming critical."}
        elseif ($AllConcernCount -le 15){$Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. The number of older concerns is becoming increasingly critical."}
        elseif ($AllConcernCount -le 20){$Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. The amount of concerns is almost critital and should be addressed as a top priority."}
        else{$Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. This is a critical and should be addressed as a priority."}
    }else{

        if ($AllConcernCount -le 2){$Color = "Green"; $Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. You should take care of these soon, but good work keeping this number low. If this number goes above two, we will display this as yellow. If you solve all of these, this section will be removed."}
        elseif ($AllConcernCount -le 5){$Color = "Yellow"; $Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. All though this number is low, ideally it should be zero. Once you get this down to two, we will show this area as green."}
        elseif ($AllConcernCount -le 10){$Color = "Yellow"; $Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. The number of older concerns is becoming critical. Once you get this down to two, we will show this area as green."}
        elseif ($AllConcernCount -le 15){$Color = "Red"; $Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. The number of older concerns is becoming increasingly critical. Once you get this down to ten, we will show this area as yellow."}
        elseif ($AllConcernCount -le 20){$Color = "Red"; $Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. The amount of concerns is almost critital and should be addressed as a top priority. Once you get this down to ten, we will show this area as yellow."}
        else{$Color = "Red"; $Message = "There are $IncidentConcernCount Incidents and $WorkOrderConcernCount Work Orders that have been open at least $DaysUntilConcern days. This is a critical and should be addressed as a priority. Once you get this down to ten, we will show this area as yellow."}
    }

    if ($AllConcernCount -gt $MaximumNumberOfRowsToDisplay){
        $Title = "$GroupDescription`: At Least $DaysUntilConcern days old (only showing top $MaximumNumberOfRowsToDisplay)";
    }else{
        $Title = "$GroupDescription`: At Least $DaysUntilConcern days old";
    }

    #Array2
    $output = [PSCustomObject] @{
    'Message' = $Message;
    'Title' = $Title;
    'Color' = $Color;
    'Array' = $AllConcerns | Sort-Object -Property "Days Old" -Descending | Select-Object -First $MaximumNumberOfRowsToDisplay;
    }
    if ($output.Array -ne $NULL){return $output}
}

PowerShell Module: New-DGMSDEOpenIncidents.psm1


<#
.SYNOPSIS
  Generates New-DMGSDEOpenIncidents
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-22
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

function New-DMGSDEOpenIncidents{
param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("SDEGROUP1","SDEGROUP2","SDEGROUP3","SDEGROUP4","SDEGROUP5","SDEGROUP6","SDEGROUP7","SDEGROUP8","SDEGROUP9","SDEGROUP10")]
        [String]$GroupName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$GroupDescription,
        [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true)]
        $MaximumNumberOfRowsToDisplay = 50,
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        [String]$Color = "Blue",
        [Parameter(Position=5,Mandatory=$false,ValueFromPipeline=$true)]
        $ServerInstance = "SDESQLSERVER"
    )
    
    $OutputArrays =@()
    $sqlquery = $null
    $Array = $null

    #========================================================================================
    #SQL Query To Find Open Incidents
    #========================================================================================
    $sqlquery = "SELECT [Incident #],[First Name],[Last Name],[Login ID Assigned To],[Open Date & Time],[Incident Description]
      FROM [SDE_PROD].[_SMDBA_].[Incident]
      WHERE ([Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT' AND [Group Name] = '$GroupName')
      ORDER BY [Incident #] DESC"

    #Generate different messages and colors based on the number open
    $Array = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance  | Select `
        "Incident #",`
        @{L='Customer';E={$_."First Name"+" "+$_."Last Name"}},`
        @{L='Assigned To';E={$_."Login ID Assigned To"}},
        @{L='Opened Date';E={($_."Open Date & Time").ToString("MM/dd/yyyy")}},
        "Incident Description"
    [int]$IncidentCount = $Array.Count


    if([bool]($MyInvocation.BoundParameters.Keys -contains 'Color')){
        if ($IncidentCount -le 19){$Message = "These are the $IncidentCount open incidents currently assigned to your group. This small number opened is considered excellent!"}
        elseif ($IncidentCount -le 30){$Message = "These are the $IncidentCount open incidents currently assigned to your group."}
        elseif ($IncidentCount -le 60){$Message = "These are the $IncidentCount open incidents currently assigned to your group. The number of open incidents is getting somewhat critical."}
        elseif ($IncidentCount -le 99){$Message = "These are the $IncidentCount open incidents currently assigned to your group. The number of open incidents is getting critical."}
        elseif ($IncidentCount -le 199){$Message = "These are the $IncidentCount open incidents currently assigned to your group. With over 100 open incidents, the number is critical!"}
        else{$Message = "These are the $IncidentCount open incidents currently assigned to your group. At over 200 tickets, the number of open incidents is severe!"}
    }
    else{
        if ($IncidentCount -le 19){$Color = "Green"; $Message = "These are the $IncidentCount open incidents currently assigned to your group. This small number opened is considered excellent!"}
        elseif ($IncidentCount -le 30){$Color = "Green"; $Message = "These are the $IncidentCount open incidents currently assigned to your group."}
        elseif ($IncidentCount -le 60){$Color = "Yellow"; $Message = "These are the $IncidentCount open incidents currently assigned to your group. The number of open incidents is getting somewhat critical."}
        elseif ($IncidentCount -le 99){$Color = "Red"; $Message = "These are the $IncidentCount open incidents currently assigned to your group. The number of open incidents is getting critical."}
        elseif ($IncidentCount -le 199){$Color = "Red"; $Message = "These are the $IncidentCount open incidents currently assigned to your group. With over 100 open incidents, the number is critical!"}
        else{$Color = "Red"; $Message = "These are the $IncidentCount open incidents currently assigned to your group. At over 200 tickets, the number of open incidents is severe!"}
    }

    if ($Array.Count -gt $MaximumNumberOfRowsToDisplay){
        $Title = "$GroupDescription`: $IncidentCount Open Incidents (only showing top $MaximumNumberOfRowsToDisplay)";
    }else{
        $Title = "$GroupDescription`: $IncidentCount Open Incidents";
    }

    #Array1
    $output = [PSCustomObject] @{
    'Message' = $Message;
    'Title' = $Title;
    'Color' = $Color;
    'Array' = $Array | Select-Object -First $MaximumNumberOfRowsToDisplay;
    }
    if ($output.Array -ne $NULL){return $output}
}

PowerShell Module: New-DGMSDEOpenWorkOrders.psm1


<#
.SYNOPSIS
  Generates New-DMGSDEOpenIncidents
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-22
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

function New-DMGSDEOpenWorkOrders{
param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("SDEGROUP1","SDEGROUP2","SDEGROUP3","SDEGROUP4","SDEGROUP5","SDEGROUP6","SDEGROUP7","SDEGROUP8","SDEGROUP9","SDEGROUP10")]
        [String]$GroupName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$GroupDescription,
        [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true)]
        $MaximumNumberOfRowsToDisplay = 50,
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        [String]$Color = "Blue",
        [Parameter(Position=5,Mandatory=$false,ValueFromPipeline=$true)]
        $ServerInstance = "SDESQLSERVER"
    )
    
    $OutputArrays =@()
    $sqlquery = $null
    $Array = $null

    #========================================================================================
    #SQL Query To Find Open Work Orders
    #========================================================================================
    $sqlquery = "SELECT [Work Order #],[First Name],[Last Name],[Login ID Assigned To],[Open Date & Time],[Incident Description]
      FROM [SDE_PROD].[_SMDBA_].[Work Orders]
      WHERE ([Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT' AND [Group Name] = '$GroupName')
      ORDER BY [Work Order #] DESC"

    #Generate different messages and colors based on the number open
    $Array = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance  | Select `
        "Work Order #",`
        @{L='Customer';E={$_."First Name"+" "+$_."Last Name"}},`
        @{L='Assigned To';E={$_."Login ID Assigned To"}},
        @{L='Opened Date';E={($_."Open Date & Time").ToString("MM/dd/yyyy")}},
        "Incident Description"
    [int]$WorkOrderCount = $Array.Count

    if([bool]($MyInvocation.BoundParameters.Keys -contains 'Color')){
        if ($WorkOrderCount -le 19){$Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. This small number opened is considered excellent!"}
        elseif ($WorkOrderCount -le 30){$Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group."}
        elseif ($WorkOrderCount -le 60){$Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. The number of open Work Orders is getting somewhat critical."}
        elseif ($WorkOrderCount -le 99){$Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. The number of open Work Orders is getting critical."}
        elseif ($WorkOrderCount -le 199){$Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. With over 100 open Work Orders, the number is critical!"}
        else{$Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. At over 200 tickets, the number of open Work Orders is severe!"}
    }
    else{
        if ($WorkOrderCount -le 19){$Color = "Green"; $Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. This small number opened is considered excellent!"}
        elseif ($WorkOrderCount -le 30){$Color = "Green"; $Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group."}
        elseif ($WorkOrderCount -le 60){$Color = "Yellow"; $Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. The number of open Work Orders is getting somewhat critical."}
        elseif ($WorkOrderCount -le 99){$Color = "Red"; $Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. The number of open Work Orders is getting critical."}
        elseif ($WorkOrderCount -le 199){$Color = "Red"; $Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. With over 100 open Work Orders, the number is critical!"}
        else{$Color = "Red"; $Message = "These are the $WorkOrderCount open Work Orders currently assigned to your group. At over 200 tickets, the number of open Work Orders is severe!"}
    }

    if ($Array.Count -gt $MaximumNumberOfRowsToDisplay){
        $Title = "$GroupDescription`: $WorkOrderCount Open Work Orders (only showing top $MaximumNumberOfRowsToDisplay)";
    }else{
        $Title = "$GroupDescription`: $WorkOrderCount Open Work Orders";
    }

    #Array2
    $output = [PSCustomObject] @{
    'Message' = $Message;
    'Title' = $Title;
    'Color' = $Color;
    'Array' = $Array | Select-Object -First $MaximumNumberOfRowsToDisplay;
    }
    if ($output.Array -ne $NULL){return $output}
}

PowerShell Module: New-DGMSDETeamsOpenHistory.psm1


<#
.SYNOPSIS
  Generates New-DMGSDEOpenIncidents
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-22
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

function New-DMGSDETeamsOpenHistory{
param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='Single Team')]
        [ValidateSet("SDEGROUP1","SDEGROUP2","SDEGROUP3","SDEGROUP4","SDEGROUP5","SDEGROUP6","SDEGROUP7","SDEGROUP8","SDEGROUP9","SDEGROUP10")]
        [String]$GroupName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='Single Team')]
        [String]$GroupDescription,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true,ParameterSetName='All Teams')]
        [Switch]$ShowAllTeams,
        [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        [String]$Color = "Blue",
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$true)]
        $ServerInstance = "SDESQLSERVER"
    )
    
    $OutputArrays =@()
    $sqlquery = $null
    $Array = $null
    $teams = @()
    $Currentyear = get-date -Format yyyy
    $Currentdate = get-date -Format MM/dd
    $Lastyear = ($Currentyear-1)

    #========================================================================================
    #SQL Query To Find Open Tickets For Each Team
    #========================================================================================

    if([bool]($MyInvocation.BoundParameters.Keys -contains 'ShowAllTeams')){

        $teams += [PSCustomObject] @{'Group' = 'BHD'; 'Description' = 'Branch Help Desk'};
        $teams += [PSCustomObject] @{'Group' = 'CCENT'; 'Description' = 'DS User Request'};
        $teams += [PSCustomObject] @{'Group' = 'CORPSEC'; 'Description' = 'Corporate Security'};
        $teams += [PSCustomObject] @{'Group' = 'DBA'; 'Description' = 'Database Administrators'};
        $teams += [PSCustomObject] @{'Group' = 'DS_ADMIN'; 'Description' = 'Directory Services Administrators'};
        $teams += [PSCustomObject] @{'Group' = 'DW'; 'Description' = 'Data Warehouse'};
        $teams += [PSCustomObject] @{'Group' = 'EA'; 'Description' = 'Enterprise Architecture'};
        $teams += [PSCustomObject] @{'Group' = 'EA_DEV'; 'Description' = 'Enterprise Architecture - Dev'};
        $teams += [PSCustomObject] @{'Group' = 'EDW_ADMIN'; 'Description' = 'EDW Administration'};
        $teams += [PSCustomObject] @{'Group' = 'GOVERNANCE'; 'Description' = 'IT Governance'};
        $teams += [PSCustomObject] @{'Group' = 'HELP DESK'; 'Description' = 'Technology Help Desk'};
        $teams += [PSCustomObject] @{'Group' = 'INFOSEC'; 'Description' = 'Information Security'};
        $teams += [PSCustomObject] @{'Group' = 'MAC'; 'Description' = 'Move / Add / Change'};
        $teams += [PSCustomObject] @{'Group' = 'N2K'; 'Description' = 'Network 2000 - General'};
        $teams += [PSCustomObject] @{'Group' = 'N2K_IMAGE'; 'Description' = 'Network 2000 - Imaging'};
        $teams += [PSCustomObject] @{'Group' = 'NOG'; 'Description' = 'Network Team'};
        $teams += [PSCustomObject] @{'Group' = 'SHRSVS'; 'Description' = 'Server Infr. Team'};
        $teams += [PSCustomObject] @{'Group' = 'TECH_OPS_CHANNELS'; 'Description' = 'Tech-Ops Channels'};
        $teams += [PSCustomObject] @{'Group' = 'TECH_OPS_CORE_IPS'; 'Description' = 'Tech-Ops Core & IPS'};
        $teams += [PSCustomObject] @{'Group' = 'TECH_OPS_LENDING_CREDIT'; 'Description' = 'Tech-Ops Lending Credit'};
        $teams += [PSCustomObject] @{'Group' = 'TECH_PLATFORM_MANAGERS'; 'Description' = 'Tech-IT Platform Managers'};
        $teams += [PSCustomObject] @{'Group' = 'TELECOMS'; 'Description' = 'Telecom Team'};
        #$teams += [PSCustomObject] @{'Group' = 'WORKFLOW'; 'Description' = 'Nautilus Workflow'};
        $teams += [PSCustomObject] @{'Group' = 'WSG'; 'Description' = 'Endpoint Admins Team'};
        $teams += [PSCustomObject] @{'Group' = 'XEROX'; 'Description' = 'Xerox'};
    }else{
        $teams += [PSCustomObject] @{'Group' = $GroupName; 'Description' = $GroupDescription};
    }

    
    $Array = @()

    foreach ($team in $teams){
      
      #==Currently Open
      $sqlquery = "SELECT COUNT ([Work Order #])
      FROM [SDE_PROD].[_SMDBA_].[Work Orders]
      WHERE ([Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT' AND [Group Name] = '"+($team.Group)+"')"

      $team_WO = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance
      [int]$team_WO_Count = $team_WO.Column1

      $sqlquery = "SELECT COUNT ([Incident #])
      FROM [SDE_PROD].[_SMDBA_].[Incident]
      WHERE ([Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT' AND [Group Name] = '"+($team.Group)+"')"

      $team_IN = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance
      [int]$team_IN_Count = $team_IN.Column1

      #==This Year Count
      $sqlquery = "SELECT COUNT ([Incident #])
      FROM [SDE_PROD].[_SMDBA_].[Incident]
      WHERE ([Close Date & Time] between '$Currentyear/01/01' and '$Currentyear/12/31' AND [Group Name] = '"+($team.Group)+"')"

      $Closed_This_Year_IN = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      $sqlquery = "SELECT COUNT ([Work Order #])
      FROM [SDE_PROD].[_SMDBA_].[Work Orders]
      WHERE ([Close Date & Time] between '$Currentyear/01/01' and '$Currentyear/12/31' AND [Group Name] = '"+($team.Group)+"')"

      $Closed_This_Year_WO = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      [int]$Closed_This_Year_Count = $Closed_This_Year_IN.Column1 + $Closed_This_Year_WO.Column1

      #==Last Year Count
      $sqlquery = "SELECT COUNT ([Incident #])
      FROM [SDE_PROD].[_SMDBA_].[Incident]
      WHERE ([Close Date & Time] between '$Lastyear/01/01' and '$Lastyear/12/31' AND [Group Name] = '"+($team.Group)+"')"

      $Closed_Last_Year_IN = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      $sqlquery = "SELECT COUNT ([Work Order #])
      FROM [SDE_PROD].[_SMDBA_].[Work Orders]
      WHERE ([Close Date & Time] between '$Lastyear/01/01' and '$Lastyear/12/31' AND [Group Name] = '"+($team.Group)+"')"

      $Closed_Last_Year_WO = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      [int]$Closed_Last_Year_Count = $Closed_Last_Year_IN.Column1 + $Closed_Last_Year_WO.Column1

      #==Last Year to This Day Count
      $sqlquery = "SELECT COUNT ([Incident #])
      FROM [SDE_PROD].[_SMDBA_].[Incident]
      WHERE ([Close Date & Time] between '$Lastyear/01/01' and '$LastYear/$Currentdate' AND [Group Name] = '"+($team.Group)+"')"

      $Closed_Last_Year_To_This_Day_IN = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      $sqlquery = "SELECT COUNT ([Work Order #])
      FROM [SDE_PROD].[_SMDBA_].[Work Orders]
      WHERE ([Close Date & Time] between '$Lastyear/01/01' and '$LastYear/$Currentdate' AND [Group Name] = '"+($team.Group)+"')"

      $Closed_Last_Year_To_This_Day_WO = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      [int]$Closed_Last_Year_To_This_Day_Count = $Closed_Last_Year_To_This_Day_IN.Column1 + $Closed_Last_Year_To_This_Day_WO.Column1

      #==Tickets Open Exactly a year ago
      $sqlquery = "SELECT COUNT ([Incident #])
      FROM [SDE_PROD].[_SMDBA_].[Incident]
      WHERE ([Open Date & Time] <= '$LastYear/$Currentdate' AND [Close Date & Time] > '$LastYear/$Currentdate' AND [Group Name] = '"+($team.Group)+"')"

      $Open_Exactly_A_Year_Ago_IN = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      $sqlquery = "SELECT COUNT ([Work Order #])
      FROM [SDE_PROD].[_SMDBA_].[Work Orders]
      WHERE ([Open Date & Time] <= '$LastYear/$Currentdate' AND [Close Date & Time] > '$LastYear/$Currentdate' AND [Group Name] = '"+($team.Group)+"')"

      $Open_Exactly_A_Year_Ago_WO = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

      [int]$Open_Exactly_A_Year_Ago_Count = $Open_Exactly_A_Year_Ago_IN.Column1 + $Open_Exactly_A_Year_Ago_WO.Column1

      $output = [PSCustomObject] @{
        'Team' = $team.Description;
        'SDE Group' = $team.Group;
        "Closed Total $Lastyear" = $Closed_Last_Year_Count; 
        "Closed So Far $Lastyear" = $Closed_Last_Year_To_This_Day_Count;
        "Closed So Far $Currentyear" = $Closed_This_Year_Count;
        #'Open Work Orders' = $team_WO_Count;
        #'Open Incidents' = $team_IN_Count;
        "# Open This Day in $Lastyear" = $Open_Exactly_A_Year_Ago_Count;
        'Open Today' = $team_WO_Count+$team_IN_Count;
        }

        $Array+=$output

    }
    
    #Array3
    $output = [PSCustomObject] @{
    'Message' = "This is a look at all of the open tickets and closed ticket trends amongst the other teams. For reference, your SDE Group is $GroupName. This list is sorted by Open Now";
    'Title' = "SDE Group Trends";
    'Color' = $Color;
    'Array' = $Array | Sort-Object -Descending -Property "Open Today";
    }
    if ($output.Array -ne $NULL){return $output}
}

PowerShell Module: New-DGMSDETeamTicketHistory2.psm1


<#
.SYNOPSIS
  Generates New-DMGSDETeamTicketHistory
.NOTES
  Version:        2.0
  Author:         David Maiolo
  Creation Date:  2018-01-26
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

function New-DMGSDETeamTicketHistory2{
param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("SDEGROUP1","SDEGROUP2","SDEGROUP3","SDEGROUP4","SDEGROUP5","SDEGROUP6","SDEGROUP7","SDEGROUP8","SDEGROUP9","SDEGROUP10")]
        [String]$GroupName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$GroupDescription,
        [Parameter(Position=2,Mandatory=$false,ValueFromPipeline=$true)]
        [Int]$NumOfMonthsToShowTrends = 7,
        [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        [String]$Color = "Blue",
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$true)]
        $ServerInstance = "SDESQLSERVER"
    )
    
    $OutputArrays =@()
    $sqlquery = $null
    $Array = $null

    $Currentyear = get-date -Format yyyy
    $Currentdate = get-date -Format MM/dd
    $Lastyear = ($Currentyear-1)

    #========================================================================================
    #SQL Query To Find Ticket History
    #========================================================================================
    $Array = @()
    
    #Create Array Of All The Dates
    $Dates = @()
    $i=$NumOfMonthsToShowTrends
    while ($i -ge 0){
        
        <#
        $Date=(Get-Date).AddMonths(-($i)).ToString('yyyy/MM/dd')
        $DateMinusOne = (Get-Date).AddMonths(-($i+1)).ToString('yyyy/MM/dd')
        #>

        $CurrentDate=(Get-Date).AddMonths(-($i)).ToString('MM/dd/yyyy')

        $FirstDayOfMonth=GET-DATE $CurrentDate -Day 1
        $LastDayOfMonth=GET-DATE $FirstDayOfMonth.AddMonths(1).AddSeconds(-1)

        $Year = $FirstDayOfMonth.ToString('yy')
        $Month = $FirstDayOfMonth.ToString('MM')

        $Date = $LastDayOfMonth.ToString('yyyy/MM/dd')
        $DateMinusOne = $FirstDayOfMonth.ToString('yyyy/MM/dd')

        $output = [PSCustomObject] @{
        'Date' = $Date;
        'DateMinusOne' = $DateMinusOne;
        'Month' = $Month;
        'Year' = $Year;
        }

        $Dates+=$output
        $i--
    }

    foreach ($DateToProcess in $Dates){
        
        $Date = $DateToProcess.Date
        $DateMinusOne = $DateToProcess.DateMinusOne
        $Month = $DateToProcess.Month
        $Month = (Get-Culture).DateTimeFormat.GetMonthName($Month)
        $Year = $DateToProcess.Year

        <#
        Write-Host "Processing..."
        Write-Host "Date: $Date"
        Write-Host "DateMinusOne: $DateMinusOne"
        Write-Host "Month: $Month"
        Write-Host "Year: $Year"
        #>

        #Incidents Opened
        $sqlquery = "SELECT COUNT ([Incident #])
        FROM [SDE_PROD].[_SMDBA_].[Incident]
        WHERE ([Open Date & Time] between '$DateMinusOne' and '$Date' AND [Group Name] = '$GroupName')"
        $IncidentsOpened = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance
        [int]$IncidentsOpenedCount = $IncidentsOpened.Column1

        #Work Orders Opened
        $sqlquery = "SELECT COUNT ([Work Order #])
        FROM [SDE_PROD].[_SMDBA_].[Work Orders]
        WHERE ([Open Date & Time] between '$DateMinusOne' and '$Date' AND [Group Name] = '$GroupName')"
        $WorkOrdersOpened = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance
        [int]$WorkOrdersOpenedCount = $WorkOrdersOpened.Column1

        #Total Opened
        [int]$TotalOpenedCount = $IncidentsOpenedCount+$WorkOrdersOpenedCount

        #Incidents Closed
        $sqlquery = "SELECT COUNT ([Incident #])
        FROM [SDE_PROD].[_SMDBA_].[Incident]
        WHERE ([Close Date & Time] between '$DateMinusOne' and '$Date' AND [Group Name] = '$GroupName')"
        $IncidentsClosed = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance
        [int]$IncidentsClosedCount = $IncidentsClosed.Column1

        #Work Orders Closed
        $sqlquery = "SELECT COUNT ([Work Order #])
        FROM [SDE_PROD].[_SMDBA_].[Work Orders]
        WHERE ([Close Date & Time] between '$DateMinusOne' and '$Date' AND [Group Name] = '$GroupName')"
        $WorkOrdersClosed = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance
        [int]$WorkOrdersClosedCount = $WorkOrdersClosed.Column1

        #Total Closed
        [int]$TotalClosedCount = $IncidentsClosedCount+$WorkOrdersClosedCount

        #Ratio Opened to Closed
        if($TotalOpenedCount -gt 0){
            $RatioOpenedtoClosedCount = ([math]::Round((($TotalClosedCount)/($TotalOpenedCount)),2))
        }else{
            $RatioOpenedtoClosedCount = 0
        }

        $output = [PSCustomObject] @{
        'Month' = $Month+" `'"+$Year;
        #'Incidents Opened' = $IncidentsOpenedCount;
        #'Work Orders Opened' = $WorkOrdersOpenedCount;
        "Total Opened" = $TotalOpenedCount; 
        #"Incidents Closed" = $IncidentsClosedCount;
        #"Work Orders Closed" = $WorkOrdersClosedCount;
        "Total Closed" = $TotalClosedCount;
        'Ratio Opened to Closed' = $RatioOpenedtoClosedCount;
        }

        $Array+=$output

    }

    #Array4
    $output = [PSCustomObject] @{
    'Message' = "This is a look at the ticket trends for this group over the past $NumOfMonthsToShowTrends months. Ratios greater than 1.0 mean more tickets were closed than were opened, whereas those lower than 1.0 mean more tickets were opened than closed. ";
    'Title' = "$GroupDescription`: Open / Close Ratios";
    'Color' = $Color;
    'Array' = $Array;
    }
    if ($output.Array -ne $NULL){return $output}
}

PowerShell Module: New-DGMSDETeamUserHistory.psm1


<#
.SYNOPSIS
  Generates New-DMGSDEOpenIncidents
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-22
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

function New-DMGSDETeamUserHistory{
param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("SDEGROUP1","SDEGROUP2","SDEGROUP3","SDEGROUP4","SDEGROUP5","SDEGROUP6","SDEGROUP7","SDEGROUP8","SDEGROUP9","SDEGROUP10")]
        [String]$GroupName,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$GroupDescription,
        [Parameter(Position=2,Mandatory=$false,ValueFromPipeline=$true)]
        [Int]$NumOfMonthsToShowTrends = 7,
        [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true)]
        [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")]
        [String]$Color = "Blue",
        [Parameter(Position=4,Mandatory=$false,ValueFromPipeline=$true)]
        $ServerInstance = "SDESQLSERVER"
    )
    
    $OutputArrays =@()
    $sqlquery = $null
    $Array = $null

    #Compute Month 0 Variables
    $i=0
    $CurrentDate0=(Get-Date).AddMonths(-($i)).ToString('MM/dd/yyyy')

    $FirstDayOfMonth0=GET-DATE $CurrentDate0 -Day 1
    $LastDayOfMonth0=GET-DATE $FirstDayOfMonth0.AddMonths(1).AddSeconds(-1)

    $Year0 = $FirstDayOfMonth0.ToString('yy')
    $Month0 = $FirstDayOfMonth0.ToString('MM')
    $Month0 = (Get-Culture).DateTimeFormat.GetMonthName($Month0)

    $Date0 = $LastDayOfMonth0.ToString('yyyy/MM/dd')
    $DateMinusOne0 = $FirstDayOfMonth0.ToString('yyyy/MM/dd')

    #Compute Month 1 Variables
    $i=1
    $CurrentDate1=(Get-Date).AddMonths(-($i)).ToString('MM/dd/yyyy')

    $FirstDayOfMonth1=GET-DATE $CurrentDate1 -Day 1
    $LastDayOfMonth1=GET-DATE $FirstDayOfMonth1.AddMonths(1).AddSeconds(-1)

    $Year1 = $FirstDayOfMonth1.ToString('yyy')
    $Month1 = $FirstDayOfMonth1.ToString('MM')
    $Month1 = (Get-Culture).DateTimeFormat.GetMonthName($Month1)

    $Date1 = $LastDayOfMonth1.ToString('yyyy/MM/dd')
    $DateMinusOne1 = $FirstDayOfMonth1.ToString('yyyy/MM/dd')

    #Compute Month 2 Variables
    $i=2
    $CurrentDate2=(Get-Date).AddMonths(-($i)).ToString('MM/dd/yyyy')

    $FirstDayOfMonth2=GET-DATE $CurrentDate2 -Day 1
    $LastDayOfMonth2=GET-DATE $FirstDayOfMonth2.AddMonths(1).AddSeconds(-1)

    $Year2 = $FirstDayOfMonth2.ToString('yyyy')
    $Month2 = $FirstDayOfMonth2.ToString('MM')
    $Month2 = (Get-Culture).DateTimeFormat.GetMonthName($Month2)

    $Date2 = $LastDayOfMonth2.ToString('yyyy/MM/dd')
    $DateMinusOne2 = $FirstDayOfMonth2.ToString('yyyy/MM/dd')

    #Compute Month 3 Variables
    $i=3
    $CurrentDate3=(Get-Date).AddMonths(-($i)).ToString('MM/dd/yyyy')

    $FirstDayOfMonth3=GET-DATE $CurrentDate3 -Day 1
    $LastDayOfMonth3=GET-DATE $FirstDayOfMonth3.AddMonths(1).AddSeconds(-1)

    $Year3 = $FirstDayOfMonth3.ToString('yyyy')
    $Month3 = $FirstDayOfMonth3.ToString('MM')
    $Month3 = (Get-Culture).DateTimeFormat.GetMonthName($Month3)

    $Date3 = $LastDayOfMonth3.ToString('yyyy/MM/dd')
    $DateMinusOne3 = $FirstDayOfMonth3.ToString('yyyy/MM/dd')

    #========================================================================================
    #SQL Query To Find User Trends
    #========================================================================================
    $Array = @()

    $sqlquery = "SELECT [Support Staff ID]
      ,[Full Name]
      FROM [SDE_PROD].[_SMDBA_].[Groups Details]
      WHERE ([Group ID] = '$GroupName')"

    $TeamMmbers = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance


    foreach ($TeamMmber in $TeamMmbers){

        #Query For Incidents Open
        $sqlquery = "SELECT COUNT ([Incident #])
          FROM [SDE_PROD].[_SMDBA_].[Incident]
          WHERE ([Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"' AND [Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT')"

        $TotalIncidentsOpen = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Work Orders Open
        $sqlquery = "SELECT COUNT ([Work Order #])
          FROM [SDE_PROD].[_SMDBA_].[Work Orders]
          WHERE ([Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"' AND [Status ID:] != 'Closed' AND [Status ID:] != 'ADMIN_CLOSE' AND [Status ID:] != 'CANCEL' AND [Status ID:] != 'REJECT')"

        $TotalWorkOrdersOpen = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #=====
        #Query For Incidents Closed
        $sqlquery = "SELECT COUNT ([Incident #])
          FROM [SDE_PROD].[_SMDBA_].[Incident]
          WHERE ([Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"' AND ([Status ID:] = 'Closed' OR [Status ID:] = 'ADMIN_CLOSE'))"

        $TotalIncidentsClosed = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Work Orders Closed
        $sqlquery = "SELECT COUNT ([Work Order #])
          FROM [SDE_PROD].[_SMDBA_].[Work Orders]
          WHERE ([Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"' AND ([Status ID:] = 'Closed' OR [Status ID:] = 'ADMIN_CLOSE'))"

        $TotalWorkOrdersClosed = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Work Orders Closed At Date0
        $sqlquery = "SELECT COUNT ([Work Order #])
            FROM [SDE_PROD].[_SMDBA_].[Work Orders]
            WHERE ([Close Date & Time] between '$DateMinusOne0' and '$Date0' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalWorkOrdersClosedAtDate0 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Incidents Closed At Date0
        $sqlquery = "SELECT COUNT ([Incident #])
            FROM [SDE_PROD].[_SMDBA_].[Incident]
            WHERE ([Close Date & Time] between '$DateMinusOne0' and '$Date0' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalIncidentsClosedAtDate0 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Work Orders Closed At Date1
        $sqlquery = "SELECT COUNT ([Work Order #])
            FROM [SDE_PROD].[_SMDBA_].[Work Orders]
            WHERE ([Close Date & Time] between '$DateMinusOne1' and '$Date1' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalWorkOrdersClosedAtDate1 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Incidents Closed At Date1
        $sqlquery = "SELECT COUNT ([Incident #])
            FROM [SDE_PROD].[_SMDBA_].[Incident]
            WHERE ([Close Date & Time] between '$DateMinusOne1' and '$Date1' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalIncidentsClosedAtDate1 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

         #Query For Work Orders Closed At Date2
        $sqlquery = "SELECT COUNT ([Work Order #])
            FROM [SDE_PROD].[_SMDBA_].[Work Orders]
            WHERE ([Close Date & Time] between '$DateMinusOne2' and '$Date2' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalWorkOrdersClosedAtDate2 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Incidents Closed At Date2
        $sqlquery = "SELECT COUNT ([Incident #])
            FROM [SDE_PROD].[_SMDBA_].[Incident]
            WHERE ([Close Date & Time] between '$DateMinusOne2' and '$Date2' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalIncidentsClosedAtDate2 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

         #Query For Work Orders Closed At Date3
        $sqlquery = "SELECT COUNT ([Work Order #])
            FROM [SDE_PROD].[_SMDBA_].[Work Orders]
            WHERE ([Close Date & Time] between '$DateMinusOne3' and '$Date3' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalWorkOrdersClosedAtDate3 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance

        #Query For Incidents Closed At Date3
        $sqlquery = "SELECT COUNT ([Incident #])
            FROM [SDE_PROD].[_SMDBA_].[Incident]
            WHERE ([Close Date & Time] between '$DateMinusOne3' and '$Date3' AND [Login ID Assigned To] = '"+($TeamMmber."Support Staff ID")+"')"

        $TotalIncidentsClosedAtDate3 = Invoke-Sqlcmd -Query $sqlquery -ServerInstance $ServerInstance


        $output = [PSCustomObject] @{
            'Team Member' = ($TeamMmber."Full Name");
            #'Incidents Closed' = ($TotalIncidentsClosed.Column1);
            #'Work Orders Closed' = ($TotalWorkOrdersClosed.Column1);
            "Closed $Month3 $Year3" = (($TotalWorkOrdersClosedAtDate3.Column1)+($TotalIncidentsClosedAtDate3.Column1))
            "Closed $Month2 $Year2" = (($TotalWorkOrdersClosedAtDate2.Column1)+($TotalIncidentsClosedAtDate2.Column1))
            "Closed $Month1 $Year1" = (($TotalWorkOrdersClosedAtDate1.Column1)+($TotalIncidentsClosedAtDate1.Column1))
            "Closed $Month0 $Year0" = (($TotalWorkOrdersClosedAtDate0.Column1)+($TotalIncidentsClosedAtDate0.Column1))
            #'Open Incidents' = ($TotalIncidentsOpen.Column1);
            #'Open Work orders' = ($TotalWorkOrdersOpen.Column1);
            'Total Closed' = (($TotalWorkOrdersClosed.Column1)+($TotalIncidentsClosed.Column1));
            'Currently Assigned' = (($TotalIncidentsOpen.Column1)+($TotalWorkOrdersOpen.Column1));
        }
        $Array+=$output
    }

    #Array5
    $output = [PSCustomObject] @{
    'Message' = "This is a look at the user trends over the entire SDE timeline.";
    'Title' = "$GroupDescription`: User Trends";
    'Color' = $Color;
    'Array' = $Array | Sort-Object -Property "Team Member";
    }
    if ($output.Array -ne $NULL){return $output}
}

Overview

I developed this tool, Get-DMGThreadedPingableComputers.ps1, to allow you to ping several computers in your environment at once though multiple threads, severely decreasing the amount of time required to return ping results on a large set of computers. What’s neat about it is you can easily get ping results on hundreds of computers in just seconds.

How To Use This Tool

You can either search directly for computers within the script, pass some computers through on the pipeline or import a .CSV file. You can also export results to a .CSV file.

Pass a list of computers and export results to CSV:


  Get-ADComputer -filter {name -like "*sql*"} | Get-DGMThreadedPingableComputers -csvoutput "C:\Scripts\Get-DGMThreadedPingableComputer\Get-DGMThreadedPingableComputer-output.csv" 

Pass a list of computers (with example output):


  Get-ADComputer -filter {name -like "*DGM06123*"} -Properties Description,OperatingSystem | Get-DGMThreadedPingableComputers 

No Arguments triggers searching for computers within the script (with example output):


  Get-DGMThreadedPingableComputers 

PowerShell Script: Get-DMGThreadedPingableComputers.ps1


<#
.Synopsis
   Threads and threads
.DESCRIPTION
   The tool, Get-DMGThreadedPingableComputers.ps1  was written by David Maiolo.
.EXAMPLE
   Get-DMGThreadedPingableComputers -CSVFile laps_computers_import.csv
#>

function Where-ParallelObject {
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)] $input,
        [ScriptBlock] $Filter,
        [int] $threads,
        [switch] $progressBar,
        [String] $progressBartext
    )

    $inputQueue = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )
    $results = [System.Collections.Queue]::Synchronized( (New-Object System.Collections.Queue) )

    $sessionstate = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    $sessionstate.Variables.Add(
        (New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry('inputQueue', $inputQueue, $null))
    )
    $sessionstate.Variables.Add(
        (New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry('results', $results, $null))
    )

    $runspacepool = [runspacefactory]::CreateRunspacePool(1, $threads, $sessionstate, $Host)
    $runspacepool.Open()

    foreach ($object in $input) {
        $inputQueue.Enqueue($object)
    }

    $jobs = @()

    $sbpre = '
        while($inputQueue.Count -gt 0) {
            $_ = $inputQueue.Dequeue();
            if('
    $sbpost = ') 
            {
                $results.Enqueue($_);    
            }
        }
    '

    $sb = [ScriptBlock]::Create($sbpre + $Filter.toString() + $sbpost)

    1..$threads | % {
        $job = [PowerShell]::Create().AddScript($sb)
        $job.RunspacePool = $runspacepool
        $jobs += New-Object PSObject -Property @{
            Job = $job
            Result = $job.BeginInvoke()
        }
    }

    do {
        if($progressBar.IsPresent) 
        {
            Write-Progress -Activity ($progressBartext+" " +$input.Count+ " Objects") -status ("" + $($results.Count) + " complete.") -percentComplete ( ($results.Count) / $input.Count * 100) 
        }
        Start-Sleep -Seconds 1
    } while ( $jobs.Result.IsCompleted -contains $false)

    foreach ($job in $jobs) {
        $job.Job.EndInvoke($job.Result)
    }
    $runspacepool.Close()
    $runspacepool.Dispose()

    return $results.ToArray()

}

function LogIt
{
  param (
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Global:Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($Global:ScriptStatus -ne 'Error')) { $Global:ScriptStatus = $type }
  if ($type -eq 'Error') { $Global:ScriptStatus = $type }

  if ((Get-Item $Global:LogFile).Length/1KB -gt $Global:MaxLogSizeInKB)
  {
    $log = $Global:LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $Global:LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

function GetScriptDirectory
{
  $invocation = (Get-Variable MyInvocation -Scope 1).Value
  Split-Path $invocation.MyCommand.Path
} 

function Get-DMGThreadedPingableComputers {

    param(
        [Parameter(Position=0,Mandatory=$false,ValueFromPipeline=$true)] $input,
        [ValidateScript({(Test-Path $_)})]
        [String] $csvinput,
        [ValidateScript({($_ -le 100 -and $_ -gt 0)})]
        [int] $threads=100,
        [String] $csvoutput
    )

        $path = (get-item -Path .).FullName
        $arraytoping=@()

        #Header
        Write-Host "==========================================" -ForegroundColor Cyan
        Write-Host "Ping Computers in Threads " -ForegroundColor Cyan
        Write-Host "v0.1 (2017-12-28) by dmaiolo" -ForegroundColor Cyan
        Write-Host "Threading function by jreal" -ForegroundColor Cyan
        Write-Host "==========================================" -ForegroundColor Cyan
        LogIt -message ("Starting Logging for $Global:ScriptName") -component "Main()" -type 1

        #Check for what arguments were passed
        if([bool]($MyInvocation.BoundParameters.Keys -match 'csvinput')){
            Write-Host "Importing $csvinput..."
            $csvimport = import-csv $csvinput
            $csvimport | foreach-object {$arraytoping += $_.name; Write-Host "Importing $_.name ..."}       
        }
        elseif([bool]($MyInvocation.BoundParameters.Keys -match 'input')){
            Write-Host "Importing from pipeline..."
            foreach ($object in $input) {
                $arraytoping += $object
            }
        }else{
            Write-Host "Manual Input Selected."
            $arraytoping = Get-DMGSearchQueryComputers
        }
        if([bool]($MyInvocation.BoundParameters.Keys -match 'csvoutput')){
            $csvoutputcheck = $true
        }

        #Ping the computers
        $pingablecomputers = Get-DMGOnlineComputers -ComputerList $arraytoping -Threads $threads

        #Create Pingable Table
        if ($pingablecomputers){
            Write-Host ========================================== -ForegroundColor Cyan
            Write-Host Pingable Computers -ForegroundColor Green
            Write-Host ========================================== -ForegroundColor Cyan
            $pingablecomputers | Select Name,Description,OperatingSystem | Format-Table -AutoSize
        }else{
            Write-Host "No Pingable Computers Were Found."
        }

        #Create Non-Pingable Array
        $nonpingablecomputers = $arraytoping | where {$pingablecomputers -notcontains $_}   

        #Create Non-Pingable Table
        if ($nonpingablecomputers){
            Write-Host ========================================== -ForegroundColor Cyan
            Write-Host Non Pingable Computers -ForegroundColor Red
            Write-Host ========================================== -ForegroundColor Cyan
            $nonpingablecomputers | Select Name,Description,OperatingSystem | Format-Table -AutoSize
        }else{
            Write-Host "No Non-Pingable Computers Were Found."
        }

        #Export to CSV if chosen
        if ($csvoutputcheck){
            Write-Host ========================================== -ForegroundColor Cyan
            Write-Host CSV Output Results -ForegroundColor Cyan
            Write-Host ========================================== -ForegroundColor Cyan
            
            #Build the array
            $results = @()
            $pingablecomputers | select Name,Description,OperatingSystem,@{Name='Pingable';Expression={"True"}}  | %{$results += $_ }
            $nonpingablecomputers | select Name,Description,OperatingSystem,@{Name='Pingable';Expression={"False"}} | %{$results += $_ }

            New-DMGCSVOut -csvoutputpath $csvoutput -arrayforoutput $results 
        }


        #Footer
        Write-Host ========================================== -ForegroundColor Cyan
        Write-Host "Log File of Results Generated" -ForegroundColor Cyan
        Write-Host ========================================== -ForegroundColor Cyan
        Write-Host "Log File`: $Global:LogFile VIEW WITH CMTRACE.EXE"
        LogIt -message ("Ending Logging for $Global:ScriptName") -component "Main()" -type 1

}

function Get-DMGSearchQueryComputers
{
    Write-Host "[1] Search by whole or partial computer name"
    Write-Host "[2] Search by description"
    Write-Host "[3] Search by Operating System"
    do {
        try {$numOk = $true; [int]$GetMyANumber = Read-host "Selection"}
        catch {$numOK = $false}}
    until (($GetMyANumber -ge 1 -and $GetMyANumber -le 3) -and $numOK)

    $validcharacters = "^[a-zA-Z0-9\s]+$"
    do {
        try {$stringOk = $true; [string]$query = Read-host "Enter search query (only letters and numbers)"}
        catch {$stringOk = $false}}
    until (($query -match $validcharacters) -and $stringOk)
    $query = "*"+$query
    $query = $query+"*"

    switch ($GetMyANumber) 
        { 
            1 {$computers = Get-ADComputer -Properties Description,OperatingSystem -filter {name -like $query}} 
            2 {$computers = Get-ADComputer -Properties Description,OperatingSystem -filter {description -like $query}} 
            3 {$computers = Get-ADComputer -Properties Description,OperatingSystem -filter {OperatingSystem -like $query}}
        }

    return $computers
}

function Get-DMGOnlineComputers{

    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [int] $threads,
        [Array] $ComputerList
    )
    
    Write-Host ========================================== -ForegroundColor Cyan
    Write-Host Pinging Computers and Building Table -ForegroundColor Cyan
    Write-Host ========================================== -ForegroundColor Cyan
    
    $computers = @()

    if ($ComputerList.Length -gt 0){
        Write-Host "Pinging"($ComputerList.length)"computers in $threads threads."
        $computers = $ComputerList | Where-ParallelObject -Filter {Test-Connection -ComputerName $_.Name -Quiet -Count 1} -Threads $threads -ProgressBar -progressBartext "Pinging"
        <#$computers | foreach-object {
            LogIt -message ("ICMP Response Succesful`: " +($_.Name).ToString() +" - "+($_.Description).ToString()) -component "Main()" -type 1
            }#>
        return $computers
    }else{
        Write-Host "No Computers were found."
        return $false
    }
}

function New-DMGCSVOut{

    param(
            [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
            [String] $csvoutputpath,
            [array] $arrayforoutput
        )

    try{
        $arrayforoutput | export-csv $csvoutputpath -notypeinformation
        LogIt -message ("CSV Export`: CSV Created at $csvoutputpath") -component "Main()" -type 1
    }catch{
        LogIt -message ("CSV Export`: CSV Could NOT be Created at $csvoutputpath") -component "Main()" -type 3
    }


}

$VerboseLogging = "true"
[bool]$Global:Verbose = [System.Convert]::ToBoolean($VerboseLogging)
$Global:LogFile = Join-Path (GetScriptDirectory) "'Get-DMGThreadedPingableComputer.log"
$Global:MaxLogSizeInKB = 10240
$Global:ScriptName = 'Get-DMGThreadedPingableComputer.ps1' 
$Global:ScriptStatus = 'Success'

Overview

I created this series of functions to allow you to conveniently take one or more PowerShell object and have it turn into an email report. For example, if you had a list of computers you have come out of a PowerShell script you wrote, these functions could help you turn them into an email with the same data without much effort.

Create an Email with A Simple Array of Data: EASY

Import-Module \\scriptserver\scripts\Get-DGMEmailReport\Get-DGMEmailReport.psm1 -Force

Import-Module \\pathtoscriptfile\Get-DGMEmailReport.psm1 -Force


$myarray = (Get-ADComputer -properties Description -filter {description -like "*sql*"} | Select Name,Description)

#Single Array Email Report
Get-DGMEmailReport `
    -Array $myarray `
    -ArrayTitle "SQL Servers" `
    -ArrayTitleColor "Blue" `
    -ArrayMessage "These are all the SQL Servers" `
    -ReportTitle "SQL Report 2017" `
    -from "SQLReports@emailaddress.com" `
    -To "c-dmaiolo@emailaddress.com" `
    -subject "SQL Test Report"  

Create an Email With Multiple Arrays of Data: MODERATE DIFFICULTY



Import-Module \\pathtoscriptfile\Get-DGMEmailReport.psm1 -Force 

#Create Some Arrays Of Data To Display in Report. You can create as many as you want.
$OutputArrays = @()
   
#Array1
$output = [PSCustomObject] @{
'Message' = "These are deployments older than 90 days, with lower than an 80% success rate where 100 or more computers were targeted.";
'Title' = "SCCM Problem Deployments";
'Color' = "Red";
'Array' = Get-DGMSCCMProblemDeploymentsArray -Days 90 -PercentSuccessThreshold .8 -NumberOfTargetedThreshold 100;
}

$OutputArrays+=$output

#Array2
$output = [PSCustomObject] @{
'Message' = "These are all deployments that were deployed over a year ago.";
'Title' = "SCCM Deployments Greater Than 1 Year Old";
'Color' = "Green";
'Array' = Get-DGMSCCMProblemDeploymentsArray -Days 365 -PercentSuccessThreshold 1 -NumberOfTargetedThreshold 0;
}
    
$OutputArrays+=$output
    
#Array3
$output = [PSCustomObject] @{
'Message' = "These are all our SQL Servers.";
'Title' = "SQL Servers";
'Array' = Get-ADComputer -properties Description -filter {description -like "*sql*"} | Select Name,Description
}

$OutputArrays+=$output 

#Multiple Arrays Email Report
Get-DGMEmailReport `
    -Arrays $OutputArrays `
    -ReportTitle "SQL Report 2017" `
    -from "SQLReports@emailaddress.com" `
    -To "c-dmaiolo@emailaddress.com" `
    -subject "SQL Test Report" 

Sample Single Array Output

cid:image002.jpg@01D38FBC.5C3028C0

Sample Multiple Array Output Email

cid:image003.jpg@01D38FBC.5C3028C0

PowerShell Functions


<#
.SYNOPSIS
  Generates an Email Report
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-01-02
  Purpose/Change: Initial script development

#>

#---------------------------------------------------------[Initialisations]--------------------------------------------------------

Import-Module \\scriptserver\scripts\DMGSCCM\New-DMGCMTraceLog\New-DMGCMTraceLog.psm1 -Force

function New-DMGCombinedHTMLTable{
 param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        $DMGCombinedHTMLTable
    )
    $CombinedHTML = ""
    
    foreach ($object in $DMGCombinedHTMLTable){

            $CombinedHTML+= "
" if ($object.Title){ if ($object.Color){ $CombinedHTML+= "" #$CombinedHTML+= "

"+($object.Title)+"

" $CombinedHTML+= "

"+($object.Title)+"

" }else{ $CombinedHTML+= "

"+($object.Title)+"

" } } if($object.Message){ $CombinedHTML+= "

➥ "+($object.Message)+"

" } if ($object.Array){ $CombinedHTML+= $object.Array | ConvertTo-Html } $CombinedHTML+= "
" } return $CombinedHTML } function New-DMGHTMLTable{ param( [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)] $Array, [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$true)] $Title, [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$true)] $Message, [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)] [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")] $Color ) $HTML = "" $HTML+= "
" if([bool]($MyInvocation.BoundParameters.Keys -match 'Title')){ if([bool]($MyInvocation.BoundParameters.Keys -match 'Color')){ $HTML+= "" } $HTML+= "

"+($Title)+"

" } if([bool]($MyInvocation.BoundParameters.Keys -match 'Message')){ $HTML+= "

➥ "+($Message)+"

" } if([bool]($MyInvocation.BoundParameters.Keys -match 'Array')){ $HTML+= $Array | ConvertTo-Html } $HTML+= "
" return $HTML } function New-DMGEmailReport{ param( [Parameter(Position=0,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Multiple Arrays')] $Arrays, [Parameter(Position=1,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')] $Array, [Parameter(Position=2,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')] $ArrayMessage, [Parameter(Position=3,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')] $ArrayTitle, [Parameter(Position=9,Mandatory=$false,ValueFromPipeline=$true,ParameterSetName='Single Array')] [ValidateSet("Red","Yellow","Green","Black")] $ArrayTableColor = "Black", [Parameter(Position=4,Mandatory=$true,ValueFromPipeline=$true)] $From, [Parameter(Position=5,Mandatory=$true,ValueFromPipeline=$true)] $To, [Parameter(Position=6,Mandatory=$true,ValueFromPipeline=$true)] $Subject, [Parameter(Position=7,Mandatory=$true,ValueFromPipeline=$true)] $ReportTitle, [Parameter(Position=8,Mandatory=$false,ValueFromPipeline=$true)] $SmtpServer = "mail.emailaddress.com", [Parameter(Position=9,Mandatory=$false,ValueFromPipeline=$true)] [Switch]$AttatchResults, [Parameter(Position=10,Mandatory=$false,ValueFromPipeline=$true)] [Switch]$AttatchSource ) #Set Logging Varibales $invocation = (Get-Variable MyInvocation -Scope 1).Value $ScriptDirectory = Split-Path $invocation.MyCommand.Path $ScriptName = ($MyInvocation.MyCommand.Name)+".psm1" $LogName = ($MyInvocation.MyCommand.Name)+".log" $LogFile = Join-Path $ScriptDirectory $LogName $ScriptFile = Join-Path $ScriptDirectory $ScriptName $ReportDate = Get-Date $WrapperScriptPath = $MyInvocation.PSCommandPath #Map a Drive to UNC Path New-PSDrive -Name UNCPath -PSProvider FileSystem -Root $ScriptDirectory #Log Start of Function New-DMGCMTraceLog -message ("Starting Logging for $ScriptName") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile #Create Attachments Directory If It Doesn't Exist if (!(Test-Path "UNCPath:\Attachments\")){ New-DMGCMTraceLog -message ("UNCPath:\Attachments\ Not Found. Created.") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile New-Item "UNCPath:\Attachments\" -ItemType Directory } if([bool]($MyInvocation.BoundParameters.Keys -contains 'Arrays')){ $HTMLTable = New-DMGCombinedHTMLTable -DMGCombinedHTMLTable $Arrays #Log New-DMGCMTraceLog -message ("An Array of Arrays Was Passed") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile if([bool]($MyInvocation.BoundParameters.Keys -contains 'AttatchResults')){ $attachment = @() foreach ($object in $Arrays){ $AttachmentName = (Remove-DMGInvalidFileNameChars($object.Title))+".csv" $AttachmentFile = Join-Path UNCPath:\Attachments\ $AttachmentName $object.Array | export-csv -path $AttachmentFile -notypeinformation $attachment += $AttachmentFile } New-DMGCMTraceLog -message ("Attatching multiple files: $attachment") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile } }elseif([bool]($MyInvocation.BoundParameters.Keys -contains 'Array')){ $HTMLTable = New-DMGHTMLTable -Array $Array -Message $ArrayMessage -Title $ArrayTitle -Color $ArrayTableColor #Log New-DMGCMTraceLog -message ("An Single Array Was Passed") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile if([bool]($MyInvocation.BoundParameters.Keys -contains 'AttatchResults')){ $attachment = @() $AttachmentName = (Remove-DMGInvalidFileNameChars($ArrayTitle))+".csv" $AttachmentFile = Join-Path UNCPath:\Attachments\ $AttachmentName $Array | export-csv -path $AttachmentFile -notypeinformation $attachment += $AttachmentFile } New-DMGCMTraceLog -message ("Attatching the single file: $Attachment") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile } #Construct HTML for Email $HTMLEmailMessage = @" CodeNarc Report: Sample Project

$Subject

$HTMLTable

Path to script: $ScriptFile

Path to log file: $LogFile

Path to script that called $ScriptName`: $WrapperScriptPath

"@ if([bool]($MyInvocation.BoundParameters.Keys -contains 'AttatchSource')){ if ($attachment -eq $null){$attachment = @()} $AttachmentName = (Remove-DMGInvalidFileNameChars($ReportTitle))+"_html_source.txt" $AttachmentFile = Join-Path UNCPath:\ $AttachmentName $HTMLEmailMessage | Out-File -FilePath $AttachmentFile $attachment += $AttachmentFile } #Send The Email if ($Attachment){ Send-MailMessage -From $from -To $To -Subject $subject -SmtpServer $smtpServer -BodyAsHtml -Body $HTMLEmailMessage -Attachments $Attachment #Log Sent Email New-DMGCMTraceLog -message ("Email Sent`: Subject`:$subject To`:$To From`:$From Attatchments`:$attachment") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile }else{ Send-MailMessage -From $from -To $To -Subject $subject -SmtpServer $smtpServer -BodyAsHtml -Body $HTMLEmailMessage #Log Sent Email New-DMGCMTraceLog -message ("Email Sent`: Subject`:$subject To`:$To From`:$From") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile } #Remove Attachments if (Test-Path "UNCPath:\Attachments\"){ Remove-Item "UNCPath:\Attachments\" -Recurse New-DMGCMTraceLog -message ("UNCPath:\Attachments\ Found. Removing files within.") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile } #Log End Of Function New-DMGCMTraceLog -message ("End Logging for $ScriptName") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile } function Get-DMGColorHexValue{ param( [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)] [ValidateSet("Black","White","Red","Lime","Blue","Yellow","Cyan","Magenta","Silver","Gray","Maroon","Olive","Green","Purple","Teal","Navy")] $Color ) switch ($Color) { "Black" {"#000000"} "White" {"#FFFFFF"} "Red" {"#FF0000"} "Lime" {"#00FF00"} "Blue" {"#0000FF"} "Yellow" {"#FFFF00"} "Cyan" {"#00FFFF"} "Magenta" {"#FF00FF"} "Silver" {"#C0C0C0"} "Gray" {"#808080"} "Maroon" {"#800000"} "Olive" {"#808000"} "Green" {"#008000"} "Purple" {"#800080"} "Teal" {"#008080"} "Navy" {"#000080"} default {"#DDDDFF"} } } Function Remove-DMGInvalidFileNameChars { param( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)] [String]$String ) $pattern = '[^a-zA-Z]' $CleanString = $String -replace $pattern return $CleanString }

PowerShell Code: The Logging Functions

Import this module to enable the logging functions.


function New-DGMCMTraceLog{
  param (
  [Parameter(Mandatory=$true)]
  $ScriptName,
  [Parameter(Mandatory=$true)]
  $LogFile,
  [Parameter(Mandatory=$true)]
  $ScriptFile,
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  $VerboseLogging = "true"
  [bool]$Verbose = [System.Convert]::ToBoolean($VerboseLogging)
  $MaxLogSizeInKB = 10240
  $ScriptStatus = 'Success'

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($ScriptStatus -ne 'Error')) { $ScriptStatus = $type }
  if ($type -eq 'Error') { $ScriptStatus = $type }

  if ((Get-Item $LogFile -ErrorAction SilentlyContinue).Length/1KB -gt $MaxLogSizeInKB)
  {
    $log = $LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

Overview

I developed this tool to help you deploy Software Inventory Logger Servers. Software Inventory Logger collects Microsoft software inventory data on a per server basis and reports them to a central Software Inventory Aggregator. This utility can be deployed through SCCM and will setup a Software Inventory Logger Server. Because s Software Inventory Logger requires a trusted key to be distributed to the server, this tool will allow you to automatically deploy the same certificate and store the password in an encypted AES key that can either be deleted after deployment or hidden with ACL’s. This will allow you to automate the deploymenet of Software Inventory Logger Servers in an environment.

How To Use This Tool

Follow the procedures below to begin deploying Software Inventory Logger Servers in your environment.

Creating the AES Key / Secure Password Pair Files

Load the functions below and run this one time to create the AES Key and Secure password files that will be included with your deployment package. The password that you will supply will be the password to your SLI certificate. In our example below, this is the PFX certificate that can be included with the other files for easy distribution.


$cred = Get-Credential
New-DGMAESKey -Path .\ -credentialFile credentialFile -AESKeyFile AESKeyFile -passwordSecureString $cred.Password

This will generate the following two files. Along with the script below, this can be put into an SCCM or other deployment package:

sli_in_view

Deploying Via SCCM

This can easily be turned into an SCCM Application. When creating the application, call it as:


Powershell.exe -ExecutionPolicy ByPass -File "Invoke-DGMSILloggingServer.ps1"

sli_software_center

Sample Output and Logging

The utility will create a log file which is compatible with the CMTrace.exe tool.
Log_File_SLI_Aggregator

PowerShell Code


<#

.SYNOPSIS
  Enable SIL on Servers
.NOTES
  Version:        1.0
  Author:         David Maiolo
  Creation Date:  2018-02-01
  Purpose/Change: Initial script development

Enable SIL on Servers

There are several ways to enable SIL in a distributed server infrastructure, such as in a private cloud of virtual machines. 

You will need a valid client SSL certificate in .pfx format to use these steps. 
The thumbprint of this certificate will need to be added to your SIL Aggregator using the Set-SILAggregator –AddCertificateThumbprint cmdlet. 
This client certificate does not need to match the name of your SIL Aggregator.

#>


function New-DGMCMTraceLog{
  param (
  [Parameter(Mandatory=$true)]
  $ScriptName,
  [Parameter(Mandatory=$true)]
  $LogFile,
  [Parameter(Mandatory=$true)]
  $ScriptFile,
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  $VerboseLogging = "true"
  [bool]$Verbose = [System.Convert]::ToBoolean($VerboseLogging)
  $MaxLogSizeInKB = 10240
  $ScriptStatus = 'Success'

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($ScriptStatus -ne 'Error')) { $ScriptStatus = $type }
  if ($type -eq 'Error') { $ScriptStatus = $type }

  if ((Get-Item $LogFile -ErrorAction SilentlyContinue).Length/1KB -gt $MaxLogSizeInKB)
  {
    $log = $LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

function Set-DGMSILLoggingServer{
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$pfxcertificateserverpath,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$pfxcertificatename,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$pdxcertificatethumbprint,
        [Parameter(Position=3,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$pfxcertificatelocalpath,
        [Parameter(Position=4,Mandatory=$true,ValueFromPipeline=$true)]
        [String]$SILAggregatorURI,
        [Parameter(Position=5,Mandatory=$false,ValueFromPipeline=$true)]
        $pfxcertificatelocalpathsercurepassword,
        [Parameter(Position=6,Mandatory=$false,ValueFromPipeline=$true)]
        [String]$pfxcertificatelocalpathusername,
        [Parameter(Position=7,Mandatory=$true,ValueFromPipeline=$true)]
        $pfxcertificatesecurepassword,
        [Parameter(Position=8,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("Install","Uninstall")]
        [String]$InstallationType
    )

    #Set Logging Varibales
    $invocation = (Get-Variable MyInvocation -Scope 1).Value
    #$ScriptDirectory = Split-Path $invocation.MyCommand.Path
    $ScriptDirectory = "c:\admin"
    $ScriptName = ($MyInvocation.MyCommand.Name)+".psm1"
    $LogName = ($MyInvocation.MyCommand.Name)+".log"
    $LogFile = Join-Path $ScriptDirectory $LogName
    $ScriptFile = Join-Path $ScriptDirectory $ScriptName
    $ReportDate = Get-Date 
    $WrapperScriptPath = $MyInvocation.PSCommandPath

    $errorcount = 0
    if ($InstallationType -eq "Install"){

        try{
            #Map Drive to Client PFX Certificate Path
            New-DGMCMTraceLog -message ("Mapping Drive to Client PFX Certificate Path...") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
        
            $driveLetters = ([int][char]'C')..([int][char]'Z') | % {[char]$_}
            $occupiedDriveLetters = Get-Volume | % DriveLetter
            $availableDriveLetters = $driveLetters | ? {$occupiedDriveLetters -notcontains $_}
            $firstAvailableDriveLetter = $availableDriveLetters[0]

            if ($pfxcertificatelocalpathusername -ne $null -and $pfxcertificatelocalpathsercurepassword -ne $null){
                $mycreds = New-Object System.Management.Automation.PSCredential ($pfxcertificatelocalpathusername, $pfxcertificatelocalpathsercurepassword)
                New-PSDrive -Name $firstAvailableDriveLetter -PSProvider filesystem -root $pfxcertificateserverpath -credential $mycreds  | Out-Null
            }else{
                New-PSDrive -Name $firstAvailableDriveLetter -PSProvider filesystem -root $pfxcertificateserverpath  | Out-Null
            }
        }catch{
            New-DGMCMTraceLog -message ("Could Not Map Drive to Client PFX Certificate Path.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            $errorcount++
        }

        try{
            #Copy Client PFX Certificate to Local Path
            New-DGMCMTraceLog -message ("Copying Client PFX Certificate to Local Path...") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

            Copy-Item ${firstAvailableDriveLetter}:\$pfxcertificatename $pfxcertificatelocalpath
            Remove-PSDrive –Name $firstAvailableDriveLetter
        }catch{
            New-DGMCMTraceLog -message ("Could Not Copy Client PFX Certificate to Local Path.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            $errorcount++
        }

        try{
            #Import Client PFX Certificate To Local Store
            New-DGMCMTraceLog -message ("Importing Client PFX Certificate To Local Store...") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

            Import-PfxCertificate -FilePath $pfxcertificatelocalpath\$pfxcertificatename cert:\localMachine\my -Password $pfxcertificatesecurepassword | Out-Null
        }catch{
            New-DGMCMTraceLog -message ("Could not Import Client PFX Certificate To Local Store.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            $errorcount++
        }

        try{
            #Set SIL Logging
            New-DGMCMTraceLog -message ("Setting SIL Logging...") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

            Set-sillogging –targeturi $SILAggregatorURI –certificatethumbprint $pdxcertificatethumbprint
        }catch{
            New-DGMCMTraceLog -message ("Could not set SIL Logging.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            $errorcount++
        }
        try{
            #Start SIL Logging
            New-DGMCMTraceLog -message ("Starting SIL Logging...") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

            Start-SilLogging
        }catch{
            New-DGMCMTraceLog -message ("Could not Start SIL Logging.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            $errorcount++
        }

        if ($errorcount -le 0){
            Set-DGMRegistryInstallValue -InstallationType Install
            New-DGMCMTraceLog -message ("The Set-DGMSILLoggingServer has been installed.") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
        }else{
            New-DGMCMTraceLog -message ("The Set-DGMSILLoggingServer could not be installed. $errorcount error(s) were found.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            Write-Host "This Window Will Close Automatically in 10 Seconds. A log of these errors can be found at $ScriptFile."
            Start-Sleep -Seconds 10
        }
    }
    elseif ($InstallationType -eq "Uninstall"){
        try{
            #Start SIL Logging
            New-DGMCMTraceLog -message ("Stopping SIL Logging...") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile

            Stop-SilLogging
        }catch{
            New-DGMCMTraceLog -message ("Could not Stop SIL Logging.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            $errorcount++
        }
        if ($errorcount -le 0){
            Set-DGMRegistryInstallValue -InstallationType Uninstall
            
            New-DGMCMTraceLog -message ("The Set-DGMSILLoggingServer has been uninstalled.") -component "Main()" -type 1 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
        }else{
            New-DGMCMTraceLog -message ("The Set-DGMSILLoggingServer could not be uninstalled. $errorcount error(s) were found.") -component "Main()" -type 3 -ScriptName $ScriptName -LogFile $LogFile -ScriptFile $ScriptFile
            Write-Host "This Window Will Close Automatically in 10 Seconds. A log of these errors can be found at $ScriptFile."
            Start-Sleep -Seconds 10
        }
    }

}

function Set-DGMRegistryInstallValue{
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("Install","Uninstall")]
        [String]$InstallationType
    )

    #Set The Success Bit for SCCM Application Detection
    $Path = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Set-DGMSILLoggingServer"
    $Name = "IsInstalled"
    $Name2 = "Version"
    $Value2 = "1"

    if(!(Test-Path $Path)){New-Item -Path $Path -Force | Out-Null}

    if ($InstallationType -eq "Install"){
        $Value = "1"
        New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null
        New-ItemProperty -Path $Path -Name $Name2 -Value $Value2 -PropertyType DWORD -Force | Out-Null
        
    }elseif ($InstallationType -eq "Uninstall"){
        $Value = "0"
        New-ItemProperty -Path $Path -Name $Name -Value $Value -PropertyType DWORD -Force | Out-Null
        New-ItemProperty -Path $Path -Name $Name2 -Value $Value2 -PropertyType DWORD -Force | Out-Null
    }
}

function Invoke-DGMSILLoggingServer{
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateSet("Install","Uninstall")]
        [String]$InstallationType
    )
    #Set DGM Variables
    $pfxcertificateserverpath = ".\"
    $pfxcertificatename = "SILClient2018.pfx"

    #Set Key Variables
    $AESKeyFile = "AESKeyFile"
    $AESKeyFile = "AESKeyFile"

    #Client PFX Certificate Thumbprint and added to your SIL Aggregator using Set-SilAggregator -AddCertificateThumbprint
    #For example, the thumbprint "a9 09 50 2d d8 2a e4 14 33 e6 f8 38 86 b0 0d 42 77 a3 2a 7b" should be specified as "a909502dd82ae41433e6f83886b00d4277a32a7b"
    $pdxcertificatethumbprint = "‎a909502dd82ae41433e6f83886b00d4277a32a7b"

    $pfxcertificatelocalpath = "c:\admin"
    $SILAggregatorURI = "https://VCOMSIL001PRD"

    #Get Credentials to Client PFX Certificate Path
    #$Cred = Get-Credential -Message "Enter DGM Account With Permissions to Client PFX File Path"
    #$pfxcertificatelocalpathsercurepassword = $Cred.Password
    #$pfxcertificatelocalpathusername = $Cred.UserName

    #Get Credentials for Client PFX Certificate Password
    #$cred2 = Get-Credential -Message "Enter Password for PFX Certificate. You Can Make Up a UserName"
    #$pfxcertificatesecurepassword = $cred2.Password


    $pfxcertificatesecurepassword = Get-DGMAESKey -Path $pfxcertificateserverpath -credentialFile credentialFile -AESKeyFile AESKeyFile

    Set-DGMSILLoggingServer `
        -pfxcertificateserverpath $pfxcertificateserverpath `
        -pfxcertificatename $pfxcertificatename `
        -pdxcertificatethumbprint $pdxcertificatethumbprint `
        -pfxcertificatelocalpath $pfxcertificatelocalpath `
        -SILAggregatorURI $SILAggregatorURI `
        -pfxcertificatesecurepassword $pfxcertificatesecurepassword `
        -InstallationType $InstallationType

        #-pfxcertificatelocalpathsercurepassword $pfxcertificatelocalpathsercurepassword `
        #-pfxcertificatelocalpathusername $pfxcertificatelocalpathusername `
}

function Set-DGMAESKey{
    
    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        $Path,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        $credentialFile,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
        $AESKeyFile,
        [Parameter(Position=3,Mandatory=$true,ValueFromPipeline=$true)]
        $passwordSecureString
        
    )

    $credentialFilePath = Join-Path $Path $credentialFile
    $AESKeyFilePath = Join-Path $Path $AESKeyFile

    # Generate a random AES Encryption Key.
    Write-Host "Generating a random AES Encryption Key..."
    $AESKey = New-Object Byte[] 32
    [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($AESKey)
	
    # Store the AESKey into a file. This file should be protected!  (e.g. ACL on the file to allow only select people to read)
    Write-Host "Storing the AESKey into $AESKeyFilePath. This file should be protected!..."
    Set-Content $AESKeyFilePath $AESKey   # Any existing AES Key file will be overwritten		

    # Store the Credentials into a file
    Write-Host "Storing the Credential File into $credentialFilePath..."
    $password = $passwordSecureString | ConvertFrom-SecureString -Key $AESKey
    Add-Content $credentialFilePath $password

}

function Get-DGMAESKey{

    param(
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        $Path,
        [Parameter(Position=1,Mandatory=$true,ValueFromPipeline=$true)]
        $credentialFile,
        [Parameter(Position=2,Mandatory=$true,ValueFromPipeline=$true)]
        $AESKeyFile
        
    )

    #To get a new key, run such as:
    #$cred = Get-Credential
    #New-DGMAESKey -Path .\ -credentialFile credentialFile -AESKeyFile AESKeyFile -passwordSecureString $cred.Password

    $AESKeyFilePath = Join-Path $Path $AESKeyFile
    $credentialFilePath = Join-Path $Path $credentialFile

    #Retreive AES Encryption Key
    Write-Host "Retrieving AES Encryption Key from $AESKeyFilePath..."
    $AESKey = Get-Content $AESKeyFilePath
    
    #Retreive Credential File
    Write-Host "Retrieving AES Credential File from $credentialFilePath..."
    $pwdTxt = Get-Content $credentialFilePath

    #Convert and return this a secure string
    Write-Host "ConvertTo-SecureString with  $credentialFile and $AESKeyFile..."
    $securePwd = $pwdTxt | ConvertTo-SecureString -Key $AESKey

    Return $securePwd
}

Overview

I developed this tool, Fix-DMGSCCMWUAPolicy.ps1,to help you mitigate WUA Policy issues that result in error 0x80004005 on the client-side WUAHandler.log file. This tool will attempt to resolve those issues.

Troubleshooting Error 0x80004005

When you deploy software updates, you add the updates to a SUG and then deploy the SUG to your clients. When you create the deployment, the update policy is sent to client computers, and the update content files are downloaded from a DP to the local cache on the client computer. The updates are then available for installation. After the deployment and the deployment policy have been created on the server, clients receive the policy on the next policy evaluation cycle. However, group policy issues can sometimes block these policies.

Root Cause of WSUS Error 0x80004005

If you receive error 0x80004005 in your WUAHandler.log file, then Group policy is likely overriding the local policy, not allowing the workstation to be patched or updated.

Resolution to WSUS Error 0x80004005

If you have this error, a few steps need to be carried out to resolve. The below script will automate this task against remotely against a set of imported computers. If the fix is not required, the utility will pass on to the next computer in the CSV file.

The utility will create a log file which is compatible with the CMTrace.exe tool.

log_file

PowerShell Script: Fix-DMGSCCMWUAPolicy.ps1


<#
.Synopsis
   Fix the WUA Policy Locally On The Client
.DESCRIPTION
   The tool, Fix-DMGSCCMWUAPolicy.ps1 was written by David Maiolo and will automatically fix WUA Policies
.EXAMPLE
   Fix-DMGSCCMWUAPolicy -CSVFile sccm_import.csv
.EXAMPLE
   Fix-DMGSCCMWUAPolicy -Hostname WORKSTATION01
#>


function New-DGMLogFile
{
  param (
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Global:Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($Global:ScriptStatus -ne 'Error')) { $Global:ScriptStatus = $type }
  if ($type -eq 'Error') { $Global:ScriptStatus = $type }

  if ((Get-Item $Global:LogFile).Length/1KB -gt $Global:MaxLogSizeInKB)
  {
    $log = $Global:LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $Global:LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

function GetScriptDirectory
{
  $invocation = (Get-Variable MyInvocation -Scope 1).Value
  Split-Path $invocation.MyCommand.Path
} 

function Fix-DMGSCCMWUAPolicy
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 1')]
                   [ValidateScript({(Test-Path $_)})]
                   $CSVFile,
        # Param2 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 2')]
                   [ValidateScript({(Get-ADComputer -Identity $_).objectclass -eq 'computer' })]
                   [String]$Hostname
    )

    Begin
    {
        $path = (get-item -Path .).FullName 
        
        if ($CSVFile -ne $null){
            Write-Host Importing $CSVFile...
            $csv = import-csv "$CSVFile"
        }else{
            $csv = [PSCustomObject]@{
                Hostname = $Hostname}
        }
        Write-Host ==========================================
        Write-Host SCCM WUA Policy Error 0`x80070002 Fix Tool
        Write-Host ==========================================
        Write-Host "v0.1 (2017-12-20) by dmaiolo"
        New-DGMLogFile -message ("Starting Logging for Fix-DMGSCCMWUAPolicy") -component "Main()" -type 1 
    }
    Process
    {
    

    $computers = @();
        
    $csv | foreach-object {
        $h = $_.Hostname
    

        if(Test-Connection -ComputerName $h -Count 1 -Quiet){


            $g = $null
            $resultsfile = $null

            $resultsfile = "\\$h\c$\Windows\CCM\Logs\WUAHandler.log"
            #$resultsfile = "\\cs-msc\public\Temp\Noah\WUAHandler.log"
            $logLines = @()

            Write-Host $h`: Loading $resultsfile ...
            if (Test-Path $resultsfile){
                $g = Get-Content  $resultsfile -ErrorAction Stop
            }else{
                New-DGMLogFile -message ("$h`: $resultsfile Does Not Exist") -component "Main()" -type 3
            }
            
            $lastLine = $g.Length
            $WUAPolicyErrorFound = $false
            Write-Host $h`: Size of File`: $lastLine Lines

            for($i = $lastLine-1; $i -ge 0; $i--)
            {
                #if ($g[$i] -contains "*0x80004005*") { 
                #    Write-Host $g[$i]
                #    $WUAPolicyErrorFound = $true
                #    break
                $g[$i] -match '(?:.*)(0x80004005)' | out-null
                    #$logLines += ($h + ': ' + $matches[1] + ' on ' + $matches[3] + ' at ' + $matches[2])
                    if ($matches -ne $null){
                        if ($matches[1] -eq "0x80004005") { 
                            $WUAPolicyErrorFound = $true
                            break 
                        }
                    }
               
            }

            if($WUAPolicyErrorFound){
                New-DGMLogFile -message ("$h`: Unable to read existing WUA Group Policy object. 0x80004005. Fix requred.") -component "Main()" -type 2
                $sourcefile = "\\$h\c$\Windows\System32\GroupPolicy\Machine\Registry.pol"
                #$destinationfile = "\\$($h)\c$\Windows\System32\GroupPolicy\Registry_$(Get-Date -Format dd-MM-yyyy).pol"

                Write-Host "$h`: Removing $sourcefile..."
                if (Test-Path $sourcefile){
                    try{
                        Remove-Item -Path $sourcefile -Force
                        New-DGMLogFile -message ("$h`: Removed Policy File`: $sourcefile.") -component "Main()" -type 1
                    }
                    catch{
                        New-DGMLogFile -message ("$h`: Could NOT Remove Policy File`: $sourcefile.") -component "Main()" -type 3
                    }
                }else{
                    New-DGMLogFile -message ("$h`: File Does Not Exist`: $sourcefile.") -component "Main()" -type 3
                }

                Write-Host $h`: Sleeping 5 Seconds...
                sleep 5

                Write-Host "$h`: Running GPUdate `/Force..."
                Try{
                    Invoke-Command -ComputerName $h -ScriptBlock { gpupdate /force } -ErrorAction Stop
                    New-DGMLogFile -message ("$h`: GPUpdate /Force Ran Succesfully") -component "Main()" -type 1
                }
                Catch [System.Management.Automation.Remoting.PSRemotingTransportException]{
                    New-DGMLogFile -message ("$h`: GPUpdate /Force Did NOT Run Succesfully (errorcode 0x80090322 occurred while using Kerberos authentication: An unknown security error occurred.)") -component "Main()" -type 3
                    try {
                        $command = "C:\ADMIN`\PSTools\PsExec.exe \\$h /s /accepteula /nobanner cmd /c `"Winrm quickconfig /q`""
                        Invoke-Expression -Command:"$command"
                        New-DGMLogFile -message ("$h`: WinRM Repair Ran Succesfully") -component "Main()" -type 1
                        }
                    catch{
                        New-DGMLogFile -message ("$h`: WinRM Repair NOT Succesfull") -component "Main()" -type 3
                    }
                }
                Catch{
                    New-DGMLogFile -message ("$h`: GPUpdate /Force Did NOT Run Succesfully (generic error)") -component "Main()" -type 3
                }

                Write-Host $h`: Sleeping 5 Seconds...
                sleep 5

                Write-Host "$h`: Updating the SCCM Store Policy..."
                Try{
                    Invoke-Command -ComputerName $h -ScriptBlock { 
                        $strAction = "{00000000-0000-0000-0000-000000000108}"

                        $WMIPath = "\\" + $Using:h + "\root\ccm:SMS_Client" 
                        $SMSwmi = [wmiclass] $WMIPath 
                        [Void]$SMSwmi.TriggerSchedule($strAction)
                     } -ErrorAction Stop
                    New-DGMLogFile -message ("$h`: Update of the SCCM Store Policy Was Succesful") -component "Main()" -type 1
                }
                Catch [System.Management.Automation.Remoting.PSRemotingTransportException]{
                    New-DGMLogFile -message ("$h`: Update of the SCCM Store Policy Was NOT Succesful (errorcode 0x80090322 occurred while using Kerberos authentication: An unknown security error occurred.)") -component "Main()" -type 3
                }
                Catch{
                    New-DGMLogFile -message ("$h`: Update of the SCCM Store Policy Was NOT Succesful (generic error)") -component "Main()" -type 3
                }

            }else{
                New-DGMLogFile -message ("$h`: No WUA Group Policy Object Errors Found. No Fix requred.") -component "Main()" -type 1
            }

        }
        else{
            New-DGMLogFile -message ("$h`: is offline.") -component "Main()" -type 2
        }
       }
    }
    End
    {
       Write-Host ===============================================================
       Write-Host Log File of Results Generated at $Global:LogFile VIEW WITH CMTRACE.EXE
       New-DGMLogFile -message ("Ending Logging for Fix-DMGSCCMWUAPolicy") -component "Main()" -type 1
    }
}

$VerboseLogging = "true"
[bool]$Global:Verbose = [System.Convert]::ToBoolean($VerboseLogging)
#$Global:LogFile = Join-Path (GetScriptDirectory) "Fix-DMGSCCMWUAPolicy_$(Get-Date -Format dd-MM-yyyy).log"
$Global:LogFile = Join-Path (GetScriptDirectory) "Fix-DMGSCCMWUAPolicy.log"
$Global:MaxLogSizeInKB = 10240
$Global:ScriptName = 'Fix-DMGSCCMWUAPolicy.ps1' 
$Global:ScriptStatus = 'Success'

Overview

I developed this tool, Fix-DMGSCCMStateMessage.ps1, to assit in troubleshooting SCCM “In Progress” and State Message Communication issues. If the UpdateStore.log shows that a particular windows update component is installed, but it is still in progress in the SCCM console, the State Message is likely not communicating properly to the SQL server. State messaging is a mechanism in SCCM which replicates point in time conditions on the client.

Fix-DGMSCCMStateMessage Tool

The tool will automatically update the State Message locally on the SCCM client by invoking the following two commands:


$SCCMUpdatesStore = New-Object -ComObject Microsoft.CCM.UpdatesStore
$SCCMUpdatesStore.RefreshServerComplianceState()

The tool requires the –csvfile argument, which is the path to a .CSV file containing one column, Hostname, with the computer hostnames listed in the column and can be run as in the example below.

Fix-DGMSCCMStateMessage Log File

The utility will create a log file that is compatible with the CMTrace tool, which includes the thread, time, state and component for each process.


<#
.Synopsis
   Update the State Message locally on the SCCM client
.DESCRIPTION
   The tool, Fix-DMGSCCMStateMessage.ps1 was written by David Maiolo which will automatically update the State Message locally on the SCCM client by 
   invoking the following two commands: $SCCMUpdatesStore = New-Object -ComObject Microsoft.CCM.UpdatesStore and $SCCMUpdatesStore.RefreshServerComplianceState()
.EXAMPLE
   Fix-DMGSCCMStateMessage -CSVFile state_message_import.csv
.EXAMPLE
   Fix-DMGSCCMStateMessage -Hostname WORKSTATION01
#>


function New-DGMLogFile
{
  param (
  [Parameter(Mandatory=$true)]
  $message,
  [Parameter(Mandatory=$true)]
  $component,
  [Parameter(Mandatory=$true)]
  $type )

  switch ($type)
  {
    1 { $type = "Info" }
    2 { $type = "Warning" }
    3 { $type = "Error" }
    4 { $type = "Verbose" }
  }

  if (($type -eq "Verbose") -and ($Global:Verbose))
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    Write-Host $message
  }
  elseif ($type -ne "Verbose")
  {
    $toLog = "{0} `$$<{1}><{2} {3}>" -f ($type + ":" + $message), ($Global:ScriptName + ":" + $component), (Get-Date -Format "MM-dd-yyyy"), (Get-Date -Format "HH:mm:ss.ffffff"), $pid
    $toLog | Out-File -Append -Encoding UTF8 -FilePath ("filesystem::{0}" -f $Global:LogFile)
    if ($type -eq 'Info') { Write-Host $message }
    if ($type -eq 'Warning') { Write-Host $message -ForegroundColor Yellow}
    if ($type -eq 'Error') { Write-Host $message -ForegroundColor Red}
    

  }
  if (($type -eq 'Warning') -and ($Global:ScriptStatus -ne 'Error')) { $Global:ScriptStatus = $type }
  if ($type -eq 'Error') { $Global:ScriptStatus = $type }

  if ((Get-Item $Global:LogFile).Length/1KB -gt $Global:MaxLogSizeInKB)
  {
    $log = $Global:LogFile
    Remove-Item ($log.Replace(".log", ".lo_"))
    Rename-Item $Global:LogFile ($log.Replace(".log", ".lo_")) -Force
  }
} 

function GetScriptDirectory
{
  $invocation = (Get-Variable MyInvocation -Scope 1).Value
  Split-Path $invocation.MyCommand.Path
} 

function GetStringBetweenTwoStrings($firstString, $secondString, $importString){

    #Get content from file
    $file = $importString

    #Regex pattern to compare two strings
    $pattern = "$firstString(.*?)$secondString"

    #Perform the opperation
    $result = [regex]::Match($file,$pattern).Groups[1].Value

    #Return result
    return $result

}

function Fix-DMGSCCMStateMessage
{
    [CmdletBinding()]
    [Alias()]
    [OutputType([int])]
    Param
    (
        # Param1 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 1')]
                   [ValidateScript({(Test-Path $_)})]
                   $CSVFile,
        # Param2 help description
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$true,
                   Position=0,
                   ParameterSetName='Parameter Set 2')]
                   [ValidateScript({(Get-ADComputer -Identity $_).objectclass -eq 'computer' })]
                   [String]$Hostname
    )

    Begin
    {
        $path = (get-item -Path .).FullName 
        
        if ($CSVFile -ne $null){
            Write-Host Importing $CSVFile...
            $csv = import-csv "$CSVFile"
        }else{
            $csv = [PSCustomObject]@{
                Hostname = $Hostname}
        }
        Write-Host =========================================
        Write-Host SCCM State Message Update Tool
        Write-Host =========================================
        Write-Host "dmaiolo"
        New-DGMLogFile -message ("Starting Logging for Fix-DMGSCCMStateMessage") -component "Main()" -type 1 
    }
    Process
    {
    

    $computers = @();
        
    $csv | foreach-object {
        $h = $_.Hostname
    
        if(Test-Connection -ComputerName $h -Count 1 -ErrorAction SilentlyContinue){

            Try{
               
                $g = $null               
                Invoke-Command -ComputerName $h -ScriptBlock { $SCCMUpdatesStore = New-Object -ComObject Microsoft.CCM.UpdatesStore
                                                               $SCCMUpdatesStore.RefreshServerComplianceState() } -ErrorAction Stop 
                New-DGMLogFile -message ("$h`: State Message Updated Succesfully (RefreshServerComplianceState())") -component "Main()" -type 1
                Write-Host $h`: Sleeping 5 Seconds...
                sleep 5
                $resultsfile = "\\$h\c$\Windows\CCM\Logs\UpdatesStore.log"

                $logLines = @()

                $g = Get-Content  $resultsfile -ErrorAction Stop
                $lastLine = $g.Length
                for($i = $lastLine-1; $i -ge 0; $i--)
                {
                    $g[$i] -match '<\!\[LOG\[(.*?)\]LOG.*