Thursday, June 25, 2015

64-bit vs 32-bit PowerShell

When PowerShell is installed on a 64-bit OS, you get both a 64-bit and a 32-bit version of PowerShell. On a 32-bit OS, of course, you only get the 32-bit version of PowerShell.

Which version of PowerShell is running in my session right now? What different does it make? Do I care?

Most of the time, you don’t need to care. Your system will launch the appropriate one, and they both do PowerShell things exactly the same.

Where it matters is when PowerShell needs to talk to or use something outside itself. When that happens, the versions have to match.

For example, if you are running 32-bit PowerShell and you try to use a module that calls a 64-bit .dll, it will fail. Or if you are running 64-bit PowerShell and you try to create a 32-bit COM object, it will fail.

So if I just make sure I install the 64-bit version of all my dependencies, I’m good, right?

Well, maybe not. If something other than you launches PowerShell, it may launch the 32-bit version. Generally, a 32-bit process will launch the 32-bit version of PowerShell, and a 64-bit process will launch the 64-bit version of PowerShell.

For example, System Center Orchestrator is still a 32-bit application, even in 2012 R2. Microsoft has not given any reason to believe that the 2016 version will be any different, as they are devoting their resources to other automation platforms. So when Orchestrator runs a PowerShell script, it runs it using 32-bit PowerShell.

So how can my script check which version it is being run as?

If you are running .Net 4.5 or higher, there is System class named Environment with a static method we can query.

001
[System.Environment]::Is64BitProcess
or
001
[Environment]::Is64BitProcess
will result in $True or $False.

([System] is a default namespace that PowerShell will search when looking for .Net references. So anywhere something starts with “System.”, you can omit “System.”.)

001
[Environment]::Is64BitOperatingSystem
will tell us if it’s a 64-bit OS (and therefore whether or not 64-bit PowerShell is even a possibility on this machine).

But most of us don’t have .Net 4.5 on every server and desktop in our environment.

Fortunately, there is a neat little trick that is backwards compatible with any version of .Net.

There is a System class named IntPtr. This class can be used to create memory pointer objects, which we as scripters would normally never care about. But what’s useful here is the fact that in a 32-bit process, the memory pointers are 32-bits long, and in a 64-bit process, the pointers are 64-bits long.

And the class even has a built-in property that can be called statically to find the size of IntPtr objects. (A static property is one that is hard-coded in the definition of the class, and we can call it directly from the definition without having to create an object of that class first.)

001
[IntPtr]::Size
returns 4 in 32-bit PowerShell, and 8 in 64-bit PowerShell, which is the size in bytes. So we just multiply by 8 to get the number of bits.

001
[IntPtr]::Size * 8
returns 32 or 64 as appropriate.

What do I do if this PowerShell session is the wrong version?

One option is to have your script relaunch itself in the correct version.

BE CAREFUL WITH THIS OPTION. If you get the logic wrong in a script that launches another copy of itself, it will try to iteratively launch an infinite number of copies, and crash your machine.

From a 32-bit PowerShell session, to launch a 64-bit PowerShell session, use:

C:\Windows\SysNative\WindowsPowerShell\v1.0\PowerShell.exe

From a 64-bit PowerShell session, to launch a 32-bit PowerShell session, use:

C:\Windows\SysWOW64\WindowsPowerShell\v1.0\PowerShell.exe

And yes, it looks wrong to launch the 32-bit version from a folder with 64 in the name, but that is correct.

To add to the confusion, the C:\Windows\Sys* folder names are dynamic. When you are in a 64 bit session, the 64-bit folder is named System32, the 32-bit folder is named SysWOW64, and the SysNative folder does not exist. When you are in a 32-bit session, the 64-bit folder is named SysNative, the 32-bit folder is named System32, and the SysWOW64 folder does not exist.

The full path to the script that is running can be found in automatic variable $MyInvocation.MyCommand.Path; we can specify that in the -File parameter.

If your script needs to run in a 32-bit PowerShell session, wrap it in this. If the session is not 32-bit, it will relaunch itself.

001
002
003
004
005
006
007
008
If ( [IntPtr]::Size * 8 -ne 32 )
    {
    C:\Windows\SysWOW64\WindowsPowerShell\v1.0\PowerShell.exe -File $MyInvocation.MyCommand.Path
    }
Else
    {
    # Your code here
    }

If your script needs to run in a 64-bit PowerShell session, wrap it in this. If the session is not 64-bit, it will relaunch itself.

001
002
003
004
005
006
007
008
If ( [IntPtr]::Size * 8 -ne 64 )
    {
    C:\Windows\SysNative\WindowsPowerShell\v1.0\PowerShell.exe -File $MyInvocation.MyCommand.Path
    }
Else
    {
    # Your code here
    }

If your script uses parameters, just add them to the mix.

If your script needs to run in a 32-bit PowerShell session with parameters, wrap it in this.

001
002
003
004
005
006
007
008
009
010
Param ( [string]$YourParam1, [int]$YourParam2 )

If ( [IntPtr]::Size * 8 -ne 32 )
    {
    C:\Windows\SysWOW64\WindowsPowerShell\v1.0\PowerShell.exe -File $MyInvocation.MyCommand.Path -YourParam1 $YourParam1 -YourParam2 $YourParam2
    }
Else
    {
    # Your code here
    }

If your script needs to run in a 64-bit PowerShell session, wrap it in this.

001
002
003
004
005
006
007
008
009
010
Param ( [string]$YourParam1, [int]$YourParam2 )

If ( [IntPtr]::Size * 8 -ne 64 )
    {
    C:\Windows\SysNative\WindowsPowerShell\v1.0\PowerShell.exe -File $MyInvocation.MyCommand.Path -YourParam1 $YourParam1 -YourParam2 $YourParam2
    }
Else
    {
    # Your code here
    }

Going back to my original point, most of the time, you don’t need to care, and this article is just an interesting exploration of some of the consequences of Microsoft trying to keep Windows backwards compatible. But if you are seeing odd behavior in your script that may be related to version incompatibilities, hopefully this will give you some help in confirming and/or working around the issue.

2 comments: