Install in session 0 or interact with user

Topics: Archive - General
Mar 6, 2014 at 11:24 AM

We currently use Altiris Deployment Server for our app deployments. For those unfamiliar with it, it essentially throws down batch files and runs them in the system context (our users do not have admin rights).

Our current WiseScript template detects whether a user is currently logged on and shows them the UI if so. If not, the install progresses silently. This is because we essentially run a 24hr operation and a PC can potentially be in use around the clock. If a machine is off or asleep, it is woken by Altiris Wake On LAN first, then our template does the user session checking.

Im not sure if this is possible to perform under PS App Deployment? At the moment, if I fire down Deploy-Application.exe as SYSTEM, the install starts and completes, but the user sees nothing. If I set it to run in the users console session in Altiris, the user sees the UI, but then the install fails as the user isn't an admin! Since we don't know the state of the machine prior to deployment, we need the installer to have the ability to check and run either interactive or silent, depending on if a user is logged on or not.

Is this possible?

Mar 7, 2014 at 2:19 PM

There's a trick that a number of people are using the spawn the install out of Session 0 into the user Session while still running as SYSTEM. Details are here:

This works for SCCM - you might have to play around with Altiris to get it to work, but the principal is the same.

Cheers, Dan
Mar 7, 2014 at 4:37 PM
Edited Mar 7, 2014 at 4:37 PM
FANTASTIC!! Thanks!! I owe you a beer

It needed some tweaking in order to work, since when running in session 0 it seemed to hang, probably displaying the UI and waiting for user input. I knocked up this in Powershell to run either just deploy-application.exe when nobody is logged on, or serviceui.exe when a user is logged on:

$LoggedOn = get-wmiobject Win32_Computersystem

if($LoggedOn.Username -ne $Null){
start-process -filepath "\DeployServer\PSDeploy\serviceui.exe" -argumentlist '\DeployServer\PSDeploy\Deploy-Application.EXE'
else {
start-process -filepath "\DeployServer\PSDeploy\Deploy-Application.EXE"

Now I need to find a way to prevent users from logging out when the UI is displayed!


Mar 7, 2014 at 5:34 PM
I've got a lead for you if you care to follow it up. I was looking at this recently but haven't gotten around to PoC it...

Vista and above have a Shutdown API which could be leveraged to prevent users from logging out:

You'd need to figure out how to implement PInvoke signatures for these two methods:

There's some good info on PInvokes in PowerShell here:

So the idea would be, prior to the install starting, you'd call ShutdownBlockReasonCreate. When the install is complete, you'd call ShutdownBlockReasonDestroy. This should prevent the user from logging out.

Hope this helps!

Mar 10, 2014 at 10:01 AM
Edited Mar 10, 2014 at 11:22 AM
Thanks Dan, that sounds promising. Ive had a stab this morning, but as Im venturing into unknown territory here, Im not sure if Im doing this correctly. Here is what Ive got so far, using those links as templates:


$signature = @’ [DllImport("user32.dll", SetLastError=true)]
public static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);

$type = Add-Type -MemberDefinition $signature -Name Win32Utils -Namespace StopShutdown -PassThru

When I try to run the script with parameters (Stop-Shutdown.ps1 -Handle (get-process powershell_ise).Handle -Reason "test"), it just exits without a code or anything, but it doesn't seem to be blocking shutdowns.

Feels like Im close, but Im flying blind here!
Mar 10, 2014 at 2:41 PM
You haven't actually called the DLL as far as I know. That would be
[Win32Utils.StopShutdown]::ShutdownBlockReasonCreate($Handle, $Reason)
That being said, I haven't dived into this much - mucking around with PInvokes isn't something I have any experience with.

Good luck! :)

Mar 11, 2014 at 12:28 PM
Many thanks again!

One step closer....

I get 'False' returned now when I run the following code:

____## Stop-Shutdown.ps1
$signature = @" [DllImport("user32.dll", SetLastError=true)]
public static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason);

$type = Add-Type -MemberDefinition $signature -Name Win32Utils -Namespace StopShutdown -PassThru

[StopShutdown.Win32Utils]::ShutdownBlockReasonCreate($Handle, $Reason)

Stop-Shutdown.ps1 -Handle 0x248 -Reason "TEST"

However, I can still shutdown...

Im guessing the handle is the thing at fault here. I found the handle using sysinternals process explorer, since the code (get-process powershell_ise).handle returns a different 4 digit value each time it is run.
Mar 11, 2014 at 1:05 PM
Hmmmm, so some digging brings up this AutoIt script that does what you want to achieve:

By the looks of it, it's doing a lot more in these lines:

GUIRegisterMsg($WM_QUERYENDSESSION, "Cancel_Shutdown")
$Hwnd = GUICreate("PreventShutdownGUI")

I don't know what the PInvoke equivalents of GUIRegistryMsg / GUICreate are unfortunately - I'm pretty out of my depth here. I would suggest maybe asking on StackOverflow as to how you can achieve the same thing as the AutoIt script (or original MSDN code) using PowerShell.

Or do we have any knowledgeable Win32 coders here?

Mar 11, 2014 at 5:33 PM
Here's as far as I've gotten with this. It returns True so the Block is being created but I think we're still missing something. I'm kinda hoping this can run from the Console because if I'm understanding correctly, we might need to actually build a form to get the handle from and set WM_QUERYENDSESSION. Totally guessing though...
Param (
        [string] $Reason = $null

# Get the MainWindowHandle of the current process
[int]$handle = [System.Diagnostics.Process]::GetCurrentProcess() | Select MainWindowHandle -ExpandProperty MainWindowHandle

$signature = @'
[DllImport("user32.dll", SetLastError=true)] 
public static extern bool ShutdownBlockReasonCreate(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] string reason); 

[DllImport("user32.dll", SetLastError=true)]
static extern bool ShutdownBlockReasonDestroy(IntPtr hWnd);

$type = Add-Type -MemberDefinition $signature -Name Win32Utils -Namespace StopShutdown -PassThru 

If ([StopShutdown.Win32Utils]::ShutdownBlockReasonCreate($Handle, $Reason)) { 
    Write-Host "ShutdownBlock Created successfully. Waiting 30 seconds..."
    Sleep -Seconds 30