Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ release notes.

## [Unreleased]

### Changed

- `Invoke-IntuneDetectionTest`, `Invoke-IntuneRequirementTest` and `Invoke-IntuneWin32AppTest` no
longer default `-Architecture` to `x64`, which Windows on ARM refuses because there is no x64
host there. Left out, the architecture is the device's 64-bit host: `x64` on an x64 device,
`arm64` on Windows on ARM, the host the agent uses for Win32 detection and requirement scripts
with "run as 32-bit" off. `-Architecture x64` and `arm64` still name one host each and are
still refused on the other CPU; the result's `Architecture` says which host ran.

### Fixed

- A Win32 install or uninstall command named as a bare batch file (`install.cmd`) failed with
Expand Down
27 changes: 27 additions & 0 deletions Private/Get-IslHostArchitecture.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
function Get-IslHostArchitecture {
<#
.SYNOPSIS
The architecture of this device's 64-bit Windows PowerShell host: x64, or arm64 on Windows on ARM.

.DESCRIPTION
The agent runs Win32 detection and requirement scripts in the device's 64-bit host unless
the rule's "run as 32-bit" option is on. On Windows on ARM that host is the native ARM64
one and there is no x64 PowerShell at all (Findings, "Windows on ARM"), so the harness
commands use this as their default architecture instead of a fixed x64 that an ARM64
device refuses. The 32-bit host (x86) exists on both and is never the answer here.

.EXAMPLE
Get-IslHostArchitecture

x64 on an x64 device, arm64 on a Windows on ARM device.

.OUTPUTS
System.String. x64 or arm64, as Get-IslHostPath and the -Architecture parameters name them.
#>
[CmdletBinding()]
[OutputType([string])]
param()

$osArchitecture = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)"
if ($osArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate helper rather than Get-IslDeviceFact, which already derives the same value: that one also reads free disk space and memory through CIM, which the harness does not need on every launch. The OS architecture is the same test Get-IslHostPath makes, so the two agree by construction.

}
6 changes: 5 additions & 1 deletion Public/Invoke-IntuneDetectionTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ function Invoke-IntuneDetectionTest {
[Alias('FullName', 'PSPath')]
[string]$Path,

# Left out: the device's 64-bit host (x64, or arm64 on Windows on ARM), the agent's default
[ValidateSet('x86', 'x64', 'arm64')]
[string]$Architecture = 'x64',
[string]$Architecture,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No default expression on the parameter, resolved in the body instead, for two reasons. [ValidateSet] is not applied to a default, so an empty default passes; and PlatyPS would print a default expression such as (Get-IslHostArchitecture) verbatim in the help's DefaultValue, where an empty value with the description explaining it reads better. Same in the other two commands.


[ValidateSet('User', 'System')]
[string]$Context = 'User',
Expand All @@ -35,6 +36,9 @@ function Invoke-IntuneDetectionTest {
throw '-Credential applies to -Context User; System runs as NT AUTHORITY\SYSTEM'
}
Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')"
# The agent's default host is the device's 64-bit one: arm64 on Windows on ARM, where no x64
# host exists, and x64 elsewhere
if (-not $Architecture) { $Architecture = Get-IslHostArchitecture }

$reasons = [System.Collections.Generic.List[string]]::new()
$signatureStatus = ''
Expand Down
6 changes: 5 additions & 1 deletion Public/Invoke-IntuneRequirementTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ function Invoke-IntuneRequirementTest {
[AllowEmptyString()]
[string]$Value,

# Left out: the device's 64-bit host (x64, or arm64 on Windows on ARM), the agent's default
[ValidateSet('x86', 'x64', 'arm64')]
[string]$Architecture = 'x64',
[string]$Architecture,

[ValidateSet('User', 'System')]
[string]$Context = 'User',
Expand All @@ -41,6 +42,9 @@ function Invoke-IntuneRequirementTest {

process {
Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')"
# The agent's default host is the device's 64-bit one: arm64 on Windows on ARM, where no x64
# host exists, and x64 elsewhere
if (-not $Architecture) { $Architecture = Get-IslHostArchitecture }

$scriptRunSplat = @{
Path = $Path
Expand Down
6 changes: 5 additions & 1 deletion Public/Invoke-IntuneWin32AppTest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ function Invoke-IntuneWin32AppTest {
# uninstalled before this app installs, an update target stays (W32-SUP-OLD-A, W32-SUP-OLD-B)
[hashtable[]]$Supersedes,

# Left out: the device's 64-bit host (x64, or arm64 on Windows on ARM), the agent's default
[ValidateSet('x86', 'x64', 'arm64')]
[string]$Architecture = 'x64',
[string]$Architecture,

[ValidateSet('User', 'System')]
[string]$Context = 'User',
Expand Down Expand Up @@ -64,6 +65,9 @@ function Invoke-IntuneWin32AppTest {
[switch]$EnforceSignatureCheck
)
Write-Verbose "Starting $($MyInvocation.MyCommand.Name) for $($PSBoundParameters.Keys -join ', ')"
# The agent's default host is the device's 64-bit one: arm64 on Windows on ARM, where no x64
# host exists, and x64 elsewhere
if (-not $Architecture) { $Architecture = Get-IslHostArchitecture }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved before $detectionSplat is built, so the dependency and supersedence flows and the result's Architecture field all carry the resolved value rather than an empty string that Invoke-IntuneDetectionTest would then refuse through its ValidateSet.


if (-not $DetectionPath -and -not $DetectionRule) {
throw 'Give a detection script (-DetectionPath), detection rules (-DetectionRule), or both'
Expand Down
30 changes: 30 additions & 0 deletions Tests/Unit/Private/Get-IslHostArchitecture.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '6.2.0' }

<#
The default architecture of the Win32 harness commands: the 64-bit host this device has,
which Get-IslHostPath resolves to System32 rather than refusing.
#>

BeforeAll {
$script:ModuleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
Import-Module (Join-Path $script:ModuleRoot 'IntuneScriptLab.psd1') -Force
$osArchitecture = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)"
$script:Native = if ($osArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
}

AfterAll {
Remove-Module IntuneScriptLab -Force -ErrorAction SilentlyContinue
}

Describe 'Get-IslHostArchitecture' -Tag 'Unit', 'Private' {

It 'names the 64-bit host this device has' {
InModuleScope IntuneScriptLab { Get-IslHostArchitecture } | Should-Be $script:Native
}

It 'names a host Get-IslHostPath resolves instead of refusing' {
$hostInfo = InModuleScope IntuneScriptLab { Get-IslHostPath -Architecture (Get-IslHostArchitecture) }
$hostInfo.Path | Should-BeLikeString '*\WindowsPowerShell\v1.0\powershell.exe'
$hostInfo.Path | Should-NotBeLikeString '*SysWOW64*'
}
}
13 changes: 13 additions & 0 deletions Tests/Unit/Public/Invoke-IntuneDetectionTest.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ BeforeAll {
Import-Module (Join-Path $script:ModuleRoot 'IntuneScriptLab.psd1') -Force
. (Join-Path $script:ModuleRoot 'Tests\TestHelpers\TestHelpers.ps1')
$script:Detect = 'C:\lab\detect.ps1'
$osArchitecture = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)"
$script:Native = if ($osArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
}

AfterAll {
Expand Down Expand Up @@ -98,6 +100,17 @@ Describe 'Invoke-IntuneDetectionTest' -Tag 'Unit', 'Public' {
$result.Architecture | Should-Be 'x86'
$result.Context | Should-Be 'User'
}

It 'defaults to the 64-bit host this device has, the one the agent uses for Win32 detection' {
Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab {
[pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = 'x'; StdErr = '' }
}
$result = Invoke-IntuneDetectionTest -Path $script:Detect
Should-Invoke Invoke-IslScriptRun -ModuleName IntuneScriptLab -Exactly -Times 1 -ParameterFilter {
$Architecture -in 'x64', 'arm64'

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameter filter accepts either 64-bit host and the exact value is asserted on the result instead, because the filter runs in the mock's scope where the test file's $script:Native is not reliably visible. The same pattern replaces the literal 'x64' in the requirement and app test suites, so the arm64 CI job runs the same assertions as the x64 one.

}
$result.Architecture | Should-Be $script:Native
}
}

Context 'Enforced signature check (W32-DET-SIGCHECK)' {
Expand Down
8 changes: 5 additions & 3 deletions Tests/Unit/Public/Invoke-IntuneRequirementTest.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ BeforeAll {
Import-Module (Join-Path $script:ModuleRoot 'IntuneScriptLab.psd1') -Force
. (Join-Path $script:ModuleRoot 'Tests\TestHelpers\TestHelpers.ps1')
$script:Script = 'C:\lab\requirement.ps1'
$osArchitecture = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)"
$script:Native = if ($osArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
}

AfterAll {
Expand Down Expand Up @@ -40,16 +42,16 @@ Describe 'Invoke-IntuneRequirementTest' -Tag 'Unit', 'Public' {
$results.Applicable | Should-All { $_ }
}

It 'defaults to the 64-bit host, as the portal does for requirement rules' {
It 'defaults to the 64-bit host this device has, as the portal does for requirement rules' {
Mock Invoke-IslScriptRun -ModuleName IntuneScriptLab {
[pscustomobject]@{ ExitCode = 0; TimedOut = $false; StdOut = "ok`r`n"; StdErr = '' }
}
$result = Invoke-IntuneRequirementTest -Path $script:Script -OutputType String -Value 'ok'
$result.Architecture | Should-Be 'x64'
$result.Architecture | Should-Be $script:Native
$result.Context | Should-Be 'User'
$result.Operator | Should-Be 'Equal'
Should-Invoke Invoke-IslScriptRun -ModuleName IntuneScriptLab -Exactly -Times 1 -ParameterFilter {
$Architecture -eq 'x64' -and $Phase -eq 'requirement'
$Architecture -in 'x64', 'arm64' -and $Phase -eq 'requirement'
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions Tests/Unit/Public/Invoke-IntuneWin32AppTest.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ BeforeAll {
$script:ModuleRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
Import-Module (Join-Path $script:ModuleRoot 'IntuneScriptLab.psd1') -Force
. (Join-Path $script:ModuleRoot 'Tests\TestHelpers\TestHelpers.ps1')
$osArchitecture = "$([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture)"
$script:Native = if ($osArchitecture -eq 'Arm64') { 'arm64' } else { 'x64' }
$script:FileRule = @{ Type = 'File'; Path = 'C:\Fixtures'; FileOrFolderName = 'present.txt'
OperationType = 'exists' }
$script:MissingRule = @{ Type = 'File'; Path = 'C:\Fixtures'; FileOrFolderName = 'missing.txt'
Expand Down Expand Up @@ -76,16 +78,16 @@ Describe 'Invoke-IntuneWin32AppTest' -Tag 'Unit', 'Public' {
Should-Throw -ExceptionMessage '*-UninstallCommand*'
}

It 'defaults to the 64-bit host, as the portal does for Win32 detection' {
It 'defaults to the 64-bit host this device has, as the portal does for Win32 detection' {
$intuneWin32AppTestSplat = @{
DetectionPath = $script:Fixture.Detection
ContentPath = $script:Fixture.Content
InstallCommand = 'setup.exe /exit 0'
}
$result = Invoke-IntuneWin32AppTest @intuneWin32AppTestSplat
$result.Architecture | Should-Be 'x64'
$result.Architecture | Should-Be $script:Native
Should-Invoke Invoke-IntuneDetectionTest -ModuleName IntuneScriptLab -ParameterFilter {
$Architecture -eq 'x64'
$Architecture -in 'x64', 'arm64'
}
}

Expand Down
9 changes: 5 additions & 4 deletions docs/IntuneScriptLab/Invoke-IntuneDetectionTest.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,14 @@ Inside a Pester test.

### -Architecture

Host to run in: x64 (Intune's default for Win32 detection), x86 (the "run as 32-bit"
option), or arm64 on a Windows on ARM device. A Windows on ARM device has no x64 host, so the
x64 default is refused there: pass arm64, the host the agent uses on ARM64.
Host to run in: x64, x86 (the "run as 32-bit" option), or arm64. Left out, the device's
64-bit host: x64 on an x64 device, arm64 on Windows on ARM, which is the host the agent uses
for Win32 detection. x64 and arm64 each name a host only its own CPU has, so one is refused
on the other.

```yaml
Type: System.String
DefaultValue: x64
DefaultValue: ''

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty DefaultValue is what Update-MarkdownCommandHelp writes for a parameter without one (compare -Credential in the same file), so a later full help build does not change this line.

SupportsWildcards: false
Aliases: []
ParameterSets:
Expand Down
9 changes: 5 additions & 4 deletions docs/IntuneScriptLab/Invoke-IntuneRequirementTest.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,14 @@ As SYSTEM from an elevated session, inside a Pester test.

### -Architecture

Host to run in: x64 (the default for requirement rules), x86 (the "run as 32-bit"
option), or arm64 on a Windows on ARM device. A Windows on ARM device has no x64 host, so the
x64 default is refused there: pass arm64, the host the agent uses on ARM64.
Host to run in: x64, x86 (the "run as 32-bit" option), or arm64. Left out, the device's
64-bit host: x64 on an x64 device, arm64 on Windows on ARM, which is the host the agent uses
for requirement rules. x64 and arm64 each name a host only its own CPU has, so one is refused
on the other.

```yaml
Type: System.String
DefaultValue: x64
DefaultValue: ''
SupportsWildcards: false
Aliases: []
ParameterSets:
Expand Down
4 changes: 2 additions & 2 deletions docs/IntuneScriptLab/Invoke-IntuneWin32AppTest.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ package is uninstalled if it is present, then the app installs.

### -Architecture

Host for the detection script: x64 (Intune default), x86, or arm64. A Windows on ARM device has no x64 host, so a -DetectionPath with the x64 default is refused there: pass arm64.
Host for the detection script: x64, x86, or arm64. Left out, the device's 64-bit host: x64 on an x64 device, arm64 on Windows on ARM, which is the host the agent uses for Win32 detection. x64 and arm64 each name a host only its own CPU has, so one is refused on the other.

```yaml
Type: System.String
DefaultValue: x64
DefaultValue: ''
SupportsWildcards: false
Aliases: []
ParameterSets:
Expand Down
16 changes: 9 additions & 7 deletions en-US/IntuneScriptLab-Help.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2751,9 +2751,10 @@ Test-IntuneScript's IslContextIssue flags statically.</maml:para>
<command:parameter required="false" variableLength="false" globbing="false" pipelineInput="false" position="Named" aliases="none">
<maml:name>Architecture</maml:name>
<maml:description>
<maml:para>Host to run in: x64 (Intune's default for Win32 detection), x86 (the "run as 32-bit"
option), or arm64 on a Windows on ARM device. A Windows on ARM device has no x64 host, so the
x64 default is refused there: pass arm64, the host the agent uses on ARM64.</maml:para>
<maml:para>Host to run in: x64, x86 (the "run as 32-bit" option), or arm64. Left out, the device's
64-bit host: x64 on an x64 device, arm64 on Windows on ARM, which is the host the agent uses
for Win32 detection. x64 and arm64 each name a host only its own CPU has, so one is refused
on the other.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="true">System.String</command:parameterValue>
<dev:type>
Expand Down Expand Up @@ -3469,9 +3470,10 @@ Under Intune the requirement runs before the install and after the detection has
<command:parameter required="false" variableLength="false" globbing="false" pipelineInput="false" position="Named" aliases="none">
<maml:name>Architecture</maml:name>
<maml:description>
<maml:para>Host to run in: x64 (the default for requirement rules), x86 (the "run as 32-bit"
option), or arm64 on a Windows on ARM device. A Windows on ARM device has no x64 host, so the
x64 default is refused there: pass arm64, the host the agent uses on ARM64.</maml:para>
<maml:para>Host to run in: x64, x86 (the "run as 32-bit" option), or arm64. Left out, the device's
64-bit host: x64 on an x64 device, arm64 on Windows on ARM, which is the host the agent uses
for requirement rules. x64 and arm64 each name a host only its own CPU has, so one is refused
on the other.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="true">System.String</command:parameterValue>
<dev:type>
Expand Down Expand Up @@ -3834,7 +3836,7 @@ Soft/hard reboot codes count as success but are reported.</maml:para>
<command:parameter required="false" variableLength="false" globbing="false" pipelineInput="false" position="8" aliases="none">
<maml:name>Architecture</maml:name>
<maml:description>
<maml:para>Host for the detection script: x64 (Intune default), x86, or arm64. A Windows on ARM device has no x64 host, so a -DetectionPath with the x64 default is refused there: pass arm64.</maml:para>
<maml:para>Host for the detection script: x64, x86, or arm64. Left out, the device's 64-bit host: x64 on an x64 device, arm64 on Windows on ARM, which is the host the agent uses for Win32 detection. x64 and arm64 each name a host only its own CPU has, so one is refused on the other.</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="true">System.String</command:parameterValue>
<dev:type>
Expand Down
Loading