Public/AzStackHci.UserModeCrashDumpSettings.ps1

# ///////////////////////////////////////////////////////////////////
# Strict Mode v1 (PS 5.1 safe) - surfaces reads of uninitialised variables at runtime.
Set-StrictMode -Version 1.0

# Get-AzStackHciUserModeCrashDumpSettings function
# Used to get the current WER LocalDumps registry settings
# ///////////////////////////////////////////////////////////////////
Function Get-AzStackHciUserModeCrashDumpSettings {
    <#
    .SYNOPSIS
 
    Gets the current user mode crash dump registry settings
 
    .DESCRIPTION
 
    Reads the WER LocalDumps registry settings (DumpType, DumpFolder, DumpCount) and returns
    the current configuration as a PSCustomObject.
 
    Use -Scope Cluster to query settings on all nodes in the failover cluster via Invoke-Command.
    The scriptblock is self-contained and does not require the module on remote nodes.
 
    #>


    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param (
        # Scope: 'Node' (default) for local node only, 'Cluster' to query all cluster nodes
        [Parameter(Mandatory=$false)]
        [ValidateSet('Node','Cluster')]
        [string]$Scope = 'Node',

        [switch]$NoOutput
    )

    begin {
        if (-not (Test-Elevation)) { throw "This script must be run as an Administrator." }
    }

    process {
        # Reset SilentMode at entry — ensures clean state even if a prior call threw while -NoOutput was active
        $script:SilentMode = $false

        if ($NoOutput.IsPresent) {
            $script:SilentMode = $true
            $VerbosePreference = 'SilentlyContinue'
            $DebugPreference = 'SilentlyContinue'
        }

        # ---------------------------------------------------------------
        # Cluster scope: fan out to all cluster nodes via Invoke-Command
        # ---------------------------------------------------------------
        if ($Scope -eq 'Cluster') {
            if (-not (Test-CommandExists Get-Cluster)) {
                throw "The Get-Cluster cmdlet is not available. Please run this function on a clustered node."
            }
            try {
                [string]$ClusterName = (Get-Cluster -ErrorAction Stop).Name
                [array]$Nodes = Get-ClusterNode | Select-Object -ExpandProperty Name -ErrorAction Stop
            } catch {
                throw "Failed to enumerate cluster nodes. Error: $($_.Exception.Message)"
            }

            Write-Verbose "Cluster '$ClusterName' detected with $($Nodes.Count) node(s): $($Nodes -join ', ')" -Verbose

            # Self-contained scriptblock — does NOT depend on the module being installed on remote nodes
            $remoteResults = Invoke-Command -ComputerName $Nodes -ScriptBlock {
                $result = [PSCustomObject]@{
                    Node       = $env:COMPUTERNAME
                    Success    = $false
                    Action     = 'GetUserModeCrashDump'
                    DumpType   = $null
                    DumpTypeDescription = 'Not configured'
                    DumpFolder = $null
                    DumpCount  = $null
                    ExistingDumps = $null
                    Error      = $null
                }
                try {
                    $HKLMWERLocalDumps = 'HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps'
                    if (Test-Path $HKLMWERLocalDumps) {
                        $props = Get-ItemProperty -Path $HKLMWERLocalDumps -ErrorAction Stop
                        $result.DumpType   = $props.DumpType
                        $result.DumpFolder = $props.DumpFolder
                        $result.DumpCount  = $props.DumpCount
                        $result.DumpTypeDescription = switch ($props.DumpType) {
                            0 { 'Custom dump' }
                            1 { 'Minidump' }
                            2 { 'Full dump' }
                            default { 'Not configured' }
                        }
                    }
                    # Backup current settings to JSON
                    $DateFormatted = Get-Date -f 'yyyyMMdd-HHmm'
                    $BackupDir = 'C:\ProgramData\AzStackHci.DiagnosticSettings'
                    if (-not (Test-Path $BackupDir)) { New-Item $BackupDir -ItemType Directory -Force | Out-Null }
                    $BackupFile = "$BackupDir\UserModeCrashDump_Settings_$DateFormatted.json"
                    $registryNames = @('DumpType','DumpFolder','DumpCount')
                    $CurrentSettings = @{}
                    foreach ($name in $registryNames) {
                        $val = Get-ItemProperty -Path $HKLMWERLocalDumps -Name $name -ErrorAction SilentlyContinue
                        $CurrentSettings[$name] = if ($val) { $val.$name } else { $null }
                    }
                    $backupObject = [PSCustomObject]@{
                        BackupSchemaVersion = 1
                        BackupDate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
                        Node = $env:COMPUTERNAME
                        Settings = $CurrentSettings
                    }
                    # SHA-256 checksum of the canonical payload (excluding Checksum field itself) for tamper detection on restore.
                    $payloadJson = $backupObject | ConvertTo-Json -Depth 3 -Compress
                    $sha256 = [System.Security.Cryptography.SHA256]::Create()
                    try {
                        $checksumBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payloadJson))
                        $Checksum = ([BitConverter]::ToString($checksumBytes) -replace '-','').ToLowerInvariant()
                    } finally { $sha256.Dispose() }
                    $backupObject | Add-Member -NotePropertyName 'Checksum' -NotePropertyValue $Checksum
                    [System.IO.File]::WriteAllText($BackupFile, ($backupObject | ConvertTo-Json -Depth 3), (New-Object System.Text.UTF8Encoding($false)))

                    # Check for existing dump files
                    $dumpFolder = if ($result.DumpFolder) { $result.DumpFolder } else { 'C:\Windows\CrashDumps' }
                    if (Test-Path $dumpFolder) {
                        $dumps = Get-ChildItem -Path $dumpFolder -Filter '*.dmp' -ErrorAction SilentlyContinue
                        $result.ExistingDumps = if ($dumps) { $dumps.Count } else { 0 }
                    } else {
                        $result.ExistingDumps = 0
                    }
                    $result.Success = $true
                } catch {
                    $result.Error = $_.Exception.Message
                }
                $result
            } -ErrorAction SilentlyContinue -ErrorVariable remoteErrors

            if ($remoteErrors) {
                foreach ($err in $remoteErrors) {
                    Write-Verbose "REMOTE ERROR: $($err.Exception.Message)" -Verbose
                }
            }
            $successCount = 0
            $failCount = 0
            foreach ($nodeResult in $remoteResults) {
                if ($nodeResult.Success) {
                    Write-Verbose " $($nodeResult.Node): DumpType=$($nodeResult.DumpType) ($($nodeResult.DumpTypeDescription)), DumpFolder=$($nodeResult.DumpFolder), DumpCount=$($nodeResult.DumpCount), ExistingDumps=$($nodeResult.ExistingDumps)" -Verbose
                    $successCount++
                } else {
                    Write-Verbose " $($nodeResult.Node): FAILED - $($nodeResult.Error)" -Verbose
                    $failCount++
                }
            }
            Write-Verbose "Cluster query complete: $successCount succeeded, $failCount failed out of $($Nodes.Count) nodes." -Verbose
            return $remoteResults
        }

        # ---------------------------------------------------------------
        # Node scope: read user mode crash dump settings on local node
        # ---------------------------------------------------------------
        $HKLMWERLocalDumps = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"

        $DumpType   = $null
        $DumpFolder = $null
        $DumpCount  = $null

        if (Test-Path $HKLMWERLocalDumps) {
            $props = Get-ItemProperty -Path $HKLMWERLocalDumps -ErrorAction SilentlyContinue
            $DumpType   = $props.DumpType
            $DumpFolder = $props.DumpFolder
            $DumpCount  = $props.DumpCount
        }

        # Translate DumpType numeric value
        $DumpTypeDescription = switch ($DumpType) {
            0 { 'Custom dump' }
            1 { 'Minidump' }
            2 { 'Full dump' }
            default { 'Not configured' }
        }

        # Backup current settings to JSON
        $DateFormatted = Get-Date -f "yyyyMMdd-HHmm"
        $BackupDir = "C:\ProgramData\AzStackHci.DiagnosticSettings"
        if (-not (Test-Path $BackupDir)) { New-Item $BackupDir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null }
        $BackupFile = "$BackupDir\UserModeCrashDump_Settings_$DateFormatted.json"
        $registryNames = @('DumpType','DumpFolder','DumpCount')
        $CurrentSettings = @{}
        foreach ($name in $registryNames) {
            $val = Get-ItemProperty -Path $HKLMWERLocalDumps -Name $name -ErrorAction SilentlyContinue
            $CurrentSettings[$name] = if ($val) { $val.$name } else { $null }
        }
        $backupObject = [PSCustomObject]@{
            BackupSchemaVersion = 1
            BackupDate = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
            Node = $env:COMPUTERNAME
            Settings = $CurrentSettings
        }
        $payloadJson = $backupObject | ConvertTo-Json -Depth 3 -Compress
        $sha256 = [System.Security.Cryptography.SHA256]::Create()
        try {
            $checksumBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payloadJson))
            $Checksum = ([BitConverter]::ToString($checksumBytes) -replace '-','').ToLowerInvariant()
        } finally { $sha256.Dispose() }
        $backupObject | Add-Member -NotePropertyName 'Checksum' -NotePropertyValue $Checksum
        [System.IO.File]::WriteAllText($BackupFile, ($backupObject | ConvertTo-Json -Depth 3), (New-Object System.Text.UTF8Encoding($false)))
        Write-Verbose "Current WER LocalDumps settings exported to '$BackupFile'" -Verbose

        # Check for existing dump files
        $dumpPath = if ($DumpFolder) { $DumpFolder } else { 'C:\Windows\CrashDumps' }
        $ExistingDumps = 0
        if (Test-Path $dumpPath) {
            $dumps = Get-ChildItem -Path $dumpPath -Filter '*.dmp' -ErrorAction SilentlyContinue
            if ($dumps) { $ExistingDumps = $dumps.Count }
        }

        Write-Verbose "WER LocalDumps: DumpType=$DumpType ($DumpTypeDescription), DumpFolder=$DumpFolder, DumpCount=$DumpCount, ExistingDumps=$ExistingDumps" -Verbose

        [PSCustomObject]@{
            Node              = $env:COMPUTERNAME
            Success           = $true
            Action            = 'GetUserModeCrashDump'
            DumpType          = $DumpType
            DumpTypeDescription = $DumpTypeDescription
            DumpFolder        = $DumpFolder
            DumpCount         = $DumpCount
            ExistingDumps     = $ExistingDumps
            Error             = $null
        }

    } # End of process block

    end {
        if ($NoOutput.IsPresent) { $script:SilentMode = $false }
        Write-Debug "Completed Get-AzStackHciUserModeCrashDumpSettings function"
    }

} # End of Get-AzStackHciUserModeCrashDumpSettings function


# ///////////////////////////////////////////////////////////////////
# Set-AzStackHciUserModeCrashDumpSettings function
# Used to set the registry settings for user mode crash dumps
# ///////////////////////////////////////////////////////////////////
Function Set-AzStackHciUserModeCrashDumpSettings {
    <#
    .SYNOPSIS
 
    Sets the registry settings for user mode crash dumps
 
    .DESCRIPTION
 
    Sets the registry settings for user mode crash dumps, enabling Windows Error Reporting to create user mode dumps on the host.
 
    Use -Scope Cluster to apply settings to all nodes in the failover cluster via Invoke-Command.
    The scriptblock is self-contained and does not require the module on remote nodes.
 
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [OutputType([PSCustomObject[]])]
    param (
        # Scope: 'Node' (default) for local node only, 'Cluster' to apply to all cluster nodes
        [Parameter(Mandatory=$false)]
        [ValidateSet('Node','Cluster')]
        [string]$Scope = 'Node',

        [switch]$NoOutput
    )

    begin {
        # Requires administrator permissions to set user mode crash dump settings
        if (-not (Test-Elevation)) { throw "This script must be run as an Administrator." }
    }

    process {
        # Reset SilentMode at entry — ensures clean state even if a prior call threw while -NoOutput was active
        $script:SilentMode = $false

        # Handle -NoOutput: suppress all console output
        if ($NoOutput.IsPresent) {
            $script:SilentMode = $true
            $VerbosePreference = 'SilentlyContinue'
            $DebugPreference = 'SilentlyContinue'
        }

        # ---------------------------------------------------------------
        # Cluster scope: fan out to all cluster nodes via Invoke-Command
        # ---------------------------------------------------------------
        if ($Scope -eq 'Cluster') {
            if (-not (Test-CommandExists Get-Cluster)) {
                throw "The Get-Cluster cmdlet is not available. Please run this function on a clustered node."
            }
            try {
                [string]$ClusterName = (Get-Cluster -ErrorAction Stop).Name
                [array]$Nodes = Get-ClusterNode | Select-Object -ExpandProperty Name -ErrorAction Stop
            } catch {
                throw "Failed to enumerate cluster nodes. Error: $($_.Exception.Message)"
            }

            Write-Verbose "Cluster '$ClusterName' detected with $($Nodes.Count) node(s): $($Nodes -join ', ')" -Verbose

            # ShouldProcess confirmation once on the initiating node for the entire cluster operation
            if (-not $PSCmdlet.ShouldProcess("Cluster '$ClusterName' ($($Nodes -join ', '))", "Set user mode crash dump settings on $($Nodes.Count) cluster nodes")) {
                return
            }

            # Self-contained scriptblock — does NOT depend on the module being installed on remote nodes
            $remoteResults = Invoke-Command -ComputerName $Nodes -ScriptBlock {
                $result = [PSCustomObject]@{ Node = $env:COMPUTERNAME; Success = $false; Action = 'SetUserModeCrashDump'; Error = $null }
                try {
                    $HKLMWERLocalDumps = 'HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps'
                    $registryNames = @('DumpType','DumpFolder','DumpCount')

                    # Backup current settings before making changes
                    $DateFormatted = Get-Date -f 'yyyyMMdd-HHmm'
                    $BackupDir = 'C:\ProgramData\AzStackHci.DiagnosticSettings'
                    if (-not (Test-Path $BackupDir)) { New-Item $BackupDir -ItemType Directory -Force | Out-Null }
                    $BackupFile = "$BackupDir\UserModeCrashDump_Settings_$DateFormatted.json"
                    $CurrentSettings = @{}
                    foreach ($name in $registryNames) {
                        $val = Get-ItemProperty -Path $HKLMWERLocalDumps -Name $name -ErrorAction SilentlyContinue
                        $CurrentSettings[$name] = if ($val) { $val.$name } else { $null }
                    }
                    $backupObject = [PSCustomObject]@{
                        BackupSchemaVersion = 1
                        BackupDate = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
                        Node = $env:COMPUTERNAME
                        Settings = $CurrentSettings
                    }
                    $payloadJson = $backupObject | ConvertTo-Json -Depth 3 -Compress
                    $sha256 = [System.Security.Cryptography.SHA256]::Create()
                    try {
                        $checksumBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payloadJson))
                        $Checksum = ([BitConverter]::ToString($checksumBytes) -replace '-','').ToLowerInvariant()
                    } finally { $sha256.Dispose() }
                    $backupObject | Add-Member -NotePropertyName 'Checksum' -NotePropertyValue $Checksum
                    [System.IO.File]::WriteAllText($BackupFile, ($backupObject | ConvertTo-Json -Depth 3), (New-Object System.Text.UTF8Encoding($false)))

                    # Capture original values for rollback
                    $originalValues = @{}
                    foreach ($name in $registryNames) {
                        try { $originalValues[$name] = (Get-ItemProperty -Path $HKLMWERLocalDumps -Name $name -ErrorAction Stop).$name }
                        catch { $originalValues[$name] = $null }
                    }
                    $keyExisted = Test-Path $HKLMWERLocalDumps

                    try {
                        New-Item $HKLMWERLocalDumps -ErrorAction SilentlyContinue | Out-Null
                        Set-ItemProperty -Path $HKLMWERLocalDumps -Name DumpType -Type DWord -Value 1 -ErrorAction Stop
                        if (-not (Test-Path 'C:\Windows\CrashDumps')) { New-Item 'C:\Windows\CrashDumps' -ItemType Directory -ErrorAction SilentlyContinue | Out-Null }
                        $compactExe = "$env:SystemRoot\System32\compact.exe"
                        if (Test-Path $compactExe) { & $compactExe /i /c /a /s:C:\Windows\CrashDumps | Out-Null }
                        Set-ItemProperty -Path $HKLMWERLocalDumps -Name DumpFolder -Type ExpandString -Value 'C:\Windows\CrashDumps' -ErrorAction Stop
                        Set-ItemProperty -Path $HKLMWERLocalDumps -Name DumpCount -Type DWord -Value 1 -ErrorAction Stop
                    } catch {
                        # Rollback on failure
                        foreach ($name in $registryNames) {
                            try {
                                if ($null -eq $originalValues[$name]) { Remove-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Force -ErrorAction SilentlyContinue }
                                else { Set-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Value $originalValues[$name] -ErrorAction SilentlyContinue }
                            } catch { Write-Debug "Rollback of WERLocalDumps/$name failed: $($_.Exception.Message)" }
                        }
                        if (-not $keyExisted) { Remove-Item $HKLMWERLocalDumps -Force -ErrorAction SilentlyContinue }
                        throw "Failed to set WER LocalDumps settings (rolled back). Error: $($_.Exception.Message)"
                    }
                    $result.Success = $true
                } catch {
                    $result.Error = $_.Exception.Message
                }
                $result
            } -ErrorAction SilentlyContinue -ErrorVariable remoteErrors

            # Report per-node results
            if ($remoteErrors) {
                foreach ($err in $remoteErrors) {
                    Write-Verbose "REMOTE ERROR: $($err.Exception.Message)" -Verbose
                }
            }
            $successCount = 0
            $failCount = 0
            foreach ($nodeResult in $remoteResults) {
                if ($nodeResult.Success) {
                    Write-Verbose " $($nodeResult.Node): SUCCESS" -Verbose
                    $successCount++
                } else {
                    Write-Verbose " $($nodeResult.Node): FAILED - $($nodeResult.Error)" -Verbose
                    $failCount++
                }
            }
            Write-Verbose "Cluster operation complete: $successCount succeeded, $failCount failed out of $($Nodes.Count) nodes." -Verbose
            if ($failCount -gt 0) {
                Write-Warning "$failCount node(s) failed. Review the output above for details."
            }
            return $remoteResults
        }

        # ---------------------------------------------------------------
        # Node scope: configure user mode crash dumps on local node
        # ---------------------------------------------------------------

        # Enabling Windows Error Reporting to create user mode dumps on Host
        $HKLMWERLocalDumps = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"
        $registryNames = @('DumpType','DumpFolder','DumpCount')

        # Backup current settings BEFORE making any changes
        $null = Get-AzStackHciUserModeCrashDumpSettings

        # ShouldProcess gate: prompt user before modifying WER registry settings
        if (-not $PSCmdlet.ShouldProcess("WER LocalDumps registry ($HKLMWERLocalDumps)", "Set user mode crash dump registry values")) {
            return
        }

        # Capture original registry values for rollback in case of partial failure
        $originalValues = @{}
        foreach ($name in $registryNames) {
            try { $originalValues[$name] = (Get-ItemProperty -Path $HKLMWERLocalDumps -Name $name -ErrorAction Stop).$name }
            catch { $originalValues[$name] = $null }
        }
        $keyExisted = Test-Path $HKLMWERLocalDumps

        try {
            New-Item $HKLMWERLocalDumps -ErrorAction SilentlyContinue | Out-Null

            # Host_Application_LocalDump_DumpType
            Set-ItemProperty -Path $HKLMWERLocalDumps -Name DumpType -Type DWord -Value 1 -ErrorAction Stop

            # Use path "C:\Windows\CrashDumps" and compress the folder
            if(-not(test-path "C:\Windows\CrashDumps")) { New-Item "C:\Windows\CrashDumps" -ItemType Directory -ErrorAction SilentlyContinue | Out-Null }
            Write-Verbose "Compressing the 'C:\Windows\CrashDumps' folder..." -Verbose
            $compactExe = "$env:SystemRoot\System32\compact.exe"
            if (Test-Path $compactExe) {
                & $compactExe /i /c /a /s:C:\Windows\CrashDumps | Out-Null
            } else {
                Write-Warning "compact.exe not found at '$compactExe'. Skipping folder compression."
            }

            # Host_Application_LocalDump_DumpFolder
            Set-ItemProperty -Path $HKLMWERLocalDumps -Name DumpFolder -Type ExpandString -Value "C:\Windows\CrashDumps" -ErrorAction Stop

            # Host_Application_LocalDump_DumpCount, only keep one dump per process to limit disk space usage
            Set-ItemProperty -Path $HKLMWERLocalDumps -Name DumpCount -Type DWord -Value 1 -ErrorAction Stop
        } catch {
            # Rollback: restore original values to avoid leaving the system in a partially configured state
            Write-Verbose "Registry write failed, rolling back to original values. Error: $($_.Exception.Message)" -Verbose
            foreach ($name in $registryNames) {
                try {
                    if ($null -eq $originalValues[$name]) {
                        Remove-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Force -ErrorAction SilentlyContinue
                    } else {
                        Set-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Value $originalValues[$name] -ErrorAction SilentlyContinue
                    }
                } catch {
                    Write-Warning "Rollback: Failed to restore '$name'. Manual intervention may be required."
                }
            }
            if (-not $keyExisted) { Remove-Item $HKLMWERLocalDumps -Force -ErrorAction SilentlyContinue }
            Throw "Failed to set WER LocalDumps registry settings (rolled back). Error: $($_.Exception.Message)"
        }

        Write-Verbose "User mode crash dump settings configured: DumpType=1 (Minidump), DumpFolder='C:\Windows\CrashDumps', DumpCount=1" -Verbose

        [PSCustomObject]@{ Node = $env:COMPUTERNAME; Success = $true; Action = 'SetUserModeCrashDump'; Error = $null }

    } # End of process block

    end {
        if ($NoOutput.IsPresent) { $script:SilentMode = $false }
        Write-Debug "Completed Set-AzStackHciUserModeCrashDumpSettings function"
    }

} # End of Set-AzStackHciUserModeCrashDumpSettings function


# ///////////////////////////////////////////////////////////////////
# Restore-AzStackHciUserModeCrashDumpSettings function
# Used to restore WER LocalDumps registry settings from a backup file
# ///////////////////////////////////////////////////////////////////
Function Restore-AzStackHciUserModeCrashDumpSettings {
    <#
    .SYNOPSIS
 
    Restores user mode crash dump settings from a backup file
 
    .DESCRIPTION
 
    Restores the WER LocalDumps registry settings (DumpType, DumpFolder, DumpCount) from a backup file
    created by Set-AzStackHciUserModeCrashDumpSettings.
 
    Use -Scope Cluster to restore settings on all nodes in the failover cluster via Invoke-Command.
    The scriptblock is self-contained and does not require the module on remote nodes.
    #>


    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
    [OutputType([PSCustomObject[]])]
    param (
        # File path to backup file created by Set-AzStackHciUserModeCrashDumpSettings
        [Parameter(Mandatory=$false, Position=0)]
        [ValidateScript({Test-Path $_})]
        [String]$UserModeCrashDumpSettingsFilePath,

        # Scope: 'Node' (default) for local node only, 'Cluster' to restore on all cluster nodes
        [Parameter(Mandatory=$false)]
        [ValidateSet('Node','Cluster')]
        [string]$Scope = 'Node',

        [Parameter(Mandatory=$false, HelpMessage="Optional switch to prevent console output from the function.")]
        [switch]$NoOutput
    )

    begin {
        if (-not (Test-Elevation)) { throw "This script must be run as an Administrator." }
    }

    process {
        # Reset SilentMode at entry — ensures clean state even if a prior call threw while -NoOutput was active
        $script:SilentMode = $false

        if ($NoOutput.IsPresent) {
            $script:SilentMode = $true
            $VerbosePreference = 'SilentlyContinue'
            $DebugPreference = 'SilentlyContinue'
        }

        # ---------------------------------------------------------------
        # Cluster scope: fan out to all cluster nodes via Invoke-Command
        # ---------------------------------------------------------------
        if ($Scope -eq 'Cluster') {
            if (-not (Test-CommandExists Get-Cluster)) {
                throw "The Get-Cluster cmdlet is not available. Please run this function on a clustered node."
            }
            try {
                [string]$ClusterName = (Get-Cluster -ErrorAction Stop).Name
                [array]$Nodes = Get-ClusterNode | Select-Object -ExpandProperty Name -ErrorAction Stop
            } catch {
                throw "Failed to enumerate cluster nodes. Error: $($_.Exception.Message)"
            }

            Write-Verbose "Cluster '$ClusterName' detected with $($Nodes.Count) node(s): $($Nodes -join ', ')" -Verbose

            if (-not $PSCmdlet.ShouldProcess("Cluster '$ClusterName' ($($Nodes -join ', '))", "Restore user mode crash dump settings from backup on $($Nodes.Count) cluster nodes")) {
                return
            }

            $remoteFilePath = if ($PSBoundParameters.ContainsKey('UserModeCrashDumpSettingsFilePath')) { $UserModeCrashDumpSettingsFilePath } else { $null }

            $remoteResults = Invoke-Command -ComputerName $Nodes -ScriptBlock {
                param($FilePath)
                $result = [PSCustomObject]@{ Node = $env:COMPUTERNAME; Success = $false; Action = 'Restore'; BackupFile = $FilePath; Error = $null }
                try {
                    # If no file specified, find the most recent backup on this node
                    if ([string]::IsNullOrEmpty($FilePath)) {
                        $BackupDir = 'C:\ProgramData\AzStackHci.DiagnosticSettings'
                        $latestBackup = Get-ChildItem "$BackupDir\UserModeCrashDump_Settings_*.json" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1
                        if (-not $latestBackup) {
                            throw "No backup file specified and no UserModeCrashDump_Settings_*.json files found in '$BackupDir'."
                        }
                        $FilePath = $latestBackup.FullName
                        $result.BackupFile = $FilePath
                    }

                    $ResolvedPath = (Resolve-Path -Path $FilePath -ErrorAction Stop).Path
                    if ($ResolvedPath -notlike 'C:\ProgramData\AzStackHci.DiagnosticSettings\UserModeCrashDump_Settings_*') {
                        throw "Backup file path must resolve to 'C:\ProgramData\AzStackHci.DiagnosticSettings\UserModeCrashDump_Settings_*'. Got: '$ResolvedPath'"
                    }
                    $backup = Get-Content $ResolvedPath -Raw -ErrorAction Stop | ConvertFrom-Json

                    # Verify checksum if present (v0.6.5+). Older backups lacked this field so we warn instead of fail.
                    if ($backup.PSObject.Properties.Name -contains 'Checksum' -and $backup.Checksum) {
                        $storedChecksum = [string]$backup.Checksum
                        $verifyObject = [PSCustomObject]@{
                            BackupSchemaVersion = $backup.BackupSchemaVersion
                            BackupDate          = $backup.BackupDate
                            Node                = $backup.Node
                            Settings            = $backup.Settings
                        }
                        $verifyJson = $verifyObject | ConvertTo-Json -Depth 3 -Compress
                        $sha256 = [System.Security.Cryptography.SHA256]::Create()
                        try {
                            $computedBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($verifyJson))
                            $computedChecksum = ([BitConverter]::ToString($computedBytes) -replace '-','').ToLowerInvariant()
                        } finally { $sha256.Dispose() }
                        if ($computedChecksum -ne $storedChecksum.ToLowerInvariant()) {
                            throw "Backup file checksum mismatch — the file at '$ResolvedPath' may have been tampered with or corrupted. Aborting restore."
                        }
                    } else {
                        Write-Warning "Backup file '$ResolvedPath' has no Checksum field (pre-v0.6.5 format). Restore will proceed without integrity verification."
                    }

                    $HKLMWERLocalDumps = 'HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps'

                    $registryNames = @('DumpType','DumpFolder','DumpCount')
                    foreach ($name in $registryNames) {
                        $value = $backup.Settings.$name
                        if ($null -eq $value) {
                            Remove-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Force -ErrorAction SilentlyContinue
                        } else {
                            New-Item $HKLMWERLocalDumps -ErrorAction SilentlyContinue | Out-Null
                            if ($name -eq 'DumpFolder') {
                                Set-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Type ExpandString -Value $value -ErrorAction Stop
                            } else {
                                Set-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Type DWord -Value ([int]$value) -ErrorAction Stop
                            }
                        }
                    }
                    $result.Success = $true
                } catch {
                    $result.Error = $_.Exception.Message
                }
                $result
            } -ArgumentList $remoteFilePath -ErrorAction SilentlyContinue -ErrorVariable remoteErrors

            if ($remoteErrors) {
                foreach ($err in $remoteErrors) { Write-Verbose "REMOTE ERROR: $($err.Exception.Message)" -Verbose }
            }
            $successCount = 0; $failCount = 0
            foreach ($nodeResult in $remoteResults) {
                if ($nodeResult.Success) { Write-Verbose " $($nodeResult.Node): SUCCESS" -Verbose; $successCount++ }
                else { Write-Verbose " $($nodeResult.Node): FAILED - $($nodeResult.Error)" -Verbose; $failCount++ }
            }
            Write-Verbose "Cluster restore complete: $successCount succeeded, $failCount failed out of $($Nodes.Count) nodes." -Verbose
            if ($failCount -gt 0) { Write-Warning "$failCount node(s) failed. Review the output above for details." }
            return $remoteResults
        }

        # If no file specified, find the most recent backup (node scope only)
        if ([string]::IsNullOrEmpty($UserModeCrashDumpSettingsFilePath)) {
            $BackupDir = "C:\ProgramData\AzStackHci.DiagnosticSettings"
            $latestBackup = Get-ChildItem "$BackupDir\UserModeCrashDump_Settings_*.json" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1
            if (-not $latestBackup) {
                throw "No backup file specified and no UserModeCrashDump_Settings_*.json files found in '$BackupDir'."
            }
            $UserModeCrashDumpSettingsFilePath = $latestBackup.FullName
            Write-Verbose "No backup file specified — using most recent: '$UserModeCrashDumpSettingsFilePath'" -Verbose
        }

        # ---------------------------------------------------------------
        # Node scope: restore user mode crash dump settings on local node
        # ---------------------------------------------------------------

        # Resolve and validate backup file path
        try {
            $ResolvedBackupPath = (Resolve-Path -Path $UserModeCrashDumpSettingsFilePath -ErrorAction Stop).Path
        } catch {
            throw "Unable to resolve backup file path '$UserModeCrashDumpSettingsFilePath'. Error: $($_.Exception.Message)"
        }
        if ($ResolvedBackupPath -notlike "C:\ProgramData\AzStackHci.DiagnosticSettings\UserModeCrashDump_Settings_*") {
            throw "Backup file path must resolve to 'C:\ProgramData\AzStackHci.DiagnosticSettings\UserModeCrashDump_Settings_*'. Resolved path: '$ResolvedBackupPath'"
        }

        Write-Verbose "Reading WER LocalDumps settings from backup file: $ResolvedBackupPath" -Verbose
        $backup = Get-Content $ResolvedBackupPath -Raw -ErrorAction Stop | ConvertFrom-Json

        # Verify checksum if present (v0.6.5+). Older backups lacked this field so we warn instead of fail.
        if ($backup.PSObject.Properties.Name -contains 'Checksum' -and $backup.Checksum) {
            $storedChecksum = [string]$backup.Checksum
            $verifyObject = [PSCustomObject]@{
                BackupSchemaVersion = $backup.BackupSchemaVersion
                BackupDate          = $backup.BackupDate
                Node                = $backup.Node
                Settings            = $backup.Settings
            }
            $verifyJson = $verifyObject | ConvertTo-Json -Depth 3 -Compress
            $sha256 = [System.Security.Cryptography.SHA256]::Create()
            try {
                $computedBytes = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($verifyJson))
                $computedChecksum = ([BitConverter]::ToString($computedBytes) -replace '-','').ToLowerInvariant()
            } finally { $sha256.Dispose() }
            if ($computedChecksum -ne $storedChecksum.ToLowerInvariant()) {
                throw "Backup file checksum mismatch — the file at '$ResolvedBackupPath' may have been tampered with or corrupted. Aborting restore."
            }
        } else {
            Write-Warning "Backup file '$ResolvedBackupPath' has no Checksum field (pre-v0.6.5 format). Restore will proceed without integrity verification."
        }

        $HKLMWERLocalDumps = "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"

        # ShouldProcess gate
        if (-not $PSCmdlet.ShouldProcess("WER LocalDumps registry ($HKLMWERLocalDumps)", "Restore user mode crash dump settings from '$ResolvedBackupPath' (saved on $($backup.BackupDate))")) {
            return
        }

        # Capture current values for rollback in case of partial failure
        $registryNames = @('DumpType','DumpFolder','DumpCount')
        $originalValues = @{}
        $keyExisted = Test-Path $HKLMWERLocalDumps
        foreach ($name in $registryNames) {
            try { $originalValues[$name] = (Get-ItemProperty -Path $HKLMWERLocalDumps -Name $name -ErrorAction Stop).$name }
            catch { $originalValues[$name] = $null }
        }

        # Parse and restore settings from JSON
        try {
            foreach ($name in $registryNames) {
                $value = $backup.Settings.$name
                if ($null -eq $value) {
                    Write-Verbose " Removing '$name' (was not configured in backup)" -Verbose
                    Remove-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Force -ErrorAction SilentlyContinue
                } else {
                    Write-Verbose " Restoring '$name' = '$value'" -Verbose
                    New-Item $HKLMWERLocalDumps -ErrorAction SilentlyContinue | Out-Null
                    if ($name -eq 'DumpFolder') {
                        Set-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Type ExpandString -Value $value -ErrorAction Stop
                    } else {
                        Set-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Type DWord -Value ([int]$value) -ErrorAction Stop
                    }
                }
            }
        } catch {
            # Rollback: restore original values to avoid leaving the system in a partially restored state
            Write-Verbose "Registry restore failed, rolling back to pre-restore values. Error: $($_.Exception.Message)" -Verbose
            foreach ($name in $registryNames) {
                try {
                    if ($null -eq $originalValues[$name]) {
                        Remove-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Force -ErrorAction SilentlyContinue
                    } else {
                        Set-ItemProperty -Path $HKLMWERLocalDumps -Name $name -Value $originalValues[$name] -ErrorAction SilentlyContinue
                    }
                } catch {
                    Write-Warning "Rollback: Failed to restore '$name'. Manual intervention may be required."
                }
            }
            if (-not $keyExisted) { Remove-Item $HKLMWERLocalDumps -Force -ErrorAction SilentlyContinue }
            Throw "Failed to restore WER LocalDumps registry settings from backup (rolled back). Error: $($_.Exception.Message)"
        }

        Write-Verbose "User mode crash dump settings restored from '$ResolvedBackupPath'" -Verbose
        [PSCustomObject]@{ Node = $env:COMPUTERNAME; Success = $true; Action = 'Restore'; BackupFile = $ResolvedBackupPath; Error = $null }

    } # End of process block

    end {
        if ($NoOutput.IsPresent) { $script:SilentMode = $false }
        Write-Debug "Completed Restore-AzStackHciUserModeCrashDumpSettings function"
    }

} # End of Restore-AzStackHciUserModeCrashDumpSettings function

# SIG # Begin signature block
# MIInRgYJKoZIhvcNAQcCoIInNzCCJzMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAe64DspZXxz0qv
# +haizW9bK4nlZLxf85qsAsW+cxLgQ6CCDLowggX1MIID3aADAgECAhMzAAACHU0Z
# yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1
# OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8
# o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg
# 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4
# Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R
# X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk
# ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw
# HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg
# UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh
# MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9
# s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H
# VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3
# w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n
# 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs
# A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo
# Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb
# SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6
# 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z
# V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v
# 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs
# /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA
# AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow
# VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh
# emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h
# KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd
# M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp
# yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t
# Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5
# REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs
# 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK
# Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5
# pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW
# eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ
# 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC
# NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI
# MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4
# NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh
# ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q
# hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU
# nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb
# H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z
# uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u
# vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW
# 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV
# DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10
# 1cY2L4A7GTQG1h32HHAvfQESWP0xghniMIIZ3gIBATBuMFcxCzAJBgNVBAYTAlVT
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w
# DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK
# KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIOm+dTsb
# juQRfBHVlN/qoD9qifKHfOT/sS/Ij7Cf11wIMEIGCisGAQQBgjcCAQwxNDAyoBSA
# EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w
# DQYJKoZIhvcNAQEBBQAEggEAzpFBSME5NADxLY2oRYzS+6fdn9TjxYzR6B9cN6HY
# hNQiRIH3GxfwTd7YOa795mlDIiMIedn2d6PNm6UEB2T4PJVesjk4k/Qgp0BFoWSn
# UenblhuM+3wQeB9osPcpsIfZ+XJDdoSHn3fHNaFb0BvaF/2dmNydc3Tvmof6tuDc
# 5lnRQongPa96aoxC3CTpeMyazVTSRaKJgntJ9D4qfJMR+13y6lhmSdZIjB1Vqi+9
# Y41FLUqOkpHYJCHrbkpr7FufYtefCHoA56Gs0rgDa3u2T/2EdAieKErErCIYYZpT
# pyb+yXvuFPGXmAXg8ZJTEc8Egx15w3PZe5CX053xevNRhqGCF5QwgheQBgorBgEE
# AYI3AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUD
# BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD
# ATAxMA0GCWCGSAFlAwQCAQUABCCvkEiM+Jw/wovjnV1zhf++5YSTmebj3ULJTzul
# E4WZ3gIGajzCfWrrGBMyMDI2MDcwNzE2NTYzMC44MTlaMASAAgH0oIHRpIHOMIHL
# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN
# aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT
# UyBFU046RTAwMi0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFNlcnZpY2WgghHqMIIHIDCCBQigAwIBAgITMwAAAikO1WQqtJfyGgABAAAC
# KTANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe
# Fw0yNjAyMTkxOTQwMDdaFw0yNzA1MTcxOTQwMDdaMIHLMQswCQYDVQQGEwJVUzET
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj
# YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046RTAwMi0wNUUw
# LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi
# MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCeItFq4z1oCYSmUZmpYDsbJWEu
# ++1bbc/Mz7Pa3I0ZX5EON+WirB0FvnGlyFRUylzO5TJXZfU8QFPOU95P1Y1OZ8J+
# quA5G+AWSBOr/48scl0s9RBpqgTMq/lbyqBz4CMmvVR2QevAgVp4a1hbmOm9G7YW
# ey68N5F5rSDYV0wMlg4Iy8YRuFgRN2eBpVXt9IvFaFmBnQLZfo22KZ3L8PWEHUhX
# U5dLOSZoTfqqQ/B+deW56ACMnnHjPxZu+szHhZMLUrMWTgs9J7Cn8DtelcKj9aM+
# 0Zq7tkSDHCrwo6eCSfw3clktXRRrdmsccal8RCDiNFFgZsypwF2aGAF6kg41+Ql+
# thXpnOMUH4mPCAJZWp0zDWowsK/Yo5jHL1pT/AgbL3FoAy4cbhOI4Pb1eQFG+jT7
# skS2F/b+ZACUA1EDZ830K+Bu0yw+FpSGy8tpd1szk3cUYjIpzIG4z3oFNmiSJN8Y
# dNd4SHsER5Dks5bxiKbpvmfrOA39jTb7EW2TT7ySWgJISfvTezuLmQsTVSzNsvap
# VlHhE2zBqDw409nvOtitCFbnhhXNfatzb2+Gf2tX2s6YBa151CC/8+emJvvegXbW
# NudzYt8cFRom0PZ+fJRhhBfdSqCqr8QeOGJ8VYlmxFXqx1SdDSkTCSgpsskGqZwh
# /6umA1g4L7zeGBNngQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFCdNRaSL9AW8QvaQ
# 21WjRAXKN4M7MB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud
# HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js
# L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr
# BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv
# bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw
# MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw
# DgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQA9wc72lf/czDhp09T3
# PGAMOQhxl/x04jpE7t39FeqQSn2Up6DVzhgwnzCqY3NIhLtUaWrd7NxvrhZDca+J
# 4xzvrRQNPHeRQpnJVeHsyTu53gTBlUB1TRI6OnZt/AVmR9oMJ/NBOqB+d+SOb8Px
# 6zRgRwk62sFkOkB5lig/DMnYEeR/amW9Hdo8vXcKmaa/DbSOAHSdfZFt+iqMZfNl
# kEOn71/RAKTNv4Qpq/2FhcjMMmSkIhshBdBVB0VjmkwFfhVUf5TTuLJ9sDR4EyCv
# OZJ3B6g7Iw6WjQxycjwkfzsVMTpfusJ5SwdOHL8yGPWZOePjwa8ISXWs6kiVK/6S
# 0/JVb1LpxpyYKREQjnU/5OecKt2OXlHdwFWZrwAi98RPZa6EExcb/LGLf10tNHju
# 1eTlohY0jzNZQ0BDgSuMZgMU+8EEjtMQMIDnlPGEUON7LHXHH0KL0FA01PEWVZKr
# r/LUOuuDTNFzw543FPMp4gkCIFlKdRuciR1IXOk+Xse6rj9tJFYgVn+44BHou2XQ
# e5RX30ef3AQWa0mxyGDqJzGsV3X5+bNQeMV88iWulJPq5sgnGG9O/H1/HH4HsO9Z
# KGX/WrJpQmFuQrTOR49XjveaC0xaFmGsNg+RhbtD5qTkn+ISDvw0IJ/E/VXNdz/y
# Wgol6r507hT8sAMupnhkF2uw1DCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA
# AAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVow
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUA
# A4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX
# 9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1q
# UoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8d
# q6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byN
# pOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2k
# rnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4d
# Pf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgS
# Uei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8
# QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6Cm
# gyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF
# ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQID
# AQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU
# KqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1
# GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0
# bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA
# QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL
# j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p
# Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
# LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwU
# tj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN
# 3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU
# 5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5
# KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGy
# qVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB6
# 2FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltE
# AY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFp
# AUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcd
# FYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRb
# atGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd
# VTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
# T3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkUwMDItMDVFMC1E
# OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw
# BwYFKw4DAhoDFQC3v9iSO22xob7ZxN5dXCEq+9Iv/6CBgzCBgKR+MHwxCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7fcRXDAiGA8y
# MDI2MDcwNzA1NDcwOFoYDzIwMjYwNzA4MDU0NzA4WjB0MDoGCisGAQQBhFkKBAEx
# LDAqMAoCBQDt9xFcAgEAMAcCAQACAiV/MAcCAQACAhmBMAoCBQDt+GLcAgEAMDYG
# CisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEA
# AgMBhqAwDQYJKoZIhvcNAQELBQADggEBABwf3imr2ceWi5WrEwYbCTGFwbRk5+8D
# nax7PzHU8ByzJM5wRgJW7KuqdfWqQhuMkBVHueh5uiADuo15LgldEx8j0lmcOqs8
# HsbmojbJRLEmy7uJqve5Sa+uqW8GTeBJ2dtnMIJnqG8MsUfLXCFhLs/ev+BZdnf5
# BEt4IW7MrvJ7KxDgAf3rXE3QpNOFuOTRkj8ojSv3jCcfX3nIzVplREALUV/9vrhb
# /wx9t3IKRyRSC8YnNjIAPuWasFRfkdDOqCfFDeBgCzXiHzVl7P6R911CrsNoZ4BJ
# 0rDLYdHtsehpPklLeX7loM/3TWKQwCOOSn3sI7belY+ANBKwtoAadX4xggQNMIIE
# CQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYw
# JAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAikO1WQq
# tJfyGgABAAACKTANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqG
# SIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCCvXXtpwd8cOPX4WOWQL0HT31sG7umb
# ZPmA2iIGw7rbjjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EILfKPfEitvD/
# lSvEumxqPkkeOEtgkmKFEVMuel9oOrqSMIGYMIGApH4wfDELMAkGA1UEBhMCVVMx
# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgUENBIDIwMTACEzMAAAIpDtVkKrSX8hoAAQAAAikwIgQgM8J17N/nNlxR
# MfCMpkDt6Jy2vI6pxUSgxvAHWAlRlR0wDQYJKoZIhvcNAQELBQAEggIAED5h1O1P
# EIZKOt+9+PmnwgY6HqWpMQUJviQx6PFwI41kvesPhhrK61LZqoOIoaTYBer0154o
# LEwvlbALMMNotYmGe7KfS3kZbJrgQHtTnxdYDFno9gkAzcyvglgR4bdRSZmIkzz6
# hoUT14mHnVZ/kkaGt3WNqzdrmbVsD03PVhqa059Ywpyr88tWzbkRrqT8shQup4Xb
# WwukNs/4D+Gbc6oSnkWIdcx5KJjOK7NeGRu9UH573pOChoePf5N4WitC6b130+wy
# qZ4WUHm3MZSQ3mWnzCb+QPp0uS8wbcw67EeduA4wjiaIVpl5AxmKTdcYfCXQ+On5
# 4AcIh9yXPdRqwcLmqq3RNNThTsetcusNRRLJx3A4o0Cb0ZCkGo8qfIIgdLG1ohKg
# QuI14Enk2paq77kKTCq3OVmIq8sCiXLAbBDkAFlY+IH/m95vX95wAJiAhIWLyVSa
# 0gSHJ3YhEey/xzWAEYBJp9m+8zCGg8KZfAtT79ccozelI0MDJeBELPHNlgPplEze
# odJeYbtYOyJdJCGNrTj8GDPgmZSTXVUJXgHWAMCm10h81Xh2DQEWvRG2usIN3S3k
# qXnMvSIogdcB5Bd2CwuZ/q9uMWCGsqaERsQWeAdHr+QArHdV6xchb1piLP8BBRoP
# dVNOuv8NhS0MjC0BnyoDhJ+dejjYAs80jCQ=
# SIG # End signature block