I wrote a blog post back in September that talked about Windows 10 running on ARM64. In that, I talked a little about WOW64 on ARM64, which enables an ARM64 device to run 32-bit x86 apps, quite useful since many apps don’t yet have ARM64 versions available.
But one twist I didn’t expect was with PowerShell. I often leverage PowerShell scripts as part of various Windows Autopilot scenarios that I’m running. Once such script is the UpdateOS.ps1 script that I blogged about back in October. For some reason, it wasn’t working on ARM64, wasn’t logging anything, and seemed to be running forever. When digging into that to see what was going on, I could see an interesting behavior: It was re-launching itself over and over again. That was due to this logic:
# If we are running as a 32-bit process on an x64 system, re-launch as a 64-bit process
if (Test-Path “$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe”)
{
& “$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe” -ExecutionPolicy bypass -NoProfile -File “$PSCommandPath”
Exit $lastexitcode
}
This isn’t unusual logic to have in a PowerShell script being launched from Intune, as it makes sure that on an x64 system you get an x64 PowerShell session instead of a x86 one. But on ARM64, this was looping. For that to happen, these two things would need to be true:
- The initial PowerShell.exe process was indeed an x86 process (easy to confirm, as C:\Windows\Sysnative only exists when running in a WOW process with file system redirection active).
- The PowerShell.exe executable in the C:\Windows\System32\WindowsPowerShell\v1.0 folder is an x86 version, not an ARM64 version, so re-launching just gets you exactly the same thing again.
So what does that mean? Simply, it means that PowerShell on an ARM64 OS is x86, period – there is no ARM64 version on the system. If you compare the “native” version in C:\Windows\System32\WindowsPowerShell\v1.0 with the “emulated” version in C:\Windows\SysWOW64\WindowsPowerShell\v1.0, they are exactly the same. (For an extra twist, the x86 version on ARM64 doesn’t include the same modules as on an x64 OS. For example, my UpdateOS.ps1 script was using the built-in WindowsUpdateProvider module, which doesn’t exist on ARM64.)
For the purposes of the UpdateOS.ps1 script, I can work around these issues like so:
# If we are running as a 32-bit process on an x64 system, re-launch as a 64-bit process
if (“$env:PROCESSOR_ARCHITEW6432” -ne “ARM64”)
{
if (Test-Path “$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe”)
{
& “$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe” -ExecutionPolicy bypass -NoProfile -File “$PSCommandPath”
Exit $lastexitcode
}
}
And since I’m using PSWindowsUpdate anyway, I can eliminate the use of WindowsUpdateProvider and just use the equivalent function in PSWindowsUpdate (which works fine in an x86 process on ARM64). I’ve posted a new version 1.2 of that to the PowerShell gallery, which includes these changes.
But that’s not the end of the challenges that you could run into. This is especially true if your PowerShell script accesses the registry using the HKLM: provider, since this is going to see the redirected view of the registry, e.g. HKLM:\Software will be redirected to HKLM:\Software\Wow6432Node. That makes it fairly hard to access the “real” HKLM:\Software registry key. There are a couple of possibilities:
- Use C:\Windows\sysnative\reg.exe from the x86 PowerShell session. Since that’s a native ARM64 executable, it can see the “real” HKLM:\Software. (You would need to fall back on C:\Windows\system32\reg.exe if C:\Windows\sysnative\reg.exe doesn’t exist. That would be a sign you’re not in a WOW process.)
- Use WMI to access the registry using the COM interfaces, specifying the _ProviderArchitecture of “64”. (I haven’t tried this – it should work in theory.)
Some days nothing is simple…
Categories: Windows 10