Public/Start-NSPMDETroubleshooter.ps1

function Start-NSPMDETroubleshooter {
    <#
    .SYNOPSIS
        The MDE Troubleshooter menu: gather the evidence that Microsoft Defender for Endpoint's EDR
        sensor (MsSense.exe) collides with a line-of-business app, for an EDR exclusion request -
        Defender status, crash/hang events, ProcMon filters, a capture, the collision analysis, and
        the JSON hand-back.
 
    .DESCRIPTION
        Run it on the machine where the app fails (over a remote session is fine). Typical flow:
          1 status, 2 crash events (pick the app), 3 filters, 4 start capture -> the user
          reproduces the failure -> 5 stop and analyze, 7 open in ProcMon for screenshots,
          8 write the hand-back.
        Relaunches elevated if needed. Captures go to the MDE work folder
        (%ProgramData%\NSP\Toolkit\MDE\Captures) unless -OutputRoot says otherwise; answers
        (company, app, processes, data folders) are remembered between runs.
 
    .PARAMETER OutputRoot
        Folder for captures, filters and the crash-event CSVs (a drive with room to spare).
 
    .PARAMETER SeedAnswersJson
        Answers to merge in (Company, ApplicationName, ProcessNames, WatchPaths, LookbackDays).
 
    .PARAMETER SeedOnly
        Merge -SeedAnswersJson and return without opening the menu.
 
    .PARAMETER NoElevate
        Do not relaunch elevated.
 
    .EXAMPLE
        Start-NSPMDETroubleshooter
 
    .EXAMPLE
        Start-NSPMDETroubleshooter -OutputRoot 'D:\MDECaptures'
    #>

    [CmdletBinding()]
    param(
        [string]$OutputRoot,
        [string]$SeedAnswersJson,
        [switch]$SeedOnly,
        [switch]$NoElevate
    )

    Use-NSPMDEDependency

    if ($SeedOnly) {
        $null = Import-NSPToolSeedAnswers -Tool MDE -SeedAnswersJson $SeedAnswersJson
        return
    }

    if (-not $NoElevate) {
        $manifest = (Join-Path $script:ModuleRoot 'NSP.Win.MDE.psd1').Replace("'", "''")
        $relaunch = "Import-Module '$manifest'; Start-NSPMDETroubleshooter"
        if ($PSBoundParameters.ContainsKey('OutputRoot')) { $relaunch += " -OutputRoot '$($OutputRoot.Replace("'", "''"))'" }
        if (Invoke-NSPElevated -Command $relaunch -NoExit) { return }
    }
    $saved = Import-NSPToolSeedAnswers -Tool MDE -SeedAnswersJson $SeedAnswersJson
    $savedValue = { param($Name) if ($saved -and $saved.PSObject.Properties[$Name]) { $saved.$Name } else { $null } }

    if (-not $OutputRoot) { $OutputRoot = [string](& $savedValue 'CaptureFolder') }
    if (-not $OutputRoot) { $OutputRoot = Join-Path (Get-NSPToolWorkPath -Tool MDE -Create) 'Captures' }
    if (-not (Test-Path -LiteralPath $OutputRoot)) { New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null }

    $version = [string]$ExecutionContext.SessionState.Module.Version
    $days = [int](& $savedValue 'LookbackDays'); if ($days -le 0) { $days = 14 }
    $script:MDESession = [pscustomobject]@{
        VersionLabel    = (Get-NSPToolVersionStatus -ToolKey 'NSP.Win.MDE' -Version $version).Label
        CaptureDir      = (Resolve-Path -LiteralPath $OutputRoot).ProviderPath
        Company         = [string](& $savedValue 'Company')
        ApplicationName = [string](& $savedValue 'ApplicationName')
        ProcessNames    = @(& $savedValue 'ProcessNames' | Where-Object { $_ })
        WatchPaths      = @(& $savedValue 'WatchPaths' | Where-Object { $_ })
        LookbackDays    = $days
        Status          = $null
        CrashEvents     = $null
        Candidates      = $null
        ProcmonPath     = $null
        Filters         = $null
        Capture         = $null
        Collisions      = $null
    }
    Set-NSPConsoleMaximized -ErrorAction SilentlyContinue
    $script:MDESession.Status = Get-NSPMDEStatus
    $script:MDESession.ProcmonPath = Find-NSPProcmon -WarningAction SilentlyContinue

    $choices = @(
        [pscustomobject]@{ Section = 'Evidence'; Label = 'Defender for Endpoint status'; Value = 'Status'; Help = 'Sensor version and onboarding, AV mode, tamper protection, exclusions.' }
        [pscustomobject]@{ Label = 'Crash and hang events'; Value = 'Crashes'; Help = 'Find the app executables that fail (Application log).' }
        [pscustomobject]@{ Label = 'ProcMon filters'; Value = 'Filter'; Help = 'App executables and data folders to watch.' }
        [pscustomobject]@{ Section = 'Capture'; Label = 'Start capture'; Value = 'Start'; Help = 'ProcMon runs minimized; the user reproduces the failure.' }
        [pscustomobject]@{ Label = 'Stop capture and analyze'; Value = 'Stop'; Help = 'Who held the file at each sharing violation: MsSense (EDR) or MsMpEng (AV)?' }
        [pscustomobject]@{ Label = 'Analyze an existing capture'; Value = 'Analyze'; Help = 'A .pml or ProcMon .csv from earlier.' }
        [pscustomobject]@{ Label = 'Open the capture in ProcMon'; Value = 'View'; Help = 'With a filter loaded, for screenshots in the support case.' }
        [pscustomobject]@{ Section = 'Hand back'; Label = 'Write the hand-back JSON'; Value = 'Handoff'; Help = '<Company>_MDE_Response.json for the EDR exclusion request.' }
        [pscustomobject]@{ Key = 'F'; Label = 'Capture folder'; Value = 'Folder'; Help = 'Where captures go (pick a drive with room).' }
    )

    while ($true) {
        Show-NSPMDEDashboard
        $choice = Read-NSPMenu -Choices $choices -CancelLabel 'Quit'
        if ($null -eq $choice) {
            $capture = $script:MDESession.Capture
            if ($capture -and -not $capture.Ended -and (Get-Process -Name 'Procmon', 'Procmon64', 'Procmon64a' -ErrorAction SilentlyContinue)) {
                if (Read-NSPConfirm -Prompt 'A capture is still running. Stop it before quitting?' -DefaultYes) { $null = $capture | Stop-NSPProcmonCapture -Confirm:$false }
            }
            Write-NSPConsoleLine 'Goodbye.' -Role Muted
            return
        }
        switch ($choice) {
            'Status' { Invoke-NSPMDEMenuStatus }
            'Crashes' { Invoke-NSPMDEMenuCrashes }
            'Filter' { Invoke-NSPMDEMenuFilter }
            'Start' { Invoke-NSPMDEMenuStart }
            'Stop' { Invoke-NSPMDEMenuStop }
            'Analyze' { Invoke-NSPMDEMenuAnalyze }
            'View' { Invoke-NSPMDEMenuView }
            'Handoff' { Invoke-NSPMDEMenuHandoff }
            'Folder' { Invoke-NSPMDEMenuFolder }
        }
    }
}