Repository navigation
feat(harness): script paths from the pipeline, and a view for every result type #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -412,6 +412,65 @@ | |
| </ListEntries> | ||
| </ListControl> | ||
| </View> | ||
| <View> | ||
| <Name>IntuneScriptLab.RuleResult</Name> | ||
| <ViewSelectedBy> | ||
| <TypeName>IntuneScriptLab.RuleResult</TypeName> | ||
| </ViewSelectedBy> | ||
| <ListControl> | ||
| <ListEntries> | ||
| <ListEntry> | ||
| <ListItems> | ||
| <ListItem><PropertyName>Met</PropertyName></ListItem> | ||
| <ListItem><Label>Rule</Label><ScriptBlock>"$($_.Kind) $($_.Operation)$(if ($_.Operator) { " $($_.Operator) '$($_.Value)'" })"</ScriptBlock></ListItem> | ||
| <ListItem><PropertyName>Target</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Actual</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Reason</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Check32BitOn64System</PropertyName></ListItem> | ||
| </ListItems> | ||
| </ListEntry> | ||
| </ListEntries> | ||
| </ListControl> | ||
| </View> | ||
| <View> | ||
| <Name>IntuneScriptLab.ApplicabilityResult</Name> | ||
| <ViewSelectedBy> | ||
| <TypeName>IntuneScriptLab.ApplicabilityResult</TypeName> | ||
| </ViewSelectedBy> | ||
| <ListControl> | ||
| <ListEntries> | ||
| <ListEntry> | ||
| <ListItems> | ||
| <ListItem><PropertyName>Applicable</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Reason</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Applicability</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Details</PropertyName></ListItem> | ||
| <ListItem><Label>Checks</Label><ScriptBlock>($_.Checks | ForEach-Object { "$($_.Requirement): $(if ($_.Met) { 'met' } else { 'not met' })" }) -join "; "</ScriptBlock></ListItem> | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Checks array is summarised to one line per requirement so the default output stays a screen; the full objects are still on the property for anyone who wants them. |
||
| </ListItems> | ||
| </ListEntry> | ||
| </ListEntries> | ||
| </ListControl> | ||
| </View> | ||
| <View> | ||
| <Name>IntuneScriptLab.Diagnostic</Name> | ||
| <ViewSelectedBy> | ||
| <TypeName>IntuneScriptLab.Diagnostic</TypeName> | ||
| </ViewSelectedBy> | ||
| <ListControl> | ||
| <ListEntries> | ||
| <ListEntry> | ||
| <ListItems> | ||
| <ListItem><PropertyName>Path</PropertyName></ListItem> | ||
| <ListItem><Label>Size</Label><ScriptBlock>"{0:N1} MB" -f ($_.SizeBytes / 1MB)</ScriptBlock></ListItem> | ||
| <ListItem><PropertyName>Files</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Logs</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Registry</PropertyName></ListItem> | ||
| <ListItem><PropertyName>Timeline</PropertyName></ListItem> | ||
| </ListItems> | ||
| </ListEntry> | ||
| </ListEntries> | ||
| </ListControl> | ||
| </View> | ||
| <View> | ||
| <Name>IntuneScriptLab.RequirementResult</Name> | ||
| <ViewSelectedBy> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,8 @@ function Invoke-IntuneDetectionTest { | |
| [CmdletBinding()] | ||
| [OutputType('IntuneScriptLab.DetectionResult')] | ||
| param( | ||
| [Parameter(Mandatory, Position = 0)] | ||
| [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] | ||
| [Alias('FullName', 'PSPath')] | ||
| [string]$Path, | ||
|
|
||
| [ValidateSet('x86', 'x64', 'arm64')] | ||
|
|
@@ -28,62 +29,70 @@ function Invoke-IntuneDetectionTest { | |
|
|
||
| [switch]$EnforceSignatureCheck | ||
| ) | ||
| if ($Credential -and $Context -eq 'System') { | ||
| throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM' | ||
| } | ||
| Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')" | ||
|
|
||
| $reasons = [System.Collections.Generic.List[string]]::new() | ||
| $signatureStatus = '' | ||
| $run = $null | ||
| if ($EnforceSignatureCheck) { | ||
| # With the check on, AgentExecutor returns exit 1 for an unsigned script without running it: | ||
| # no probe record, "EnforceSignatureCheck: 1 ... applicationDetected: False" (W32-DET-SIGCHECK) | ||
| $signature = Get-AuthenticodeSignature -FilePath (Resolve-Path -LiteralPath $Path).ProviderPath | ||
| $signatureStatus = "$($signature.Status)" | ||
| if ($signature.Status -ne 'Valid') { | ||
| $reasons.Add("Signature status ${signatureStatus}: with the signature check enforced the agent " + | ||
| 'does not run the script and reports not detected (exit 1 from AgentExecutor)') | ||
| process { | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A function body outside begin/process/end runs once, as end, with -Path bound to the last piped object only. The process block is what makes a piped list run each script; the body inside it is unchanged apart from indentation and three wrapped lines. |
||
| if ($Credential -and $Context -eq 'System') { | ||
| throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM' | ||
| } | ||
| } | ||
| Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')" | ||
|
|
||
| if ($reasons.Count -eq 0) { | ||
| $scriptRunSplat = @{ | ||
| Path = $Path | ||
| Architecture = $Architecture | ||
| Context = $Context | ||
| Phase = 'detect' | ||
| TimeoutSeconds = $TimeoutSeconds | ||
| $reasons = [System.Collections.Generic.List[string]]::new() | ||
| $signatureStatus = '' | ||
| $run = $null | ||
| if ($EnforceSignatureCheck) { | ||
| # With the check on, AgentExecutor returns exit 1 for an unsigned script without running it: | ||
| # no probe record, "EnforceSignatureCheck: 1 ... applicationDetected: False" (W32-DET-SIGCHECK) | ||
| $signature = Get-AuthenticodeSignature -FilePath (Resolve-Path -LiteralPath $Path).ProviderPath | ||
| $signatureStatus = "$($signature.Status)" | ||
| if ($signature.Status -ne 'Valid') { | ||
| $reasons.Add("Signature status ${signatureStatus}: with the signature check enforced the agent " + | ||
| 'does not run the script and reports not detected (exit 1 from AgentExecutor)') | ||
| } | ||
| } | ||
| if ($Credential) { $scriptRunSplat.Credential = $Credential } | ||
| $run = Invoke-IslScriptRun @scriptRunSplat | ||
| $hasStdOut = -not [string]::IsNullOrWhiteSpace($run.StdOut) | ||
| $hasStdErr = -not [string]::IsNullOrWhiteSpace($run.StdErr) | ||
|
|
||
| if ($run.TimedOut) { $reasons.Add("Timed out after $TimeoutSeconds s; Intune kills the script at its " + | ||
| "60-minute timeout and reports not detected") } | ||
| elseif ($run.ExitCode -ne 0) { $reasons.Add("Exit code $($run.ExitCode): only exit 0 can mean installed") } | ||
| if (-not $hasStdOut) { $reasons.Add('Nothing on stdout: exit 0 alone is "not detected"') } | ||
| if ($hasStdErr) { $reasons.Add('Output on stderr: any error output means "not detected" even with exit ' + | ||
| '0 and stdout') } | ||
| } | ||
| if ($reasons.Count -eq 0) { | ||
| $scriptRunSplat = @{ | ||
| Path = $Path | ||
| Architecture = $Architecture | ||
| Context = $Context | ||
| Phase = 'detect' | ||
| TimeoutSeconds = $TimeoutSeconds | ||
| } | ||
| if ($Credential) { $scriptRunSplat.Credential = $Credential } | ||
| $run = Invoke-IslScriptRun @scriptRunSplat | ||
| $hasStdOut = -not [string]::IsNullOrWhiteSpace($run.StdOut) | ||
| $hasStdErr = -not [string]::IsNullOrWhiteSpace($run.StdErr) | ||
|
|
||
| Write-Verbose "Completed $($MyInvocation.MyCommand.Name)" | ||
| [pscustomobject]@{ | ||
| PSTypeName = 'IntuneScriptLab.DetectionResult' | ||
| Detected = ($reasons.Count -eq 0) | ||
| Reason = if ($reasons.Count -eq 0) { ('Exit 0 with stdout and no ' + | ||
| 'stderr') } else { $reasons -join '; ' } | ||
| ExitCode = if ($run) { $run.ExitCode } else { 1 } | ||
| StdOut = if ($run) { $run.StdOut } else { '' } | ||
| StdErr = if ($run) { $run.StdErr } else { '' } | ||
| TimedOut = if ($run) { $run.TimedOut } else { $false } | ||
| Duration = if ($run) { $run.Duration } else { [timespan]::Zero } | ||
| SignatureStatus = $signatureStatus | ||
| Architecture = $Architecture | ||
| Context = $Context | ||
| RunAs = if ($run) { $run.RunAs } else { '' } | ||
| Host = if ($run) { $run.Host } else { '' } | ||
| ScriptPath = if ($run) { $run.ScriptPath } else { (Resolve-Path -LiteralPath $Path).ProviderPath } | ||
| if ($run.TimedOut) { | ||
| $reasons.Add("Timed out after $TimeoutSeconds s; Intune kills the script at its " + | ||
| '60-minute timeout and reports not detected') | ||
| } | ||
| elseif ($run.ExitCode -ne 0) { | ||
| $reasons.Add("Exit code $($run.ExitCode): only exit 0 can mean installed") | ||
| } | ||
| if (-not $hasStdOut) { $reasons.Add('Nothing on stdout: exit 0 alone is "not detected"') } | ||
| if ($hasStdErr) { | ||
| $reasons.Add('Output on stderr: any error output means "not detected" even with exit 0 and stdout') | ||
| } | ||
| } | ||
|
|
||
| Write-Verbose "Completed $($MyInvocation.MyCommand.Name)" | ||
| [pscustomobject]@{ | ||
| PSTypeName = 'IntuneScriptLab.DetectionResult' | ||
| Detected = ($reasons.Count -eq 0) | ||
| Reason = if ($reasons.Count -eq 0) { ('Exit 0 with stdout and no ' + | ||
| 'stderr') } else { $reasons -join '; ' } | ||
| ExitCode = if ($run) { $run.ExitCode } else { 1 } | ||
| StdOut = if ($run) { $run.StdOut } else { '' } | ||
| StdErr = if ($run) { $run.StdErr } else { '' } | ||
| TimedOut = if ($run) { $run.TimedOut } else { $false } | ||
| Duration = if ($run) { $run.Duration } else { [timespan]::Zero } | ||
| SignatureStatus = $signatureStatus | ||
| Architecture = $Architecture | ||
| Context = $Context | ||
| RunAs = if ($run) { $run.RunAs } else { '' } | ||
| Host = if ($run) { $run.Host } else { '' } | ||
| ScriptPath = if ($run) { $run.ScriptPath } else { (Resolve-Path -LiteralPath $Path).ProviderPath } | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List views, like the other per-run results, because each carries a Reason sentence that a table would truncate; the Rule line folds Kind, Operation, Operator and Value into the shape the portal shows the rule in.