This project is read-only.

Install in session 0 or interact with user

Topics: Archive - General
Mar 6, 2014 at 12:24 PM
Hi

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?

Thanks
Mar 7, 2014 at 3:19 PM
Hey,

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: https://psappdeploytoolkit.codeplex.com/discussions/465270

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 5:37 PM
Edited Mar 7, 2014 at 5: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!

Thanks

Jamie
Mar 7, 2014 at 6: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:

http://msdn.microsoft.com/en-us/library/ms700677(v=vs.85).aspx

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

http://www.pinvoke.net/default.aspx/user32/ShutdownBlockReasonCreate.html
http://www.pinvoke.net/default.aspx/user32.ShutdownBlockReasonDestroy

There's some good info on PInvokes in PowerShell here: http://www.leeholmes.com/blog/2009/01/19/powershell-pinvoke-walkthrough/

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!

Dan
Mar 10, 2014 at 11:01 AM
Edited Mar 10, 2014 at 12:22 PM
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:

Stop-Shutdown.ps1

param(
$Handle, 
$Reason)
$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 3: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! :)

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

One step closer....

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

____## Stop-Shutdown.ps1
param(
$Handle, 
$Reason)
$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"
False

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 2:05 PM
Hmmmm, so some digging brings up this AutoIt script that does what you want to achieve: http://www.autoitscript.com/forum/topic/96109-mimic-this-shutdown-blocker-via-autoit/?p=691104

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

$WM_QUERYENDSESSION = 0x11
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?

Dan
Mar 11, 2014 at 6: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
    [StopShutdown.Win32Utils]::ShutdownBlockReasonDestroy($Handle)
}