Run ConfigMgr Client Cycle Actions on Multiple Clients with PowerShell

Not a script that hasn’t been done before, but this is my version which allows pipeline input of the computer name so that you can run one of the ConfigMgr Client Cycle actions on multiple computers, or include it as a function in a script etc.

The following actions are supported and are equivalent to running a cycle from the ‘Actions’ tab of the ConfigMgr Client Control Panel:

Application Deployment Evaluation Cycle
Discovery Data Collection Cycle
File Collection Cycle
Hardware Inventory Cycle
Machine Policy Retrieval and Evaluation Cycle
Software Inventory Cycle
Software Metering Usage Report Cycle
Software Updates Deployment Evaluation Cycle
Software Updates Scan Cycle
User Policy Retrieval and Evaluation Cycle
Windows Installer Source List Update Cycle

Some examples:

Invoke-CMClientCycle -ComputerName PC001 -Cycle Application_Deployment_Evaluation

Triggers the “Application Deployment Evaluation Cycle” on PC001 and only returns output for errors.

Get-ADComputer -filter * -searchbase "OU=Desktops,OU=England,OU=Computers,OU=United Kingdom,dc=contoso,dc=com" |
Sort Name |
Select -ExpandProperty Name |
Invoke-CMClientCycle -Cycle Machine_Policy_Retrieval_and_Evaluation -Verbose

Gets the list of desktops in the England OU and triggers the “Machine Policy Retrieval and Evaluation cycle” on each machine. Verbose output details each machine where the action
was triggered.

(Get-ADComputer -filter * -searchbase "OU=Desktops,OU=England,OU=Computers,OU=United Kingdom,dc=contoso,dc=com" |
Sort Name |
Select -ExpandProperty Name).ForEach({Invoke-CMClientCycle -ComputerName $PSItem -Cycle Machine_Policy_Retrieval_and_Evaluation -Verbose})

Gets the list of desktops in the England OU and triggers the “Machine Policy Retrieval and Evaluation cycle” on each machine. Verbose output details each machine where the action
was triggered. Uses the ‘ForEach’ method in PowerShell 4.0.

The Script


<#

.SYNOPSIS
    Triggers a ConfigMgr Client Cycle Action on a remote computer

.DESCRIPTION
    Triggers a ConfigMgr Client cycle action on a remote computer. Accepts pipeline input for computer name so can be run against many machines or in a script.

.PARAMETER ComputerName
    The name of the target computer

.PARAMETER Cycle
    The cycle to run.  Choose from the list.

.EXAMPLE
    Invoke-CMClientCycle -ComputerName PC001 -Cycle Application_Deployment_Evaluation

    Triggers the "Application Deployment Evaluation Cycle" on PC001 and only returns output for errors.

.EXAMPLE
    Get-ADComputer -filter * -searchbase "OU=Desktops,OU=England,OU=Computers,OU=United Kingdom,dc=contoso,dc=com" | 
        Sort Name | 
        Select -ExpandProperty Name | 
        Invoke-CMClientCycle -Cycle Machine_Policy_Retrieval_and_Evaluation -Verbose

    Gets the list of desktops in the England OU and triggers the "Machine Policy Retrieval and Evaluation cycle" on each machine.  Verbose output details each machine where the action 
    was triggered.

.EXAMPLE
    (Get-ADComputer -filter * -searchbase "OU=Desktops,OU=England,OU=Computers,OU=United Kingdom,dc=contoso,dc=com" | 
        Sort Name | 
        Select -ExpandProperty Name).ForEach({Invoke-CMClientCycle -ComputerName $PSItem -Cycle Machine_Policy_Retrieval_and_Evaluation -Verbose})

    Gets the list of desktops in the England OU and triggers the "Machine Policy Retrieval and Evaluation cycle" on each machine.  Verbose output details each machine where the action 
    was triggered.  Uses the 'ForEach' method in PowerShell 4.0.

.NOTES
    Cmdlet name: Invoke-CMClientCycle
    Author:      Trevor Jones
    Contact:     @trevor_smsagent
    DateCreated: 2015-05-26
    Link:        https://smsagent.wordpress.com

#>


[CmdletBinding(SupportsShouldProcess=$True)]
    param
        (
        [Parameter(Mandatory=$True, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, HelpMessage="The target computer name")]
            [ValidateScript({if(!(Test-Connection -Quiet -Count 2 -ComputerName $PSItem)) {Write-warning "$psItem is not contactable.  A validation error will be returned."} else{$true}})]
            [string[]]$ComputerName,
        [Parameter(Mandatory=$True)]
            [ValidateSet("Application_Deployment_Evaluation",
                "Discovery_Data_Collection",
                "File_Collection",
                "Hardware_Inventory",
                "Machine_Policy_Retrieval_and_Evaluation",
                "Software_Inventory",
                "Software_Metering_Usage_Report",
                "Software_Updates_Deployment_Evaluation",
                "Software_Updates_Scan",
                "User_Policy_Retrieval_and_Evaluation",
                "Windows_Installer_Source_List_Update")]
            [string]$Cycle
        )


# Set variables based on choice
if ($cycle -eq "Application_Deployment_Evaluation")
    {
        $strAction = "{00000000-0000-0000-0000-000000000121}"
        $actionDesc = "Application Deployment Evaluation Cycle"
    }
if ($cycle -eq "Discovery_Data_Collection")
    {
        $strAction = "{00000000-0000-0000-0000-000000000003}"
        $actionDesc = "Discovery Data Collection Cycle"
    }
if ($cycle -eq "File_Collection")
    {
        $strAction = "{00000000-0000-0000-0000-000000000104}"
        $actionDesc = "File Collection Cycle"
    }
if ($cycle -eq "Hardware_Inventory")
    {
        $strAction = "{00000000-0000-0000-0000-000000000001}"
        $actionDesc = "Hardware Inventory Cycle"
    }
if ($cycle -eq "Machine_Policy_Retrieval_and_Evaluation")
    {
        $strAction = "{00000000-0000-0000-0000-000000000021}"
        $actionDesc = "Machine Policy Retrieval and Evaluation Cycle"
    }
if ($cycle -eq "Software_Inventory")
    {
        $strAction = "{00000000-0000-0000-0000-000000000002}"
        $actionDesc = "Software Inventory Cycle"
    }
if ($cycle -eq "Software_Metering_Usage_Report")
    {
        $strAction = "{00000000-0000-0000-0000-000000000031}"
        $actionDesc = "Software Metering Usage Report Cycle"
    }
if ($cycle -eq "Software_Updates_Deployment_Evaluation")
    {
        $strAction = "{00000000-0000-0000-0000-000000000108}"
        $actionDesc = "Software Updates Deployment Evaluation Cycle"
    }
if ($cycle -eq "Software_Updates_Scan")
    {
        $strAction = "{00000000-0000-0000-0000-000000000113}"
        $actionDesc = "Software Updates Scan Cycle"
    }
if ($cycle -eq "User_Policy_Retrieval_and_Evaluation")
    {
        $strAction = "{00000000-0000-0000-0000-000000000026}"
        $actionDesc = "User Policy Retrieval & Evaluation Cycle"
    }
if ($cycle -eq "Windows_Installer_Source_List_Update")
    {
        $strAction = "{00000000-0000-0000-0000-000000000032}"
        $actionDesc = "Windows Installer Source List Update Cycle"
    }

if ($cycle -eq "User_Policy_Retrieval_and_Evaluation")
    {
        # Get UserSID
            try
                {
                    Write-verbose "Triggering the $actionDesc on $ComputerName"
                    $userSID = (get-wmiobject -computerName $ComputerName -query "SELECT UserSID FROM CCM_UserLogonEvents WHERE LogoffTime = NULL" -namespace "ROOT\ccm" -ErrorAction Stop).UserSID
                    $userSID = $userSID.Replace("-","_")
                }
            catch {write-host "$ComputerName`: $_" -ForegroundColor Red}

        # Change User Policy request trigger to 1 minute (temporarily)
            try
                {
                    $a = Get-WmiObject -ComputerName $ComputerName -Namespace "root\ccm\Policy\$userSID\ActualConfig" -Class CCM_Scheduler_ScheduledMessage -ErrorAction Stop | Where-Object {$_.ScheduledMessageID -eq "{00000000-0000-0000-0000-000000000026}"}
                    Set-WmiInstance -Path $a -Arguments @{Triggers=@("SimpleInterval;Minutes=1;MaxRandomDelayMinutes=0")} -Verbose -ErrorAction Stop | Out-Null
                    $a.Put() | Out-Null
                }
            catch {write-host "$ComputerName`: $_" -ForegroundColor Red}
    }
Else
    {
        # Trigger the action
            try
                {
                    Write-verbose "Triggering the $actionDesc on $ComputerName"
                    Invoke-WmiMethod -ComputerName $ComputerName -Namespace root\ccm -Class SMS_Client -Name TriggerSchedule -ArgumentList $strAction -ErrorAction Stop | Out-Null
                }
            catch {write-host "$ComputerName`: $_" -ForegroundColor Red}
    }

Checking for Failed Package Distributions in ConfigMgr with PowerShell

Recently when I checked the status of my Content distributions in the Monitoring node of the ConfigMgr console, I noticed some where the compliance was not reporting as 100%.  This means there was a failure in the distribution which needs to be addressed, usually by redistributing. However, ConfigMgr does not notify you of such distributions failures, which means you have to check in the console, or the status messages or the log files to find out.  It will retry a distribution up to 100 times, but after that I guess it just remains in a failed state.

So I wrote a Powershell script that will check for failed distributions, and give me various info such as the package name, type and ID, the distribution point where the failure occurred and the reason for the failure.  It can be run in a PS console window, but can also send results by email, which makes it useful to be run regularly as a scheduled task, so you can monitor for distribution failures and take the necessary action to remedy them.

Here’s an example:

1

After running the script I can see that there are some distributions where the content hash failed to be validated, not an uncommon error.  A redistribution should fix that.  There is also an OS Image package that looks like it couldn’t talk to the distribution point, and given the date it’s given up trying.

If I choose the -SendEmail switch, it will send me a nice (?) neon-green HTML report 🙂

2

Even though it is designed for failed distributions, it will also report on distributions that are active, basically anything not yet in the successful state, so you could use it to view the progress of current distributions too.

For example, this time using the -GridView switch, I can see some Software Update Packages that are currently being distributed, and some that failed.  Since the failures are very recent, ConfigMgr will try to redistribute those packages.  If they remain in the failed state over time, then it needs investigating.

4

Configure the Script

All parameters in the script are non-mandatory, so set the defaults if you don’t want to enter them each time:

$SiteCode – Your ConfigMgr Site Code

$SiteServer – Your ConfigMgr Site Server

$From – the From email address

$To – the To email address

$Smtpserver – Your Smtp server name

$Subject – The email subject

The Script

<#

.SYNOPSIS
    Checks for failed or incomplete package distributions in ConfigMgr

.DESCRIPTION
    This script checks for any package distributions that are not in the successful state, and return the results either into the console window or by email in an HTML table.
    The following fields are returned:
    - Package Name
    - PackageID
    - Package Type
    - Distribution Point Name
    - Distribution State
    - Status Message
    - Last Update Time
    It can be run as a scheduled task to monitor for failed distributions.  Data is retrieved from WMI on the Site Server.

.PARAMETER SiteCode
    Your ConfigMgr SiteCode

.PARAMETER SiteServer
    Your ConfigMgr Site Server

.PARAMETER SendEmail
    Use this switch to send results by email.  Console output is the default.

.PARAMETER GridView
    Use this switch to output results to Powershell's GridView. Console output is the default.

.PARAMETER From
    If sending email, the From address

.PARAMETER To
    If sending email, the To address

.PARAMETER SmtpServer
    If sending email, the Smtp server name

.PARAMETER Subject
    If sending email, the email subject

.EXAMPLE
    .\Get-CMFailedDistributions.ps1
    Checks for unsuccesful package distributions and returns the results to the console window.

.EXAMPLE
    .\Get-CMFailedDistributions.ps1 -SendEmail
    Checks for unsuccesful package distributions and sends the results by email using the default parameters.

.EXAMPLE
    .\Get-CMFailedDistributions.ps1 -SiteCode XYZ -SiteServer sccmserver02 -SendEmail -To bill.gates@microsoft.com
    Checks for unsuccesful package distributions and sends the results by email using specified parameters.

.NOTES
    Script name: Get-CMFailedDistributions.ps1
    Author:      Trevor Jones
    Contact:     @trevor_smsagent
    DateCreated: 2015-05-12
    Link:        https://smsagent.wordpress.com
    Credits:     MessageIDs from Dan Richings http://www.danrichings.com/?p=166

#>

[CmdletBinding()]
    param
        (
        [Parameter(Mandatory=$False)]
            [string]$SiteCode = "ABC",
        [Parameter(Mandatory=$False)]
            [string]$SiteServer = "sccmserver-01",
        [parameter(Mandatory=$False)]
            [switch]$SendEmail,
        [parameter(Mandatory=$False)]
            [switch]$GridView,
        [parameter(Mandatory=$False)]
            [string]$From = "ConfigMgr@contoso.com",
        [parameter(Mandatory=$False)]
            [string]$To = "ConfigMgr_Admins@contoso.com",
        [parameter(Mandatory=$False)]
            [string]$SmtpServer = "mysmtpserver",
        [parameter(Mandatory=$False)]
            [string]$Subject = "ConfigMgr Failed Distributions"
        )

#region Functions
function New-Table (
$Title,
$Topic1,
$Topic2,
$Topic3,
$Topic4,
$Topic5,
$Topic6,
$Topic7
)
    {
       Add-Content $msgfile "<style>table {border-collapse: collapse;font-family: ""Trebuchet MS"", Arial, Helvetica, sans-serif;}"
       Add-Content $msgfile "h2 {font-family: ""Trebuchet MS"", Arial, Helvetica, sans-serif;}"
       Add-Content $msgfile "th, td {font-size: 1em;border: 1px solid #1FE093;padding: 3px 7px 2px 7px;}"
       Add-Content $msgfile "th {font-size: 1.2em;text-align: left;padding-top: 5px;padding-bottom: 4px;background-color: #1FE093;color: #ffffff;}</style>"
       Add-Content $msgfile "<h2>$Title</h2>"
       Add-Content $msgfile "<p><table>"
       Add-Content $msgfile "<tr><th>$Topic1</th><th>$Topic2</th><th>$Topic3</th><th>$Topic4</th><th>$Topic5</th><th>$Topic6</th><th>$Topic7</th></tr>"
    }
function New-TableRow (
$col1,
$col2,
$col3,
$col4,
$col5,
$col6,
$col7
)
    {
        Add-Content $msgfile "<tr><td>$col1</td><td>$col2</td><td>$col3</td><td>$col4</td><td>$col5</td><td>$col6</td><td>$col7</td></tr>"
    }
function New-TableEnd {
    Add-Content $msgfile "</table></p>"}
#endregion

# Set WQL Query
$packages = @()
$Query = "
Select PackageID,Name,MessageState,ObjectTypeID,MessageID,LastUpdateDate
from SMS_DistributionDPStatus
where MessageState<>1
order by LastUpdateDate Desc
"

# Get unsuccessful distributions
$Distributions = Get-WmiObject -ComputerName $SiteServer -Namespace root\SMS\site_$SiteCode -Query $Query
foreach ($Distribution in $Distributions)
    {
        # Translate Package Type
        if ($Distribution.ObjectTypeID -eq "2")
            {$Type = "Standard Package"}
        if ($Distribution.ObjectTypeID -eq "14")
            {$Type = "OS Install Package"}
        if ($Distribution.ObjectTypeID -eq "18")
            {$Type = "OS Image Package"}
        if ($Distribution.ObjectTypeID -eq "19")
            {$Type = "Boot Image Package"}
        if ($Distribution.ObjectTypeID -eq "21")
            {$Type = "Device Setting Package"}
        if ($Distribution.ObjectTypeID -eq "23")
            {$Type = "Driver Package"}
        if ($Distribution.ObjectTypeID -eq "24")
            {$Type = "Software Updates Package"}
        if ($Distribution.ObjectTypeID -eq "31")
            {$Type = "Application Content Package"}

        # Translate Distribution State
        if ($Distribution.MessageState -eq "2")
            {$Status = "In Progress"}
        if ($Distribution.MessageState -eq "3")
            {$Status = "Error"}
        if ($Distribution.MessageState -eq "4")
            {$Status = "Failed"}

        # Translate Common MessageIDs
        if ($Distribution.MessageID -eq "2303")
            {$MSGID = "Content was successfully refreshed"}
        if ($Distribution.MessageID -eq "2324")
            {$MSGID = "Failed to access or create the content share"}
        if ($Distribution.MessageID -eq "2330")
            {$MSGID = "Content was distributed to distribution point"}
        if ($Distribution.MessageID -eq "2384")
            {$MSGID = "Content hash has been successfully verified"}
        if ($Distribution.MessageID -eq "2323")
            {$MSGID = "Failed to initialize NAL"}
        if ($Distribution.MessageID -eq "2354")
            {$MSGID = "Failed to validate content status file"}
        if ($Distribution.MessageID -eq "2357")
            {$MSGID = "Content transfer manager was instructed to send content to the distribution point"}
        if ($Distribution.MessageID -eq "2360")
            {$MSGID = "Status message 2360 unknown"}
        if ($Distribution.MessageID -eq "2370")
            {$MSGID = "Failed to install distribution point"}
        if ($Distribution.MessageID -eq "2371")
            {$MSGID = "Waiting for prestaged content"}
        if ($Distribution.MessageID -eq "2372")
            {$MSGID = "Waiting for content"}
        if ($Distribution.MessageID -eq "2375")
            {$MSGID = "Created virtual directories on the defined share or volume on the distribution point succesfully"}
        if ($Distribution.MessageID -eq "2380")
            {$MSGID = "Content evaluation has started"}
        if ($Distribution.MessageID -eq "2381")
            {$MSGID = "An evaluation task is running. Content was added to the queue"}
        if ($Distribution.MessageID -eq "2382")
            {$MSGID = "Content hash is invalid"}
        if ($Distribution.MessageID -eq "2383")
            {$MSGID = "Failed to validate the package on the distribution point. The package may not be present or may be corrupt. Please redistribute it"}
        if ($Distribution.MessageID -eq "2384")
            {$MSGID = "Package has been successfully verified on the distribution point"}
        if ($Distribution.MessageID -eq "2388")
            {$MSGID = "Failed to retrieve the package list on the distribution point. Or the package list in content library doesn't match the one in WMI. Review smsdpmon.log for more information about this failure."}
        if ($Distribution.MessageID -eq "2391")
            {$MSGID = "Failed to connect to remote distribution point"}
        if ($Distribution.MessageID -eq "2397")
            {$MSGID = "Detail will be available after the server finishes processing the messages."}
        if ($Distribution.MessageID -eq "2398")
            {$MSGID = "Content Status not found"}
        if ($Distribution.MessageID -eq "2399")
            {$MSGID = "Successfully completed the installation or upgrade of the distribution point"}
        if ($Distribution.MessageID -eq "8203")
            {$MSGID = "Failed to update package"}
        if ($Distribution.MessageID -eq "8204")
            {$MSGID = "Content is being distributed to the distribution point"}
        if ($Distribution.MessageID -eq "8211")
            {$MSGID = "Package Transfer Manager failed to update the package on the distribution point. Review PkgXferMgr.log for more information about this failure."}

        # Get / Set Additional info
        $PKGID = $Distribution.PackageID
        $LastUPDTime = [System.Management.ManagementDateTimeconverter]::ToDateTime($Distribution.LastUpdateDate)
        $PKG = Get-WmiObject -ComputerName $SiteServer -Namespace root\SMS\site_$SiteCode -Query "Select * from SMS_PackageBaseclass where PackageID = '$PKGID'"

        # Add to a PS object
        $Package = New-Object psobject
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Package Name" -Value $PKG.Name
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "PackageID" -Value $Distribution.PackageID
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Package Type" -Value $Type
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Distribution Point Name" -Value $Distribution.Name
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Distribution State" -Value $Status
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Status Message" -Value $MSGID
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Last Update Time" -Value $LastUPDTime
        $packages += $package
    }

if ($SendEmail)
    {
        # Create Tempfile to store data
        $msgfile = "$env:TEMP\mailmessage.txt"
        New-Item $msgfile -ItemType file -Force | Out-Null

        # Populate HTML table
        if ($packages -ne $null ) {
            $Params = @{
                Title = "ConfigMgr Failed Package Distributions"
                Topic1 = "Package Name"
                Topic2 = "PackageID"
                Topic3 = "Package Type"
                Topic4 = "Distribution Point Name"
                Topic5 = "Distribution State"
                Topic6 = "Status Message"
                Topic7 = "Last Update Time"
                }
        New-Table @Params
        foreach ($item in $packages )
            {
                $params = @{
                col1 = $item.'Package Name'
                col2 = $item.PackageID
                col3 = $item.'Package Type'
                col4 = $item.'Distribution Point Name'
                col5 = $item.'Distribution State'
                col6 = $item.'Status Message'
                col7 = $item.'Last Update Time'
                }
                New-TableRow @Params
            }
        New-TableEnd

        # Send email or return results
        $mailbody = Get-Content $msgfile
        Send-MailMessage -From $From -To $To -SmtpServer $SmtpServer -Subject $Subject -Body "$mailbody" -BodyAsHtml 

        # Delete tempfile
        Remove-Item $msgfile
        }
        Else {Write-Warning "No results were returned!"}
    }

if ($GridView)
    {
        if ($packages -ne $null)
            {$packages | Out-GridView -Title "ConfigMgr Failed Package Distributions"}
        Else {Write-Warning "No results were returned!"}
    }

If (!$GridView -and !$SendEmail)
    {
        if ($packages -ne $null)
            {$packages | ft -AutoSize}
        Else {Write-Warning "No results were returned!"}
    }

How to Quickly Retrieve Errors from OS Deployment Logs with PowerShell

I saw an interesting post yesterday by Keith Garner on using PowerShell’s “Select-string” to search OS deployment logs and find errors by searching on the entry type instead of the usual way that the CMTrace utility does it – by highlighting keywords.  I decided to take the idea further and create a script that will search my deployment logging shares, allow me to choose which deployed computer to search on, which log file to search, and then return the errors into CMTrace for easy viewing.

Here’a a quick demo:

I run the script and it searches my OSD logging directory (“SLShare” variable in the customsettings.ini) and returns all deployments in the last 10 days:

osd1

I select the deployment I want and click OK.  Then it searches recursively for all the log files in that directory, and asks me to choose one:

osd2

I want to search the smsts.log for errors, so I select it and click OK.  It then puts all the errors into a temporary log file and invokes it.  As .log files open with CMTrace by default, I can read through the errors more easily.

osd3

The script will then watch the CMTrace process, wait until you exit it, and delete the temporary log.

Configure the Script

The script has comment-based help, and there are some parameters you can use.  You will need to set the defaults in the script as the parameters are not mandatory.

$NumberOfDays – The script will search the logging share for deployments in the last x number of days

$LogDirectory – The location of your OSD logging share, eg your MDT “SLShare” directory

$Dynamic – Use this switch to search the dynamic logging share instead, eg your MDT “SLShareDynamicLogging” directory

$DynamicLogDirectory – the location of the  “SLShareDynamicLogging” directory

The Script


<#

.SYNOPSIS
    Retrieves error entries from logs used by MDT and ConfigMgr during OSD

.DESCRIPTION
    This script searches the OSD logs for error entries. You can:
    - specify the location of the log files, for example your deployment logging share
    - choose which computer's log files to search
    - choose which log file to search
    The errors will be added to a temporary log file, which will open with CMTrace (you need to set that as the default viewer for log files).
    This script is based on an idea from Keith Garner: https://keithga.wordpress.com/2015/05/04/find-errors-quickly-in-a-sccm-or-mdt-log-file/

.PARAMETER LogDirectory
    The location of your OSD logging directory, for example your "SLShare"

.PARAMETER DynamicLogDirectory
    The location of your OSD dynamic logging directory, for example your "SLShareDynamicLogging".  Use with the -Dynamic switch.

.PARAMETER NumberOfDays
    The number of days of deployment logs to retrieve, for example all deployments in the last 5 days

.PARAMETER Dynamic
    Use this switch to search the dynamic logging directory

.EXAMPLE
    .\Get-OSDLogErrors.ps1
    Searches the default logging directory for all deployments in the default number of days past, prompts you to choose the computer, then the log, then displays the error entries
    in a temporary log file

.EXAMPLE
    .\Get-OSDLogErrors.ps1 -LogDirectory \\mymdtserver\MDT_Logs$ -NumberOfDays 10
    Searches the logging directory specified for all deployments in the last 10 days, prompts you to choose the computer, then the log, then displays the error entries in a temporary
    log file

.EXAMPLE
    .\Get-OSDLogErrors.ps1 -Dynamic
    Searches the default dynamic logging directory for all deployments in the default number of days past, prompts you to choose the computer, then the log, then displays the error
    entries in a temporary log file

.NOTES
    Script name: Get-OSDLogErrors.ps1
    Author:      Trevor Jones
    Contact:     @trevor_smsagent
    DateCreated: 2015-05-11
    Link:        https://smsagent.wordpress.com

#>

[CmdletBinding()]
    param
        (
        [parameter(Mandatory=$False, HelpMessage="The number of days of deployment log files to check")]
            [string]$NumberOfDays = 5,
        [Parameter(Mandatory=$False, HelpMessage="The location of the OSD logging directory")]
            [string]$LogDirectory = "\\sccmserver01\MDT_Logs$",
        [parameter(Mandatory=$False)]
            [switch]$Dynamic,
        [Parameter(Mandatory=$False, HelpMessage="The location of the OSD dynamic logging directory")]
            [string]$DynamicLogDirectory = "\\sccmserver01\MDT_Logs$\Dynamic"
        )

if ($Dynamic)
    {$LogDirectory = $DynamicLogDirectory}

# Get the directory listing of deployed computers, and prompt to choose
if (test-path $LogDirectory)
    {
        $Computer = Get-ChildItem $LogDirectory |
            Where-Object {$_.LastWriteTime -ge (Get-Date).AddDays(-$NumberOfDays) -and $_.Name -notin ('Dynamic','Variables')} |
            Sort LastWriteTime -Descending |
            Select Name, LastWriteTime |
            Out-GridView -Title "Choose a deployed computer" -OutputMode Single |
            Select -ExpandProperty Name
    }
Else {Write-Warning "Could not access the log directory"; break}

# Get the list of log files for that computer, and prompt to choose
if ($Computer -ne $null)
    {
        $LogFile = Get-ChildItem "$LogDirectory\$Computer" -Recurse |
            Where-Object {$_.Mode -ne "d----"} |
            Select Name, @{N='Size (KB)'; E={[math]::Round(($_.Length / 1KB), 2)}}, LastWriteTime, @{N='Location'; E={$_.FullName}} |
            Sort Name |
            Out-GridView -Title "Choose a log file" -OutputMode Single |
            Select -ExpandProperty Location

        # Search the log file for entries of type 2 or 3
        $Entries = Select-String -Path "$LogFile" -Pattern "type=""(2|3)"""
    }
Else {break}

if ($Entries -eq $Null)
    {Write-Warning "No error entries found."; break}
Else
    {
        # Output each log entry to a temporary log file and invoke it
        $x = -1
        foreach ($Line in $Entries)
            {
                $x ++
                $Entries[$x].Line | Out-File "$env:TEMP\OSDerrors.log" -Append
            }
        Invoke-Item "$env:TEMP\OSDerrors.log"

        # Wait for CMTrace to start
        do
            {Start-Sleep -Seconds 2}
        until ((Get-Process CMTrace -ErrorAction Ignore) -ne $null)

        # wait until CMTrace is closed, then delete the temporary log file
        do
            {
                start-sleep -Seconds 2
                $process = Get-process -name CMTrace -ErrorAction Ignore
            }
        until ($process -eq $null)
        Remove-Item "$env:TEMP\OSDerrors.log"
    }