functions/invoke-d365runbookanalyzer.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

<#
    .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();
        }
    }
}