Nathan Ziehnert

4 minute read

Introduction

Let me set the stage here. Let's say you have a Win32 app you want to deploy, but you need to do some configuration as part of the install that the installer cannot natively handle - such as registry changes or file copies. In ConfigMgr, you might write a wrapper script in your favorite language to run the installer and then do that post configuration. You install it and everything is right in the world.

In the Intune world, you typically do something similar. Write a script to install the software and do post configuration, and then wrap all of it using the Microsoft Win32 Content Prep Tool. However, let's say after you load up the package with a neato PowerShell installation script in Intune and deploy it you notice that your registry settings are ending up in the wrong location in HKLM! You wanted them to go into HKLM:\SOFTWARE\App not HKLM:\SOFTWARE\WOW6432Node\App.

What happened?

Microsoft Intune Management Extension

The extension that facilitates the execution of PowerShell scripts on workstations from Intune AND also processes Win32 app installation and detection, is a 32-bit application. It executes in the 32-bit context and therefore when you call your PowerShell script it executes the 32-bit version of PowerShell. When you go to make that registry write it's writing it to the 32-bit registry. This might not be optimal for the application you are installing!

So how do we avoid this?

Get the Script to Re-Run Itself in 64-Bit!

Peter Vanderwoude has a snippet that he uses in this particular case:

If ($ENV:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
    Try {
        &"$ENV:WINDIR\SysNative\WindowsPowershell\v1.0\PowerShell.exe" -File $PSCOMMANDPATH
    }
    Catch {
        Throw "Failed to start $PSCOMMANDPATH"
    }
    Exit
}

If your installer script doesn't have any parameters, this is all you need. Add it at the top of your script and the script host will rerun the PowerShell script again, but this time in the 64-bit context.

But what if you need to install the package differently for different groups of machines, or what if you want to pass a parameter to the script so that you aren't hardcoding something in it (such as a password or secret)? You could create multiple packages of the same install. You could create multiple scripts within the package and just call different ones based on the install. But I think there's a better way!

Extending the Snippet

I recently had this issue as part of my work on a project I hope to release shortly. I wanted to provide the community a prepackaged IntuneWin file to import to Intune and execute to simplify things. However, there are a lot of parameters that need to be considered:

  • Where does the configuration file live?
  • If we're using LogAnalytics what Workspace and secret key should we use?
  • Do we want WMI Data?
  • Do we want Add Remove Program data?

It seemed best to parameterize the installation script so that you could make those decisions yourself without needing to re-package the install files. However, the solution also relies on two registry keys to determine when it last ran and how often it is allowed to run.

So, I've extended the wrapper to handle some basic parameters: Switch, Bool, String, and Int32. It would need to be further extended to support array parameters or other object types, but this seemed like a good start. Just add the following to your script after the parameter block and voila, the script will run WITH PARAMETERS in the 64-bit context!

$argsString = ""
If ($ENV:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
    Try {
        foreach($k in $MyInvocation.BoundParameters.keys)
        {
            switch($MyInvocation.BoundParameters[$k].GetType().Name)
            {
                "SwitchParameter" {if($MyInvocation.BoundParameters[$k].IsPresent) { $argsString += "-$k " } }
                "String"          { $argsString += "-$k `"$($MyInvocation.BoundParameters[$k])`" " }
                "Int32"           { $argsString += "-$k $($MyInvocation.BoundParameters[$k]) " }
                "Boolean"         { $argsString += "-$k `$$($MyInvocation.BoundParameters[$k]) " }
            }
        }
        Start-Process -FilePath "$ENV:WINDIR\SysNative\WindowsPowershell\v1.0\PowerShell.exe" -ArgumentList "-File `"$($PSScriptRoot)\Install.ps1`" $($argsString)" -Wait -NoNewWindow
    }
    Catch {
        Throw "Failed to start 64-bit PowerShell"
    }
    Exit
}

Conclusion

Let me know if you have any questions or run into any issues. I'm happy to help with extending the parameter types as well - just shoot me a comment down below!

comments powered by Disqus

Table of Contents