functions/invoke-d365runbookanalyzer.ps1


<#
    .SYNOPSIS
        Analyze the runbook
         
    .DESCRIPTION
        Get all the important details from a failed runbook
         
    .PARAMETER Path
        Path to the runbook file that you work against
         
    .PARAMETER FailedOnly
        Instruct the cmdlet to only output failed steps
         
    .PARAMETER FailedOnlyAsObjects
        Instruct the cmdlet to only output failed steps as objects
         
    .EXAMPLE
        PS C:\> Invoke-D365RunbookAnalyzer -Path "C:\DynamicsAX\InstallationRecords\Runbooks\Runbook.xml"
         
        This will analyze the Runbook.xml and output all the details about failed steps, the connected error logs and all the unprocessed steps.
         
    .EXAMPLE
        PS C:\> Get-D365Runbook -Latest | Invoke-D365RunbookAnalyzer
         
        This will find the latest runbook file and have it analyzed by the Invoke-D365RunbookAnalyzer cmdlet to output any error details.
         
    .EXAMPLE
        PS C:\> Get-D365Runbook -Latest | Invoke-D365RunbookAnalyzer -FailedOnly
         
        This will find the latest runbook file and have it analyzed by the Invoke-D365RunbookAnalyzer cmdlet to output any error details.
        The output from Invoke-D365RunbookAnalyzer will only contain failed steps.
         
    .EXAMPLE
        PS C:\> Get-D365Runbook -Latest | Invoke-D365RunbookAnalyzer -FailedOnlyAsObjects
         
        This will find the latest runbook file and have it analyzed by the Invoke-D365RunbookAnalyzer cmdlet to output any error details.
        The output from Invoke-D365RunbookAnalyzer will only contain failed steps.
        The output will be formatted as PSCustomObjects, to be used as variables or piping.
         
    .EXAMPLE
        PS C:\> Get-D365Runbook -Latest | Invoke-D365RunbookAnalyzer -FailedOnlyAsObjects | Get-D365RunbookLogFile -Path "C:\Temp\PU35" -OpenInEditor
         
        This will find the latest runbook file and have it analyzed by the Invoke-D365RunbookAnalyzer cmdlet to output any error details.
        The output from Invoke-D365RunbookAnalyzer will only contain failed steps.
        The Get-D365RunbookLogFile will open all log files for the failed step.
         
    .EXAMPLE
        PS C:\> Get-D365Runbook -Latest | Invoke-D365RunbookAnalyzer | Out-File "C:\Temp\d365fo.tools\runbook-analyze-results.xml"
         
        This will find the latest runbook file and have it analyzed by the Invoke-D365RunbookAnalyzer cmdlet to output any error details.
        The output will be saved into the "C:\Temp\d365fo.tools\runbook-analyze-results.xml" file.
         
    .EXAMPLE
        PS C:\> Get-D365Runbook -Latest | Backup-D365Runbook -Force | Invoke-D365RunbookAnalyzer
         
        This will get the latest runbook from the default location.
        This will backup the file onto the default "c:\temp\d365fo.tools\runbookbackups\".
        This will start the Runbook Analyzer on the backup file.
         
    .NOTES
        Tags: Runbook, Servicing, Hotfix, DeployablePackage, Deployable Package, InstallationRecordsDirectory, Installation Records Directory
         
        Author: Mötz Jensen (@Splaxi)
         
#>

function Invoke-D365RunbookAnalyzer {
    [CmdletBinding(DefaultParameterSetName="Default")]
    [OutputType('System.String')]
    param (
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true, ParameterSetName = "Default")]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true, ParameterSetName = "FailedOnly")]
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ValueFromPipeline = $true, ParameterSetName = "FailedOnlyAsObjects")]
        [Alias('File')]
        [string] $Path,

        [Parameter(ParameterSetName = "FailedOnly")]
        [switch] $FailedOnly,

        [Parameter(ParameterSetName = "FailedOnlyAsObjects")]
        [switch] $FailedOnlyAsObjects
    )
    
    process {
        if (-not (Test-PathExists -Path $Path -Type Leaf)) { return }

        $null = $sb = New-Object System.Text.StringBuilder
        $null = $sb.AppendLine("<D365FO.Tools.Runbook.Analyzer.Output>")

        [xml]$xmlRunbook = Get-Content $Path

        $failedObjs = New-Object System.Collections.Generic.List[System.Object]

        $failedSteps = $xmlRunbook.SelectNodes("//RunbookStepList/Step/StepState[text()='Failed']")

        $failedSteps | ForEach-Object {
            $null = $sb.AppendLine("<FailedStepInfo>")

            $stepId = $_.ParentNode | Select-Object -ExpandProperty childnodes | Where-Object { $_.name -like 'ID' } | Select-Object -ExpandProperty InnerText
            $failedLogs = $xmlRunbook.SelectNodes("//RunbookLogs/Log/StepID[text()='$stepId']")

            $failedObjs.Add([PsCustomObject]@{Step = "$stepId" })

            $null = $sb.AppendLine($_.ParentNode.OuterXml)

            $failedLogs | ForEach-Object { $null = $sb.AppendLine( $_.ParentNode.OuterXml) }

            $null = $sb.AppendLine("</FailedStepInfo>")
        }
        
        if ((-not $FailedOnly) -and (-not $FailedOnlyAsObjects)) {
            $inProgressSteps = $xmlRunbook.SelectNodes("//RunbookStepList/Step/StepState[text()='InProgress']")

            $null = $sb.AppendLine("<InProgressStepInfo>")

            $inProgressSteps | ForEach-Object { $null = $sb.AppendLine( $_.ParentNode.OuterXml) }

            $null = $sb.AppendLine("</InProgressStepInfo>")

            $unprocessedSteps = $xmlRunbook.SelectNodes("//RunbookStepList/Step/StepState[text()='NotStarted']")

            $null = $sb.AppendLine("<UnprocessedStepInfo>")

            $unprocessedSteps | ForEach-Object { $null = $sb.AppendLine( $_.ParentNode.OuterXml) }

            $null = $sb.AppendLine("</UnprocessedStepInfo>")
        }

        $null = $sb.AppendLine("</D365FO.Tools.Runbook.Analyzer.Output>")

        if ($FailedOnlyAsObjects) {
            $failedObjs.ToArray()
        }
        else {
            [xml]$xmlRaw = $sb.ToString()
        
            $stringWriter = New-Object System.IO.StringWriter;
            $xmlWriter = New-Object System.Xml.XmlTextWriter $stringWriter;
            $xmlWriter.Formatting = "indented";
            $xmlRaw.WriteTo($xmlWriter);
            $xmlWriter.Flush();
            $stringWriter.Flush();
            $stringWriter.ToString();
        }
    }
}