completers/wpr_completer/wpr_completer.ps1
|
<# .SYNOPSIS Registers a native PowerShell argument completer for wpr. .DESCRIPTION Provides a static-first native completer for `wpr` and `wpr.exe` using the local help surface and safe local discovery for profile names and process data. The completer covers: - top-level WPR commands - `-help` topic completion - profile-name completion for profile-bearing commands - local file and directory completion for path-bearing commands - process name and PID completion for heap and snapshot commands - enum and placeholder values for common non-path slots The script keeps its top level compatible with `Import-CompleterScript`. #> Set-StrictMode -Version Latest function New-WprCompletionResult { param( [string]$CompletionText, [string]$ResultType = 'ParameterValue', [string]$ToolTip, [string]$ListItemText ) if ([string]::IsNullOrWhiteSpace($ListItemText)) { $ListItemText = $CompletionText } if ([string]::IsNullOrWhiteSpace($ToolTip)) { $ToolTip = $ListItemText } [System.Management.Automation.CompletionResult]::new( $CompletionText, $ListItemText, $ResultType, $ToolTip ) } function Get-WprCommandSpecs { if (Get-Variable -Name WprCommandSpecs -Scope Script -ErrorAction Ignore) { return $script:WprCommandSpecs } $script:WprCommandSpecs = @( [pscustomobject]@{ Token='/?'; Description='Show WPR help.'; Options=@(); ValueKind=$null } [pscustomobject]@{ Token='-?'; Description='Show WPR help.'; Options=@(); ValueKind=$null } [pscustomobject]@{ Token='-help'; Description='Show WPR help for a specific topic.'; Options=@(); ValueKind='HelpTopic' } [pscustomobject]@{ Token='-start'; Description='Start WPR recording with one or more profiles.'; Options=@('-start','-shutdown','-filemode','-recordtempto','-container','-host','-instancename'); ValueKind='Start' } [pscustomobject]@{ Token='-stop'; Description='Stop WPR recording and merge it into the given file.'; Options=@('-skipPdbGen','-force','-compress','-container','-host','-instancename'); ValueKind='Stop' } [pscustomobject]@{ Token='-cancel'; Description='Cancel the recording initiated through WPR.'; Options=@('-container','-host','-instancename'); ValueKind='Cancel' } [pscustomobject]@{ Token='-merge'; Description='Merge one or more ETL files into the given file.'; Options=@('-skipPdbGen','-compress','-supresspii','-mergeonly','-injectonly'); ValueKind='Merge' } [pscustomobject]@{ Token='-status'; Description='Display the status of the active WPR recording.'; Options=@('-details','-container','-host','-instancename'); ValueKind='Status' } [pscustomobject]@{ Token='-profiles'; Description='Enumerate built-in or custom profile names.'; Options=@(); ValueKind='Profiles' } [pscustomobject]@{ Token='-profiledetails'; Description='Display details about one or more profiles.'; Options=@('-filemode'); ValueKind='ProfileDetails' } [pscustomobject]@{ Token='-exportprofile'; Description='Export one or more built-in profiles to a .wprp file.'; Options=@('-filemode'); ValueKind='ExportProfile' } [pscustomobject]@{ Token='-providers'; Description='Display provider information.'; Options=@(); ValueKind='Providers' } [pscustomobject]@{ Token='-marker'; Description='Fire an event marker.'; Options=@('-flush'); ValueKind='Marker' } [pscustomobject]@{ Token='-flush'; Description='Flush logging sessions initiated through WPR.'; Options=@('-container','-host','-instancename'); ValueKind='Flush' } [pscustomobject]@{ Token='-capturestateondemand'; Description='Capture provider states in the current recording.'; Options=@('-instancename'); ValueKind='CaptureStateOnDemand' } [pscustomobject]@{ Token='-addboot'; Description='Enable Autologger for the given profile.'; Options=@('-addboot','-filemode','-recordtempto','-export'); ValueKind='AddBoot' } [pscustomobject]@{ Token='-stopboot'; Description='Stop boot recording and merge it into the given file.'; Options=@(); ValueKind='StopBoot' } [pscustomobject]@{ Token='-cancelboot'; Description='Cancel boot recording configured by addboot.'; Options=@(); ValueKind='CancelBoot' } [pscustomobject]@{ Token='-heaptracingconfig'; Description='Enable, disable, or query heap tracing for a process or store app.'; Options=@(); ValueKind='HeapTracingConfig' } [pscustomobject]@{ Token='-snapshotconfig'; Description='Enable, disable, or query snapshot capture for a process.'; Options=@('-name','-pid'); ValueKind='SnapshotConfig' } [pscustomobject]@{ Token='-enableperiodicsnapshot'; Description='Enable periodic snapshot for specified PIDs.'; Options=@(); ValueKind='EnablePeriodicSnapshot' } [pscustomobject]@{ Token='-disableperiodicsnapshot'; Description='Disable periodic snapshot.'; Options=@(); ValueKind='DisablePeriodicSnapshot' } [pscustomobject]@{ Token='-singlesnapshot'; Description='Take an on-demand snapshot for specified PIDs.'; Options=@(); ValueKind='SingleSnapshot' } [pscustomobject]@{ Token='-pmcsources'; Description='Query hardware counters available on the system.'; Options=@(); ValueKind='None' } [pscustomobject]@{ Token='-pmcsessions'; Description='Query sessions using hardware counters.'; Options=@(); ValueKind='None' } [pscustomobject]@{ Token='-setprofint'; Description='Set sampled profile interval.'; Options=@(); ValueKind='ProfileInterval' } [pscustomobject]@{ Token='-profint'; Description='Query the current profile interval.'; Options=@(); ValueKind='None' } [pscustomobject]@{ Token='-resetprofint'; Description='Reset profile interval to its default value.'; Options=@(); ValueKind='ResetProfileInterval' } [pscustomobject]@{ Token='-purgecache'; Description='Purge the dynamic symbols cache.'; Options=@(); ValueKind='None' } [pscustomobject]@{ Token='-log'; Description='Configure WPR debug logging to the event log.'; Options=@(); ValueKind='LogMode' } [pscustomobject]@{ Token='-disablepagingexecutive'; Description='Turn Disable Paging Executive on or off.'; Options=@(); ValueKind='PagingExecutive' } ) $script:WprCommandSpecs } function Get-WprCommandLookup { if (Get-Variable -Name WprCommandLookup -Scope Script -ErrorAction Ignore) { return $script:WprCommandLookup } $lookup = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($spec in @(Get-WprCommandSpecs)) { $lookup[$spec.Token] = $spec } $script:WprCommandLookup = $lookup $script:WprCommandLookup } function Get-WprHelpTopics { @('start', 'stop', 'status', 'profiles', 'providers', 'tracing', 'boottrace', 'heap', 'hardwarecounter', 'advanced') } function Get-WprStatusTerms { @('profiles', 'collectors') } function Get-WprProviderSelectors { @('Installed', 'I', 'PerfTrack', 'PT', 'UTC') } function Get-WprSnapshotOptions { @('Heap') } function Get-WprLogModes { @('enabled', 'disabled', 'remove') } function Get-WprPagingModes { @('on', 'off') } function Get-WprEnableDisableValues { @('enable', 'disable') } function Get-WprTokenState { param( [System.Management.Automation.Language.CommandAst]$CommandAst, [int]$CursorPosition ) # Words come from the parser, so quoted words (ASCII or typographic, with escapes) stay # whole; an unterminated quoted word is one element running to the cursor. $tokens = New-Object System.Collections.Generic.List[string] $currentToken = '' foreach ($element in $CommandAst.CommandElements) { $extent = $element.Extent if ($extent.StartOffset -ge $CursorPosition) { break } if ($CursorPosition -le $extent.EndOffset) { $currentToken = $extent.Text.Substring(0, $CursorPosition - $extent.StartOffset) break } $tokens.Add($extent.Text) } [pscustomobject]@{ TokensBeforeCurrent = @($tokens); CurrentToken = $currentToken } } function Get-WprArgumentsFromTokenState { param([pscustomobject]$TokenState) $tokensBeforeCurrent = @($TokenState.TokensBeforeCurrent) $currentArgument = if ($null -eq $TokenState.CurrentToken) { '' } else { $TokenState.CurrentToken } if ($tokensBeforeCurrent.Count -gt 0) { $argumentsBeforeCurrent = @($tokensBeforeCurrent | Select-Object -Skip 1) } else { $argumentsBeforeCurrent = @() } if ($tokensBeforeCurrent.Count -eq 0 -and $currentArgument -match '^(?i)wpr(?:\.exe)?$') { $currentArgument = '' } [pscustomobject]@{ ArgumentsBeforeCurrent = $argumentsBeforeCurrent CurrentArgument = $currentArgument } } function ConvertFrom-WprTypedWord { # The value of a typed word. A word opened with a quote (ASCII or typographic) is read by # the PowerShell tokenizer, which drops the quotes and undoes that quote style's escapes. param([string]$Value) if ($Value -notmatch '^[''"\u2018-\u201E]') { return $Value } $tokens = $null $parseErrors = $null [void][System.Management.Automation.Language.Parser]::ParseInput($Value, [ref]$tokens, [ref]$parseErrors) $tokens[0].Value } function Get-WprTypedQuote { param([string]$Value) if ($Value -match '^[''"\u2018-\u201E]') { return $Value.Substring(0, 1) } '' } function ConvertTo-WprQuotedValue { # Renders a value as one PowerShell argument: bare when safe and no quote was typed, # otherwise in the quote the user typed (single by default). PowerShell reads ' and # U+2018-U+201B as single quotes and " and U+201C-U+201E as double quotes. param( [string]$Value, [string]$QuoteChar = '' ) if ([string]::IsNullOrEmpty($Value)) { return $Value } if (-not $QuoteChar) { # A bare word opened with a dash would be read as a parameter. if ($Value -notmatch '[\s{}();,|&<>''"`$@#\u2018-\u201E]|^[-\u2013-\u2015]') { return $Value } $QuoteChar = "'" } if ($QuoteChar -match '^[''\u2018-\u201B]$') { return $QuoteChar + ($Value -replace '([''\u2018-\u201B])', '$1$1') + $QuoteChar } $QuoteChar + ($Value -replace '([`"$\u201C-\u201E])', '`$1') + $QuoteChar } function Get-WprPathCompletions { param( [string]$CurrentWord, [ValidateSet('File','Directory','Any')] [string]$Kind = 'Any', [string]$ToolTip = 'Path value.', [string]$Placeholder = '<path>' ) $typedValue = ConvertFrom-WprTypedWord -Value $CurrentWord $quoteChar = Get-WprTypedQuote -Value $CurrentWord $results = New-Object System.Collections.Generic.List[object] # The typed directory part (through the last separator, or a bare drive 'C:') is kept # verbatim so a typed .\ or ./ prefix and separator style survive. $typedParent = '' $leaf = if ($null -eq $typedValue) { '' } else { $typedValue } $lastSeparator = $leaf.LastIndexOfAny([char[]]@('\', '/')) if ($lastSeparator -ge 0) { $typedParent = $leaf.Substring(0, $lastSeparator + 1) $leaf = $leaf.Substring($lastSeparator + 1) } elseif ($leaf -match '^[A-Za-z]:') { $typedParent = $leaf.Substring(0, 2) $leaf = $leaf.Substring(2) } $parentPath = if ($typedParent) { $typedParent } else { '.' } $separator = if ($typedParent.EndsWith('/')) { '/' } else { '\' } $items = @(Get-ChildItem -LiteralPath $parentPath -ErrorAction Ignore) foreach ($item in $items) { # Directories are always kept so a file slot can be steered into a subfolder; # only leaf items are filtered by Kind. if ($Kind -eq 'Directory' -and -not $item.PSIsContainer) { continue } if (-not [string]::IsNullOrWhiteSpace($leaf) -and -not $item.Name.StartsWith($leaf, [System.StringComparison]::OrdinalIgnoreCase)) { continue } # A bare relative name opening with a dash gets the current-directory prefix, as # PowerShell's own file completion does, so it is never read as a parameter. $candidate = if (-not $typedParent -and $item.Name -match '^[-\u2013-\u2015]') { '.' + [System.IO.Path]::DirectorySeparatorChar + $item.Name } else { $typedParent + $item.Name } if ($item.PSIsContainer) { $candidate += $separator } $completionText = ConvertTo-WprQuotedValue -Value $candidate -QuoteChar $quoteChar [void]$results.Add((New-WprCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip $item.FullName -ListItemText $candidate)) } if ($results.Count -eq 0) { $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $Placeholder } else { $CurrentWord } [void]$results.Add((New-WprCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip $ToolTip)) } @($results.ToArray()) } function Get-WprPlaceholderCompletions { param( [string]$CurrentWord, [string]$Placeholder, [string]$ToolTip ) if ([string]::IsNullOrWhiteSpace($CurrentWord)) { return @( New-WprCompletionResult -CompletionText $Placeholder -ResultType 'ParameterValue' -ToolTip $ToolTip ) } @( New-WprCompletionResult -CompletionText $CurrentWord -ResultType 'ParameterValue' -ToolTip $ToolTip ) } function Get-WprEnumCompletions { param( [string]$CurrentWord, [string[]]$Values, [string]$ToolTip ) $typedValue = ConvertFrom-WprTypedWord -Value $CurrentWord $quoteChar = Get-WprTypedQuote -Value $CurrentWord $results = New-Object System.Collections.Generic.List[object] foreach ($value in @($Values)) { if (-not [string]::IsNullOrWhiteSpace($typedValue) -and -not $value.StartsWith($typedValue, [System.StringComparison]::OrdinalIgnoreCase)) { continue } $completionText = ConvertTo-WprQuotedValue -Value $value -QuoteChar $quoteChar [void]$results.Add((New-WprCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip $ToolTip -ListItemText $value)) } if ($results.Count -eq 0) { $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { '<value>' } else { $CurrentWord } [void]$results.Add((New-WprCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip $ToolTip)) } @($results.ToArray()) } function Get-WprUniqueResults { param([object[]]$Results) $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $output = New-Object System.Collections.Generic.List[object] foreach ($result in @($Results)) { if ($null -eq $result) { continue } if ($seen.Add($result.CompletionText)) { [void]$output.Add($result) } } @($output.ToArray()) } function Get-WprCommandCompletions { param([string]$CurrentWord) foreach ($command in @(Get-WprCommandSpecs)) { if (-not [string]::IsNullOrWhiteSpace($CurrentWord) -and -not $command.Token.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) { continue } New-WprCompletionResult -CompletionText $command.Token -ResultType 'ParameterName' -ToolTip $command.Description } } function Get-WprProfileNames { if (Get-Variable -Name WprProfileCache -Scope Script -ErrorAction Ignore) { $cache = $script:WprProfileCache if (((Get-Date) - $cache.UpdatedAt).TotalSeconds -lt 30) { return $cache.Values } } $values = New-Object System.Collections.Generic.List[string] if (Get-Command -Name wpr.exe -ErrorAction Ignore) { try { foreach ($line in @(& wpr.exe -profiles 2>$null)) { $text = [string]$line $match = [regex]::Match($text, '^\s*(?<name>\S+)\s{2,}.+$') if (-not $match.Success) { continue } $name = $match.Groups['name'].Value if ([string]::IsNullOrWhiteSpace($name)) { continue } [void]$values.Add($name) [void]$values.Add($name + '.light') [void]$values.Add($name + '.verbose') } } catch { } } $result = @($values | Sort-Object -Unique) $script:WprProfileCache = [pscustomobject]@{ UpdatedAt = Get-Date; Values = $result } @($result) } function Get-WprProfileSpecCompletions { param([string]$CurrentWord) $typedValue = ConvertFrom-WprTypedWord -Value $CurrentWord if ($typedValue -match '^(?<path>.+!)(?<profile>[^!]*)$') { $prefix = $matches['path'] $profilePrefix = $matches['profile'] $quoteChar = Get-WprTypedQuote -Value $CurrentWord $results = New-Object System.Collections.Generic.List[object] foreach ($profile in @(Get-WprProfileNames)) { if (-not [string]::IsNullOrWhiteSpace($profilePrefix) -and -not $profile.StartsWith($profilePrefix, [System.StringComparison]::OrdinalIgnoreCase)) { continue } # The file part of file.wprp!Profile may need quoting like any other path. $completionText = ConvertTo-WprQuotedValue -Value ($prefix + $profile) -QuoteChar $quoteChar [void]$results.Add((New-WprCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip 'WPR profile specification.' -ListItemText ($prefix + $profile))) } if ($results.Count -gt 0) { return @($results.ToArray()) } } if ($typedValue -match '[\\/]' -or $typedValue.EndsWith('.wprp', [System.StringComparison]::OrdinalIgnoreCase)) { return @(Get-WprPathCompletions -CurrentWord $CurrentWord -Kind 'File' -ToolTip 'Path to a .wprp profile definition file.' -Placeholder '<profile.wprp>') } $quoteChar = Get-WprTypedQuote -Value $CurrentWord $results = New-Object System.Collections.Generic.List[object] foreach ($profile in @(Get-WprProfileNames)) { if (-not [string]::IsNullOrWhiteSpace($typedValue) -and -not $profile.StartsWith($typedValue, [System.StringComparison]::OrdinalIgnoreCase)) { continue } $completionText = ConvertTo-WprQuotedValue -Value $profile -QuoteChar $quoteChar [void]$results.Add((New-WprCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip 'WPR profile specification.' -ListItemText $profile)) } if ($results.Count -eq 0) { $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { '<profile>' } else { $CurrentWord } [void]$results.Add((New-WprCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip 'WPR profile specification.')) } @($results.ToArray()) } function Get-WprPlusListCompletions { param([string]$CurrentWord) $typedValue = ConvertFrom-WprTypedWord -Value $CurrentWord $prefix = '' $segment = $typedValue $lastPlus = if ([string]::IsNullOrEmpty($typedValue)) { -1 } else { $typedValue.LastIndexOf('+') } if ($lastPlus -ge 0) { $prefix = $typedValue.Substring(0, $lastPlus + 1) $segment = $typedValue.Substring($lastPlus + 1) } $quoteChar = Get-WprTypedQuote -Value $CurrentWord $results = New-Object System.Collections.Generic.List[object] foreach ($profile in @(Get-WprProfileNames)) { if (-not [string]::IsNullOrWhiteSpace($segment) -and -not $profile.StartsWith($segment, [System.StringComparison]::OrdinalIgnoreCase)) { continue } $completionText = ConvertTo-WprQuotedValue -Value ($prefix + $profile) -QuoteChar $quoteChar [void]$results.Add((New-WprCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip 'WPR profile list item.' -ListItemText ($prefix + $profile))) } if ($results.Count -eq 0) { $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { '<profile[+profile...]>' } else { $CurrentWord } [void]$results.Add((New-WprCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip 'WPR profile list item.')) } @($results.ToArray()) } function Get-WprProcessNames { if (Get-Variable -Name WprProcessNameCache -Scope Script -ErrorAction Ignore) { $cache = $script:WprProcessNameCache if (((Get-Date) - $cache.UpdatedAt).TotalSeconds -lt 10) { return $cache.Values } } $processes = @(Get-Process -ErrorAction SilentlyContinue) $values = @( @($processes | ForEach-Object { $_.ProcessName + '.exe' }) + @($processes | ForEach-Object { $_.ProcessName }) ) | Sort-Object -Unique $script:WprProcessNameCache = [pscustomobject]@{ UpdatedAt = Get-Date; Values = $values } @($values) } function Get-WprProcessIds { if (Get-Variable -Name WprProcessIdCache -Scope Script -ErrorAction Ignore) { $cache = $script:WprProcessIdCache if (((Get-Date) - $cache.UpdatedAt).TotalSeconds -lt 10) { return $cache.Values } } $values = @( Get-Process -ErrorAction SilentlyContinue | ForEach-Object { [string]$_.Id } ) | Sort-Object -Unique $script:WprProcessIdCache = [pscustomobject]@{ UpdatedAt = Get-Date; Values = $values } @($values) } function Get-WprOptionCompletions { param( [string]$CurrentWord, [string[]]$Options ) foreach ($option in @($Options)) { if (-not [string]::IsNullOrWhiteSpace($CurrentWord) -and -not $option.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) { continue } New-WprCompletionResult -CompletionText $option -ResultType 'ParameterName' -ToolTip $option } } function Get-WprCommonParameterValueCompletionList { param( [string]$PreviousToken, [string]$CurrentWord ) # -container and -instancename are the common parameters wpr documents for every # session-bound command; their values are opaque, so they complete as placeholders. switch ($PreviousToken) { '-container' { return @(Get-WprPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<container-id>' -ToolTip 'Container ID.') } '-instancename' { return @(Get-WprPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<instance-name>' -ToolTip 'Unique WPR instance name.') } } @() } function Test-WprProfileOperandPresent { param( [string[]]$TokensAfterCommand, [string[]]$ValueOptions ) # A token that is neither an option nor an option's value is a profile operand; # once one is present, further profiles must be reintroduced with -start/-addboot. $tokens = @($TokensAfterCommand) for ($index = 0; $index -lt $tokens.Count; $index++) { $token = $tokens[$index] if ($token.StartsWith('-')) { if ($token -eq '-shutdown') { if (($index + 1) -lt $tokens.Count -and $tokens[$index + 1] -eq 'KSR') { $index++ } } elseif ($token -in $ValueOptions) { $index++ } continue } return $true } $false } function Get-WprEnumOrPlaceholderCompletions { param( [string]$CurrentWord, [string[]]$EnumValues, [string]$Placeholder, [string]$ToolTip ) $results = @(Get-WprEnumCompletions -CurrentWord $CurrentWord -Values $EnumValues -ToolTip $ToolTip) if ($results.Count -gt 0) { return $results } @(Get-WprPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder $Placeholder -ToolTip $ToolTip) } function Complete-Wpr { param( [string]$wordToComplete, [System.Management.Automation.Language.CommandAst]$commandAst, [int]$cursorPosition ) $commandLookup = Get-WprCommandLookup $tokenState = Get-WprTokenState -CommandAst $commandAst -CursorPosition $cursorPosition $argumentState = Get-WprArgumentsFromTokenState -TokenState $tokenState $hasTrailingSpace = [string]::IsNullOrEmpty($wordToComplete) if ($hasTrailingSpace -and -not [string]::IsNullOrEmpty($argumentState.CurrentArgument)) { $currentWord = '' $argumentsBeforeCurrent = @($argumentState.ArgumentsBeforeCurrent + $argumentState.CurrentArgument) } else { $currentWord = if ($null -eq $argumentState.CurrentArgument) { '' } else { $argumentState.CurrentArgument } $argumentsBeforeCurrent = @($argumentState.ArgumentsBeforeCurrent) } $activeCommand = $null foreach ($token in @($argumentsBeforeCurrent)) { if ($commandLookup.ContainsKey($token)) { $activeCommand = $commandLookup[$token] break } } if ($null -eq $activeCommand) { if ([string]::IsNullOrWhiteSpace($currentWord) -or $currentWord.StartsWith('-') -or $currentWord.StartsWith('/')) { return @(Get-WprCommandCompletions -CurrentWord $currentWord) } return @() } # The lookup above is case-insensitive, so the index scan must be too (-Stop, -HeapTracingConfig). $commandIndex = -1 for ($index = 0; $index -lt $argumentsBeforeCurrent.Count; $index++) { if ([string]::Equals($argumentsBeforeCurrent[$index], $activeCommand.Token, [System.StringComparison]::OrdinalIgnoreCase)) { $commandIndex = $index break } } $tokensAfterCommand = @( if ($commandIndex -ge 0 -and $commandIndex -lt ($argumentsBeforeCurrent.Count - 1)) { $argumentsBeforeCurrent[($commandIndex + 1)..($argumentsBeforeCurrent.Count - 1)] } ) switch ($activeCommand.ValueKind) { 'HelpTopic' { if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprHelpTopics) -ToolTip 'WPR help topic.') } return @(Get-WprPlaceholderCompletions -CurrentWord $currentWord -Placeholder ' ' -ToolTip 'No further arguments are valid after -help <topic>.') } 'Start' { $valueOptions = @('-start', '-recordtempto', '-container', '-instancename') if ($tokensAfterCommand.Count -gt 0) { $previousToken = $tokensAfterCommand[-1] if ($previousToken -in @('-start')) { return @(Get-WprProfileSpecCompletions -CurrentWord $currentWord) } if ($previousToken -eq '-recordtempto') { return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'Directory' -ToolTip 'Temporary recording folder.' -Placeholder '<temp-folder>') } if ($previousToken -eq '-shutdown' -and -not $currentWord.StartsWith('-')) { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values @('KSR') -ToolTip 'Trace for Kernel Soft Reboot (optional).') } $commonResults = @(Get-WprCommonParameterValueCompletionList -PreviousToken $previousToken -CurrentWord $currentWord) if ($commonResults.Count -gt 0) { return $commonResults } } # After the first profile operand every further profile needs -start, so only options remain. if ((-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('-')) -or (Test-WprProfileOperandPresent -TokensAfterCommand $tokensAfterCommand -ValueOptions $valueOptions)) { return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } return @(Get-WprProfileSpecCompletions -CurrentWord $currentWord) } 'AddBoot' { $valueOptions = @('-addboot', '-recordtempto', '-export') if ($tokensAfterCommand.Count -gt 0) { $previousToken = $tokensAfterCommand[-1] if ($previousToken -eq '-addboot') { return @(Get-WprProfileSpecCompletions -CurrentWord $currentWord) } if ($previousToken -eq '-recordtempto') { return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'Directory' -ToolTip 'Temporary recording folder.' -Placeholder '<temp-folder>') } if ($previousToken -eq '-export') { return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'File' -ToolTip 'Registry export file path.' -Placeholder '<file>') } } if ((-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('-')) -or (Test-WprProfileOperandPresent -TokensAfterCommand $tokensAfterCommand -ValueOptions $valueOptions)) { return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } return @(Get-WprProfileSpecCompletions -CurrentWord $currentWord) } 'Stop' { if ($tokensAfterCommand.Count -gt 0) { $commonResults = @(Get-WprCommonParameterValueCompletionList -PreviousToken $tokensAfterCommand[-1] -CurrentWord $currentWord) if ($commonResults.Count -gt 0) { return $commonResults } } if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'File' -ToolTip 'Recording output file.' -Placeholder '<recording.etl>') } if ($tokensAfterCommand.Count -eq 1 -and -not ($currentWord.StartsWith('-'))) { return @(Get-WprPlaceholderCompletions -CurrentWord $currentWord -Placeholder '<problem-description>' -ToolTip 'Problem description.') } return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } 'StopBoot' { if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'File' -ToolTip 'Recording output file.' -Placeholder '<recording.etl>') } return @(Get-WprPlaceholderCompletions -CurrentWord $currentWord -Placeholder '<problem-description>' -ToolTip 'Problem description.') } 'Cancel' { if ($tokensAfterCommand.Count -gt 0) { $commonResults = @(Get-WprCommonParameterValueCompletionList -PreviousToken $tokensAfterCommand[-1] -CurrentWord $currentWord) if ($commonResults.Count -gt 0) { return $commonResults } } return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } 'Merge' { if ($tokensAfterCommand.Count -gt 0) { $commonResults = @(Get-WprCommonParameterValueCompletionList -PreviousToken $tokensAfterCommand[-1] -CurrentWord $currentWord) if ($commonResults.Count -gt 0) { return $commonResults } } if (-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('-')) { return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'File' -ToolTip 'Trace file path.' -Placeholder '<trace.etl>') } 'Status' { if ($tokensAfterCommand.Count -gt 0) { $commonResults = @(Get-WprCommonParameterValueCompletionList -PreviousToken $tokensAfterCommand[-1] -CurrentWord $currentWord) if ($commonResults.Count -gt 0) { return $commonResults } } if (-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('-')) { return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } $results = New-Object System.Collections.Generic.List[object] foreach ($value in @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprStatusTerms) -ToolTip 'WPR status detail selector.')) { [void]$results.Add($value) } foreach ($value in @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options)) { [void]$results.Add($value) } return @(Get-WprUniqueResults -Results $results.ToArray()) } 'Profiles' { return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'File' -ToolTip 'Optional .wprp profile definition file.' -Placeholder '<profile.wprp>') } 'ProfileDetails' { if ($tokensAfterCommand.Count -eq 0 -or ($tokensAfterCommand.Count -gt 0 -and -not ($currentWord.StartsWith('-')))) { return @(Get-WprPlusListCompletions -CurrentWord $currentWord) } return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } 'ExportProfile' { if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprPlusListCompletions -CurrentWord $currentWord) } if ($tokensAfterCommand.Count -eq 1 -and -not ($currentWord.StartsWith('-'))) { return @(Get-WprPathCompletions -CurrentWord $currentWord -Kind 'File' -ToolTip 'Destination .wprp file.' -Placeholder '<file.wprp>') } return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } 'Providers' { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprProviderSelectors) -ToolTip 'Provider selector.') } 'Marker' { if (-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('-')) { return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } return @(Get-WprPlaceholderCompletions -CurrentWord $currentWord -Placeholder '<scenario-name>' -ToolTip 'Scenario marker name.') } 'Flush' { if ($tokensAfterCommand.Count -gt 0) { $commonResults = @(Get-WprCommonParameterValueCompletionList -PreviousToken $tokensAfterCommand[-1] -CurrentWord $currentWord) if ($commonResults.Count -gt 0) { return $commonResults } } return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } 'CaptureStateOnDemand' { if ($tokensAfterCommand.Count -gt 0) { $commonResults = @(Get-WprCommonParameterValueCompletionList -PreviousToken $tokensAfterCommand[-1] -CurrentWord $currentWord) if ($commonResults.Count -gt 0) { return $commonResults } } return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } 'HeapTracingConfig' { if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprProcessNames) -ToolTip 'Process name.') } return @(Get-WprEnumOrPlaceholderCompletions -CurrentWord $currentWord -EnumValues (Get-WprEnableDisableValues) -Placeholder '<package-or-mode>' -ToolTip 'Enable/disable value or app package metadata.') } 'SnapshotConfig' { if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprSnapshotOptions) -ToolTip 'Snapshot option.') } if ($tokensAfterCommand[-1] -eq '-name') { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprProcessNames) -ToolTip 'Process name.') } if ($tokensAfterCommand[-1] -eq '-pid') { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprProcessIds) -ToolTip 'Process ID.') } if (-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('-')) { return @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options) } # Right after the snapshot option the required {-name | -pid} selector is still open. if ($tokensAfterCommand.Count -eq 1) { $results = New-Object System.Collections.Generic.List[object] foreach ($value in @(Get-WprOptionCompletions -CurrentWord $currentWord -Options $activeCommand.Options)) { [void]$results.Add($value) } foreach ($value in @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprEnableDisableValues) -ToolTip 'Enable or disable snapshot capture.')) { [void]$results.Add($value) } return @(Get-WprUniqueResults -Results $results.ToArray()) } return @(Get-WprEnumOrPlaceholderCompletions -CurrentWord $currentWord -EnumValues (Get-WprEnableDisableValues) -Placeholder '<mode>' -ToolTip 'Enable or disable snapshot capture.') } 'EnablePeriodicSnapshot' { if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprSnapshotOptions) -ToolTip 'Snapshot option.') } if ($tokensAfterCommand.Count -eq 1) { return @(Get-WprPlaceholderCompletions -CurrentWord $currentWord -Placeholder '<interval-seconds>' -ToolTip 'Periodic snapshot interval in seconds.') } return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprProcessIds) -ToolTip 'Process ID.') } 'DisablePeriodicSnapshot' { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprSnapshotOptions) -ToolTip 'Snapshot option.') } 'SingleSnapshot' { if ($tokensAfterCommand.Count -eq 0) { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprSnapshotOptions) -ToolTip 'Snapshot option.') } return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprProcessIds) -ToolTip 'Process ID.') } 'ProfileInterval' { return @(Get-WprPlaceholderCompletions -CurrentWord $currentWord -Placeholder '<profiling-interval>' -ToolTip 'Sampling profile interval.') } 'ResetProfileInterval' { return @(Get-WprPlaceholderCompletions -CurrentWord $currentWord -Placeholder '<profile-source-name>' -ToolTip 'Optional profile source name.') } 'LogMode' { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprLogModes) -ToolTip 'WPR debug logging mode.') } 'PagingExecutive' { return @(Get-WprEnumCompletions -CurrentWord $currentWord -Values (Get-WprPagingModes) -ToolTip 'Disable Paging Executive mode.') } 'None' { return @() } } @() } Register-ArgumentCompleter -Native -CommandName @('wpr', 'wpr.exe') -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) Complete-Wpr -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition } |