From 67ec859beaf292283f9a34a9ef6a3ce385ae121d Mon Sep 17 00:00:00 2001 From: fadwen <110697945+fadwen@users.noreply.github.com> Date: Tue, 6 Oct 2026 01:49:20 -0700 Subject: [PATCH 1/2] feat(harness): script paths from the pipeline, and a view for every result type Invoke-IntuneDetectionTest, Invoke-IntunePlatformScriptTest and Invoke-IntuneRequirementTest took one -Path and nothing from the pipeline, while Test-IntuneScript and Repair-IntuneScript take a folder's scripts straight from Get-ChildItem. The three now bind -Path by value and from a FullName or PSPath property, and run their body once per script; the help's INPUTS says so. Test-IntuneWin32Rule, Test-IntuneWin32Requirement and Export-IntuneAgentDiagnostic results printed as property dumps, the only result types without a format view. Each has a list view now, and the module contract test requires a view for every output type an exported command declares, so the next command cannot ship without one. --- CHANGELOG.md | 5 + IntuneScriptLab.Format.ps1xml | 59 +++++++++ Public/Invoke-IntuneDetectionTest.ps1 | 113 ++++++++++-------- Public/Invoke-IntunePlatformScriptTest.ps1 | 88 +++++++------- Public/Invoke-IntuneRequirementTest.ps1 | 92 +++++++------- Tests/Unit/Module.Contract.Tests.ps1 | 11 ++ .../Invoke-IntuneDetectionTest.Tests.ps1 | 13 ++ .../Invoke-IntunePlatformScriptTest.Tests.ps1 | 9 ++ .../Invoke-IntuneRequirementTest.Tests.ps1 | 10 ++ .../Invoke-IntuneDetectionTest.md | 15 ++- .../Invoke-IntunePlatformScriptTest.md | 15 ++- .../Invoke-IntuneRequirementTest.md | 15 ++- docs/IntuneScriptLab/Repair-IntuneScript.md | 4 +- en-US/IntuneScriptLab-Help.xml | 27 +++-- 14 files changed, 306 insertions(+), 170 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2df62a..0cf1656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,11 @@ release notes. ### Added +- `Invoke-IntuneDetectionTest`, `Invoke-IntunePlatformScriptTest` and `Invoke-IntuneRequirementTest` take script + paths from the pipeline, by value or from a `FullName` or `PSPath` property, so `Get-ChildItem .\Detections | + Invoke-IntuneDetectionTest` runs each one. `Test-IntuneWin32Rule`, `Test-IntuneWin32Requirement` and + `Export-IntuneAgentDiagnostic` results have a format view, like every other result type; the module contract + test now requires one for every output type an exported command declares. - `Repair-IntuneScript` applies eight more edits, each the one the finding's message asks for: `-Force` on `Install-Module`, `Install-PackageProvider`, `Install-Package`, `Update-Module` and `Uninstall-Module`, `-Confirm:$false` on `Register-PSRepository`, `-ErrorAction SilentlyContinue` on a probing cmdlet in a Win32 diff --git a/IntuneScriptLab.Format.ps1xml b/IntuneScriptLab.Format.ps1xml index d6f6753..db464c6 100644 --- a/IntuneScriptLab.Format.ps1xml +++ b/IntuneScriptLab.Format.ps1xml @@ -412,6 +412,65 @@ + + IntuneScriptLab.RuleResult + + IntuneScriptLab.RuleResult + + + + + + Met + "$($_.Kind) $($_.Operation)$(if ($_.Operator) { " $($_.Operator) '$($_.Value)'" })" + Target + Actual + Reason + Check32BitOn64System + + + + + + + IntuneScriptLab.ApplicabilityResult + + IntuneScriptLab.ApplicabilityResult + + + + + + Applicable + Reason + Applicability + Details + ($_.Checks | ForEach-Object { "$($_.Requirement): $(if ($_.Met) { 'met' } else { 'not met' })" }) -join "; " + + + + + + + IntuneScriptLab.Diagnostic + + IntuneScriptLab.Diagnostic + + + + + + Path + "{0:N1} MB" -f ($_.SizeBytes / 1MB) + Files + Logs + Registry + Timeline + + + + + IntuneScriptLab.RequirementResult diff --git a/Public/Invoke-IntuneDetectionTest.ps1 b/Public/Invoke-IntuneDetectionTest.ps1 index 7d2090a..172adff 100644 --- a/Public/Invoke-IntuneDetectionTest.ps1 +++ b/Public/Invoke-IntuneDetectionTest.ps1 @@ -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 { + 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 } + } } } diff --git a/Public/Invoke-IntunePlatformScriptTest.ps1 b/Public/Invoke-IntunePlatformScriptTest.ps1 index fd11fd3..20f0886 100644 --- a/Public/Invoke-IntunePlatformScriptTest.ps1 +++ b/Public/Invoke-IntunePlatformScriptTest.ps1 @@ -7,7 +7,8 @@ function Invoke-IntunePlatformScriptTest { [CmdletBinding()] [OutputType('IntuneScriptLab.PlatformScriptResult')] param( - [Parameter(Mandatory, Position = 0)] + [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [Alias('FullName', 'PSPath')] [string]$Path, [ValidateSet('x86', 'x64', 'arm64')] @@ -26,50 +27,53 @@ function Invoke-IntunePlatformScriptTest { [ValidateRange(1, 86400)] [int]$TimeoutSeconds = 300 ) - Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')" - $scriptRunSplat = @{ - Path = $Path - Architecture = $Architecture - Context = $Context - Phase = 'script' - TimeoutSeconds = $TimeoutSeconds - } - if ($Credential) { - if ($Context -eq 'System') { - throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM' + process { + Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')" + + $scriptRunSplat = @{ + Path = $Path + Architecture = $Architecture + Context = $Context + Phase = 'script' + TimeoutSeconds = $TimeoutSeconds } - $scriptRunSplat.Credential = $Credential - } - $run = Invoke-IslScriptRun @scriptRunSplat - $runState = if ($run.TimedOut) { 'TimedOut' } elseif ($run.ExitCode -eq 0) { 'Success' } else { 'Failed' } + if ($Credential) { + if ($Context -eq 'System') { + throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM' + } + $scriptRunSplat.Credential = $Credential + } + $run = Invoke-IslScriptRun @scriptRunSplat + $runState = if ($run.TimedOut) { 'TimedOut' } elseif ($run.ExitCode -eq 0) { 'Success' } else { 'Failed' } - # What happens next under Intune, from the PS-FAIL experiments: a failed script is fetched and - # run again at the agent's next script policy fetch, which happens at a service start or - # restart and otherwise every 8 hours (the hourly Win32 check-ins fetch no script policy), - # three runs in all, then never again - $warnings = [System.Collections.Generic.List[string]]::new() - if ($runState -ne 'Success') { - $warnings.Add(('Intune runs a failed platform script again at its next script policy fetch (an agent ' + - 'start or restart, otherwise every 8 hours), three runs in total (initial plus two retries), ' + - 'then reports Failed for good')) - } + # What happens next under Intune, from the PS-FAIL experiments: a failed script is fetched and + # run again at the agent's next script policy fetch, which happens at a service start or + # restart and otherwise every 8 hours (the hourly Win32 check-ins fetch no script policy), + # three runs in all, then never again + $warnings = [System.Collections.Generic.List[string]]::new() + if ($runState -ne 'Success') { + $warnings.Add(('Intune runs a failed platform script again at its next script policy fetch (an ' + + 'agent start or restart, otherwise every 8 hours), three runs in total (initial plus two ' + + 'retries), then reports Failed for good')) + } - Write-Verbose "Completed $($MyInvocation.MyCommand.Name)" - [pscustomobject]@{ - PSTypeName = 'IntuneScriptLab.PlatformScriptResult' - RunState = $runState - ExitCode = $run.ExitCode - ResultMessage = ($run.StdOut + $run.StdErr).TrimEnd("`r", "`n") - StdOut = $run.StdOut - StdErr = $run.StdErr - TimedOut = $run.TimedOut - Duration = $run.Duration - Warnings = $warnings.ToArray() - Architecture = $Architecture - Context = $Context - RunAs = $run.RunAs - Host = $run.Host - ScriptPath = $run.ScriptPath + Write-Verbose "Completed $($MyInvocation.MyCommand.Name)" + [pscustomobject]@{ + PSTypeName = 'IntuneScriptLab.PlatformScriptResult' + RunState = $runState + ExitCode = $run.ExitCode + ResultMessage = ($run.StdOut + $run.StdErr).TrimEnd("`r", "`n") + StdOut = $run.StdOut + StdErr = $run.StdErr + TimedOut = $run.TimedOut + Duration = $run.Duration + Warnings = $warnings.ToArray() + Architecture = $Architecture + Context = $Context + RunAs = $run.RunAs + Host = $run.Host + ScriptPath = $run.ScriptPath + } } } diff --git a/Public/Invoke-IntuneRequirementTest.ps1 b/Public/Invoke-IntuneRequirementTest.ps1 index 06775cb..ad2b709 100644 --- a/Public/Invoke-IntuneRequirementTest.ps1 +++ b/Public/Invoke-IntuneRequirementTest.ps1 @@ -7,7 +7,8 @@ function Invoke-IntuneRequirementTest { [CmdletBinding()] [OutputType('IntuneScriptLab.RequirementResult')] param( - [Parameter(Mandatory, Position = 0)] + [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [Alias('FullName', 'PSPath')] [string]$Path, [Parameter(Mandatory)] @@ -37,51 +38,54 @@ function Invoke-IntuneRequirementTest { [ValidateRange(1, 86400)] [int]$TimeoutSeconds = 300 ) - Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')" - $scriptRunSplat = @{ - Path = $Path - Architecture = $Architecture - Context = $Context - Phase = 'requirement' - TimeoutSeconds = $TimeoutSeconds - } - if ($Credential) { - if ($Context -eq 'System') { - throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM' + process { + Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')" + + $scriptRunSplat = @{ + Path = $Path + Architecture = $Architecture + Context = $Context + Phase = 'requirement' + TimeoutSeconds = $TimeoutSeconds } - $scriptRunSplat.Credential = $Credential - } - $run = Invoke-IslScriptRun @scriptRunSplat - $compareSplat = @{ - StdOut = $run.StdOut - StdErr = $run.StdErr - ExitCode = $run.ExitCode - TimedOut = $run.TimedOut - OutputType = $OutputType - Operator = $Operator - Value = $Value - } - $verdict = Compare-IslRequirementOutput @compareSplat + if ($Credential) { + if ($Context -eq 'System') { + throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM' + } + $scriptRunSplat.Credential = $Credential + } + $run = Invoke-IslScriptRun @scriptRunSplat + $compareSplat = @{ + StdOut = $run.StdOut + StdErr = $run.StdErr + ExitCode = $run.ExitCode + TimedOut = $run.TimedOut + OutputType = $OutputType + Operator = $Operator + Value = $Value + } + $verdict = Compare-IslRequirementOutput @compareSplat - Write-Verbose "Completed $($MyInvocation.MyCommand.Name)" - [pscustomobject]@{ - PSTypeName = 'IntuneScriptLab.RequirementResult' - Applicable = $verdict.Met - Reason = $verdict.Reason - Output = $verdict.Output - ExitCode = $run.ExitCode - StdOut = $run.StdOut - StdErr = $run.StdErr - TimedOut = $run.TimedOut - Duration = $run.Duration - OutputType = $OutputType - Operator = $Operator - Value = $Value - Architecture = $Architecture - Context = $Context - RunAs = $run.RunAs - Host = $run.Host - ScriptPath = $run.ScriptPath + Write-Verbose "Completed $($MyInvocation.MyCommand.Name)" + [pscustomobject]@{ + PSTypeName = 'IntuneScriptLab.RequirementResult' + Applicable = $verdict.Met + Reason = $verdict.Reason + Output = $verdict.Output + ExitCode = $run.ExitCode + StdOut = $run.StdOut + StdErr = $run.StdErr + TimedOut = $run.TimedOut + Duration = $run.Duration + OutputType = $OutputType + Operator = $Operator + Value = $Value + Architecture = $Architecture + Context = $Context + RunAs = $run.RunAs + Host = $run.Host + ScriptPath = $run.ScriptPath + } } } diff --git a/Tests/Unit/Module.Contract.Tests.ps1 b/Tests/Unit/Module.Contract.Tests.ps1 index 17c345d..a20d338 100644 --- a/Tests/Unit/Module.Contract.Tests.ps1 +++ b/Tests/Unit/Module.Contract.Tests.ps1 @@ -53,6 +53,17 @@ Describe 'IntuneScriptLab module contract' -Tag 'Unit', 'Contract' { $text.Length | Should-BeLessThanOrEqual $Limit } + It 'has a format view for every output type an exported command declares' { + # A result type without a view prints as a property dump; the view is part of the command's contract + $declared = @(Get-Command -Module IntuneScriptLab -CommandType Function | ForEach-Object { + $_.OutputType.Name + } | Where-Object { $_ -like 'IntuneScriptLab.*' } | Sort-Object -Unique) + $declared.Count | Should-BeGreaterThan 10 + [xml]$format = Get-Content (Join-Path $script:ModuleRoot 'IntuneScriptLab.Format.ps1xml') -Raw + $viewed = @($format.Configuration.ViewDefinitions.View.ViewSelectedBy.TypeName | Sort-Object -Unique) + @($declared | Where-Object { $_ -notin $viewed }) | Should-BeCollection @() + } + It 'exports exactly the public functions' { $exported = @((Get-Command -Module IntuneScriptLab -CommandType Function).Name | Sort-Object) $exported | Should-BeCollection @( diff --git a/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 b/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 index 6d3dfdd..ba13a79 100644 --- a/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 +++ b/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 @@ -28,6 +28,19 @@ Describe 'Invoke-IntuneDetectionTest' -Tag 'Unit', 'Public' { $command.Parameters['Context'].Attributes.ValidValues | Should-BeCollection @('User', 'System') } + It 'takes script paths from the pipeline, as Get-ChildItem gives them' { + Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab { + # The result's ScriptPath comes from the launch, which is mocked here + [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = 'ok'; StdErr = ''; ScriptPath = $Path } + } + $one = New-TestScript 'Pipe\Detect-One.ps1' 'exit 0' + $two = New-TestScript 'Pipe\Detect-Two.ps1' 'exit 0' + $results = @(Get-ChildItem (Split-Path $one) -Filter *.ps1 | Invoke-IntuneDetectionTest) + $results.Count | Should-Be 2 + @($results.ScriptPath | Sort-Object) | Should-BeCollection @($one, $two) + @('a.ps1', 'b.ps1' | Invoke-IntuneDetectionTest).Count | Should-Be 2 + } + It 'rejects a timeout outside 1..86400' { { Invoke-IntuneDetectionTest -Path $script:Detect -TimeoutSeconds 0 } | Should-Throw } diff --git a/Tests/Unit/Public/Invoke-IntunePlatformScriptTest.Tests.ps1 b/Tests/Unit/Public/Invoke-IntunePlatformScriptTest.Tests.ps1 index c96ccdd..26f5130 100644 --- a/Tests/Unit/Public/Invoke-IntunePlatformScriptTest.Tests.ps1 +++ b/Tests/Unit/Public/Invoke-IntunePlatformScriptTest.Tests.ps1 @@ -19,6 +19,15 @@ AfterAll { Describe 'Invoke-IntunePlatformScriptTest' -Tag 'Unit', 'Public' { Context 'Parameter Validation' { + It 'takes script paths from the pipeline, one result each' { + Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab { + [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = 'ok'; StdErr = '' } + } + $results = @('C:\lab\a.ps1', 'C:\lab\b.ps1' | Invoke-IntunePlatformScriptTest) + $results.Count | Should-Be 2 + Should-Invoke Invoke-IslScriptRun -ModuleName IntuneScriptLab -Times 2 -Exactly + } + It 'defaults to the 32-bit host and the current user, as the portal does' { Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab { [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = ''; StdErr = '' } diff --git a/Tests/Unit/Public/Invoke-IntuneRequirementTest.Tests.ps1 b/Tests/Unit/Public/Invoke-IntuneRequirementTest.Tests.ps1 index 63cd688..9078290 100644 --- a/Tests/Unit/Public/Invoke-IntuneRequirementTest.Tests.ps1 +++ b/Tests/Unit/Public/Invoke-IntuneRequirementTest.Tests.ps1 @@ -30,6 +30,16 @@ Describe 'Invoke-IntuneRequirementTest' -Tag 'Unit', 'Public' { $command.Parameters['Operator'].Attributes.ValidValues.Count | Should-Be 6 } + It 'takes script paths from the pipeline, with the rule given once' { + Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab { + [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = 'ok'; StdErr = '' } + } + $results = @('C:\lab\a.ps1', 'C:\lab\b.ps1' | + Invoke-IntuneRequirementTest -OutputType String -Operator Equal -Value ok) + $results.Count | Should-Be 2 + $results.Applicable | Should-All { $_ } + } + It 'defaults to the 64-bit host, as the portal does for requirement rules' { Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab { [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = "ok`r`n"; StdErr = '' } diff --git a/docs/IntuneScriptLab/Invoke-IntuneDetectionTest.md b/docs/IntuneScriptLab/Invoke-IntuneDetectionTest.md index a333e61..b8f7dc2 100644 --- a/docs/IntuneScriptLab/Invoke-IntuneDetectionTest.md +++ b/docs/IntuneScriptLab/Invoke-IntuneDetectionTest.md @@ -4,7 +4,7 @@ external help file: IntuneScriptLab-Help.xml HelpUri: https://github.com/fadwen/IntuneScriptLab/blob/main/docs/IntuneScriptLab/Invoke-IntuneDetectionTest.md Locale: en-US Module Name: IntuneScriptLab -ms.date: 10/05/2026 +ms.date: 10/06/2026 PlatyPS schema version: 2024-05-01 title: Invoke-IntuneDetectionTest --- @@ -165,13 +165,15 @@ The detection script. Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [] +Aliases: +- FullName +- PSPath ParameterSets: - Name: (All) Position: 0 IsRequired: true - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false + ValueFromPipeline: true + ValueFromPipelineByPropertyName: true ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] @@ -209,9 +211,10 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## INPUTS -### None +### System.String -This command does not accept pipeline input. Pass the script path with -Path. +A script path, or an object with a FullName or PSPath property such as Get-ChildItem gives, +one result per script. ## OUTPUTS diff --git a/docs/IntuneScriptLab/Invoke-IntunePlatformScriptTest.md b/docs/IntuneScriptLab/Invoke-IntunePlatformScriptTest.md index e649c57..a6f25b9 100644 --- a/docs/IntuneScriptLab/Invoke-IntunePlatformScriptTest.md +++ b/docs/IntuneScriptLab/Invoke-IntunePlatformScriptTest.md @@ -4,7 +4,7 @@ external help file: IntuneScriptLab-Help.xml HelpUri: https://github.com/fadwen/IntuneScriptLab/blob/main/docs/IntuneScriptLab/Invoke-IntunePlatformScriptTest.md Locale: en-US Module Name: IntuneScriptLab -ms.date: 10/05/2026 +ms.date: 10/06/2026 PlatyPS schema version: 2024-05-01 title: Invoke-IntunePlatformScriptTest --- @@ -139,13 +139,15 @@ The script. Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [] +Aliases: +- FullName +- PSPath ParameterSets: - Name: (All) Position: 0 IsRequired: true - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false + ValueFromPipeline: true + ValueFromPipelineByPropertyName: true ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] @@ -183,9 +185,10 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## INPUTS -### None +### System.String -This command does not accept pipeline input. Pass the script path with -Path. +A script path, or an object with a FullName or PSPath property such as Get-ChildItem gives, +one result per script. ## OUTPUTS diff --git a/docs/IntuneScriptLab/Invoke-IntuneRequirementTest.md b/docs/IntuneScriptLab/Invoke-IntuneRequirementTest.md index e6e95e3..9f9a009 100644 --- a/docs/IntuneScriptLab/Invoke-IntuneRequirementTest.md +++ b/docs/IntuneScriptLab/Invoke-IntuneRequirementTest.md @@ -4,7 +4,7 @@ external help file: IntuneScriptLab-Help.xml HelpUri: https://github.com/fadwen/IntuneScriptLab/blob/main/docs/IntuneScriptLab/Invoke-IntuneRequirementTest.md Locale: en-US Module Name: IntuneScriptLab -ms.date: 10/05/2026 +ms.date: 10/06/2026 PlatyPS schema version: 2024-05-01 title: Invoke-IntuneRequirementTest --- @@ -188,13 +188,15 @@ The requirement script. Type: System.String DefaultValue: '' SupportsWildcards: false -Aliases: [] +Aliases: +- FullName +- PSPath ParameterSets: - Name: (All) Position: 0 IsRequired: true - ValueFromPipeline: false - ValueFromPipelineByPropertyName: false + ValueFromPipeline: true + ValueFromPipelineByPropertyName: true ValueFromRemainingArguments: false DontShow: false AcceptedValues: [] @@ -253,9 +255,10 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable ## INPUTS -### None +### System.String -This command does not accept pipeline input. Pass the script path with -Path. +A script path, or an object with a FullName or PSPath property such as Get-ChildItem gives, +one result per script. ## OUTPUTS diff --git a/docs/IntuneScriptLab/Repair-IntuneScript.md b/docs/IntuneScriptLab/Repair-IntuneScript.md index 4949872..0912d4e 100644 --- a/docs/IntuneScriptLab/Repair-IntuneScript.md +++ b/docs/IntuneScriptLab/Repair-IntuneScript.md @@ -1,4 +1,4 @@ ---- +--- document type: cmdlet external help file: IntuneScriptLab-Help.xml HelpUri: https://github.com/fadwen/IntuneScriptLab/blob/main/docs/IntuneScriptLab/Repair-IntuneScript.md @@ -22,7 +22,7 @@ Applies the mechanical fixes for findings that have one, and reports what is lef ``` Repair-IntuneScript [-Path] [-ScriptType ] [-Context ] [-Architecture ] [-IncludeRule ] [-ExcludeRule ] - [-EnforceSignatureCheck] [-Settings ] [-WhatIf] [-Confirm] + [-EnforceSignatureCheck] [-Settings ] [-WhatIf] [-Confirm] [] ``` ## ALIASES diff --git a/en-US/IntuneScriptLab-Help.xml b/en-US/IntuneScriptLab-Help.xml index a21cce1..6d1bb74 100644 --- a/en-US/IntuneScriptLab-Help.xml +++ b/en-US/IntuneScriptLab-Help.xml @@ -2698,7 +2698,7 @@ Test-IntuneScript's IslContextIssue flags statically. Invoke-IntuneDetectionTest - + Path string @@ -2795,7 +2795,7 @@ in the reason, which is what AgentExecutor returned for an unsigned script (W32- System.Management.Automation.SwitchParameter - + Path The detection script. @@ -2820,10 +2820,11 @@ Intune allows 60 minutes; the default here is 5. - None + System.String - This command does not accept pipeline input. Pass the script path with -Path. + A script path, or an object with a FullName or PSPath property such as Get-ChildItem gives, +one result per script. @@ -2927,7 +2928,7 @@ through a scheduled task (elevated session required). Invoke-IntunePlatformScriptTest - + Path string @@ -3005,7 +3006,7 @@ hybrid-joined devices run them. System.Management.Automation.PSCredential - + Path The script. @@ -3030,10 +3031,11 @@ Intune allows 30 minutes; the default here is 5. - None + System.String - This command does not accept pipeline input. Pass the script path with -Path. + A script path, or an object with a FullName or PSPath property such as Get-ChildItem gives, +one result per script. @@ -3390,7 +3392,7 @@ Under Intune the requirement runs before the install and after the detection has Invoke-IntuneRequirementTest - + Path string @@ -3515,7 +3517,7 @@ Version or Boolean. System.String - + Path The requirement script. @@ -3550,10 +3552,11 @@ Intune allows 60 minutes; the default here is 5. - None + System.String - This command does not accept pipeline input. Pass the script path with -Path. + A script path, or an object with a FullName or PSPath property such as Get-ChildItem gives, +one result per script. From 285c7ce4388aa81adc6ed3f9502fd39a1bf83ce4 Mon Sep 17 00:00:00 2001 From: fadwen <110697945+fadwen@users.noreply.github.com> Date: Tue, 6 Oct 2026 02:00:00 -0700 Subject: [PATCH 2/2] test(harness): wrap a mock line the line gate caught at 116 characters --- Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 b/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 index ba13a79..36bd313 100644 --- a/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 +++ b/Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1 @@ -31,7 +31,9 @@ Describe 'Invoke-IntuneDetectionTest' -Tag 'Unit', 'Public' { It 'takes script paths from the pipeline, as Get-ChildItem gives them' { Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab { # The result's ScriptPath comes from the launch, which is mocked here - [pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = 'ok'; StdErr = ''; ScriptPath = $Path } + [pscustomobject]@{ + ExitCode = 0; TimedOut = $false; StdOut = 'ok'; StdErr = ''; ScriptPath = $Path + } } $one = New-TestScript 'Pipe\Detect-One.ps1' 'exit 0' $two = New-TestScript 'Pipe\Detect-Two.ps1' 'exit 0'