functions/utility/New-PSFSupportPackage.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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
function New-PSFSupportPackage
{
<#
 .SYNOPSIS
  Creates a package of troubleshooting information that can be used by developers to help debug issues.
  
 .DESCRIPTION
  This function creates an extensive debugging package that can help with reproducing and fixing issues.
   
  The file will be created on the desktop by default and will contain quite a bit of information:
  - OS Information
  - Hardware Information (CPU, Ram, things like that)
  - .NET Information
  - PowerShell Information
  - Your input history
  - The In-Memory message log
  - The In-Memory error log
  - Screenshot of the console buffer (Basically, everything written in your current console, even if you have to scroll upwards to see it).
  
 .PARAMETER Path
  The folder where to place the output xml in.
  Defaults to your desktop.
  
 .PARAMETER Variables
  Name of additional variables to attach.
  This allows you to add the content of variables to the support package, if you believe them to be relevant to the case.
  
 .PARAMETER ExcludeError
  By default, the content of $Error is included, as it often can be helpful in debugging, even with error handling using the message system.
  However, there can be rare instances where this will explode the total export size to gigabytes, in which case it becomes necessary to skip this.
  
 .PARAMETER EnableException
  This parameters disables user-friendly warnings and enables the throwing of exceptions.
  This is less user friendly, but allows catching exceptions in calling scripts.
  
 .EXAMPLE
  New-PSFSupportPackage
   
  Creates a large support pack in order to help us troubleshoot stuff.
#>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "")]
    [CmdletBinding()]
    param (
        [string]
        $Path = "$($env:USERPROFILE)\Desktop",
        
        [string[]]
        $Variables,
        
        [switch]
        $ExcludeError,
        
        [switch]
        [Alias('Silent')]
        $EnableException
    )
    
    begin
    {
        Write-PSFMessage -Level InternalComment -Message "Starting"
        Write-PSFMessage -Level Verbose -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")"
        
        #region Helper functions
        function Get-ShellBuffer
        {
            [CmdletBinding()]
            param ()
            
            try
            {
                # Define limits
                $rec = New-Object System.Management.Automation.Host.Rectangle
                $rec.Left = 0
                $rec.Right = $host.ui.rawui.BufferSize.Width - 1
                $rec.Top = 0
                $rec.Bottom = $host.ui.rawui.BufferSize.Height - 1
                
                # Load buffer
                $buffer = $host.ui.rawui.GetBufferContents($rec)
                
                # Convert Buffer to list of strings
                $int = 0
                $lines = @()
                while ($int -le $rec.Bottom)
                {
                    $n = 0
                    $line = ""
                    while ($n -le $rec.Right)
                    {
                        $line += $buffer[$int, $n].Character
                        $n++
                    }
                    $line = $line.TrimEnd()
                    $lines += $line
                    $int++
                }
                
                # Measure empty lines at the beginning
                $int = 0
                $temp = $lines[$int]
                while ($temp -eq "") { $int++; $temp = $lines[$int] }
                
                # Measure empty lines at the end
                $z = $rec.Bottom
                $temp = $lines[$z]
                while ($temp -eq "") { $z--; $temp = $lines[$z] }
                
                # Skip the line launching this very function
                $z--
                
                # Measure empty lines at the end (continued)
                $temp = $lines[$z]
                while ($temp -eq "") { $z--; $temp = $lines[$z] }
                
                # Cut results to the limit and return them
                return $lines[$int .. $z]
            }
            catch { }
        }
        #endregion Helper functions
    }
    process
    {
        $filePathXml = Join-Path $Path "powershell_support_pack_$(Get-Date -Format "yyyy_MM_dd-HH_mm_ss").xml"
        $filePathZip = $filePathXml -replace "\.xml$", ".zip"
        
        Write-PSFMessage -Level Critical -Message @"
Gathering information...
Will write the final output to: $filePathZip
 
Be aware that this package contains a lot of information including your input history in the console.
Please make sure no sensitive data (such as passwords) can be caught this way.
 
Ideally start a new console, perform the minimal steps required to reproduce the issue, then run this command.
This will make it easier for us to troubleshoot and you won't be sending us the keys to your castle.
"@

        
        $hash = @{ }
        Write-PSFMessage -Level Important -Message "Collecting PSFramework logged messages (Get-PSFMessage)"
        $hash["Messages"] = Get-PSFMessage
        Write-PSFMessage -Level Important -Message "Collecting PSFramework logged errors (Get-PSFMessage -Errors)"
        $hash["Errors"] = Get-PSFMessage -Errors
        Write-PSFMessage -Level Important -Message "Trying to collect copy of console buffer (what you can see on your console)"
        $hash["ConsoleBuffer"] = Get-ShellBuffer
        Write-PSFMessage -Level Important -Message "Collecting Operating System information (Win32_OperatingSystem)"
        $hash["OperatingSystem"] = Get-CimInstance -ClassName Win32_OperatingSystem
        Write-PSFMessage -Level Important -Message "Collecting CPU information (Win32_Processor)"
        $hash["CPU"] = Get-CimInstance -ClassName Win32_Processor
        Write-PSFMessage -Level Important -Message "Collecting Ram information (Win32_PhysicalMemory)"
        $hash["Ram"] = Get-CimInstance -ClassName Win32_PhysicalMemory
        Write-PSFMessage -Level Important -Message "Collecting PowerShell & .NET Version (`$PSVersionTable)"
        $hash["PSVersion"] = $PSVersionTable
        Write-PSFMessage -Level Important -Message "Collecting Input history (Get-History)"
        $hash["History"] = Get-History
        Write-PSFMessage -Level Important -Message "Collecting list of loaded modules (Get-Module)"
        $hash["Modules"] = Get-Module
        Write-PSFMessage -Level Important -Message "Collecting list of loaded snapins (Get-PSSnapin)"
        $hash["SnapIns"] = Get-PSSnapin
        Write-PSFMessage -Level Important -Message "Collecting list of loaded assemblies (Name, Version, and Location)"
        $hash["Assemblies"] = [appdomain]::CurrentDomain.GetAssemblies() | Select-Object CodeBase, FullName, Location, ImageRuntimeVersion, GlobalAssemblyCache, IsDynamic
        
        if (Test-PSFParameterBinding -ParameterName "Variables")
        {
            Write-PSFMessage -Level Important -Message "Adding variables specified for export: $($Variables -join ", ")"
            $hash["Variables"] = $Variables | Get-Variable -ErrorAction Ignore
        }
        if (-not $ExcludeError)
        {
            Write-PSFMessage -Level Important -Message "Adding content of `$Error"
            $hash["PSErrors"] = $Error | ForEach-Object { New-Object PSFramework.Message.PsfException($_) }
        }
        if (Test-Path function:Get-DbatoolsLog)
        {
            Write-PSFMessage -Level Important -Message "Collecting dbatools logged messages (Get-DbatoolsLog)"
            $hash["DbatoolsMessages"] = Get-DbatoolsLog
            Write-PSFMessage -Level Important -Message "Collecting dbatools logged errors (Get-DbatoolsLog -Errors)"
            $hash["DbatoolsErrors"] = Get-DbatoolsLog -Errors
        }
        
        $data = [pscustomobject]$hash
        
        try { $data | Export-Clixml -Path $filePathXml -ErrorAction Stop }
        catch
        {
            Stop-PSFFunction -Message "Failed to export dump to file!" -ErrorRecord $_ -Target $filePathXml
            return
        }
        
        try { Compress-Archive -Path $filePathXml -DestinationPath $filePathZip -ErrorAction Stop }
        catch
        {
            Stop-PSFFunction -Message "Failed to pack dump-file into a zip archive. Please do so manually before submitting the results as the unpacked xml file will be rather large." -ErrorRecord $_ -Target $filePathZip
            return
        }
        
        Remove-Item -Path $filePathXml -ErrorAction Ignore
    }
    end
    {
        Write-PSFMessage -Level InternalComment -Message "Ending"
    }
}