Start-ProcessAsUser.psm1
# Copyright (C) 2016 Moriyoshi Koizumi <mozo@mozo.jp> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. $win32apidll = (Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Definition) "Win32API.dll") Add-Type -LiteralPath $win32apidll Add-Type -TypeDefinition @' using System; namespace StartProcessAsUser { public class Process: IDisposable { IntPtr handle; public int Id { get { return (int)Win32API.Kernel32.GetProcessId(handle); } } public IntPtr Handle { get { return handle; } } public int ExitCode { get { return (int)Win32API.Kernel32.GetExitCodeProcess(handle); } } public string ProcessName { get { return Win32API.PSAPI.GetProcessImageFileName(handle); } } public void Dispose() { if (handle != IntPtr.Zero) { Win32API.Kernel32.CloseHandle(handle); } handle = IntPtr.Zero; } ~Process() { Dispose(); } public void WaitForExit() { Win32API.Kernel32.WaitForSingleObject(handle, uint.MaxValue); } public void WaitForExit(int timeout) { if (timeout < 0) { throw new ArgumentException("timeout cannot be negative"); } Win32API.Kernel32.WaitForSingleObject(handle, (uint)timeout); } public void WaitForInputIdle() { Win32API.Kernel32.WaitForInputIdle(handle, uint.MaxValue); } public void WaitForInputIdle(int timeout) { if (timeout < 0) { throw new ArgumentException("timeout cannot be negative"); } Win32API.Kernel32.WaitForInputIdle(handle, (uint)timeout); } public Process(IntPtr handle) { this.handle = handle; } } } '@ -Language CSharp -ReferencedAssemblies $win32apidll function Start-ProcessAsUser { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [String] $FilePath, [string[]] $ArgumentList = @(), [System.Management.Automation.PSCredential] $Credential = $null, [switch] $NoNewWindow = $false, [switch] $LoadUserProfile = $false, [switch] $UseNewEnvironment = $true, [System.Diagnostics.ProcessWindowStyle] $WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal, [string] $WorkingDirectory = $null, [switch] $Wait = $false, [switch] $PassThru = $false ) PROCESS { [object] $_workingDirectory = [nullstring]::Value if (![string]::IsNullOrEmpty($WorkingDirectory)) { $_workingDirectory = $WorkingDirectory } # resolve the path $resolvedFilePath = (Get-Command $FilePath).Definition # retrieve the window station $winStn = [Win32API.User32]::GetProcessWindowStation() # check if window station is visible if (![Win32API.User32]::GetUserObjectInformation( $winStn, [Win32API.User32+UserObjectInformationIndex]::UOI_FLAGS ).dwFlags) { throw "Window station is not visible; If you run this Cmdlet in the service context, you may need to set the value of HKLM:\System\CurrentControlSet\Control\Windows::NoInteractiveServices to 0." } if ($Credential -ne $null) { $token = [Win32API.AdvApi32]::LogonUser( $Credential.UserName, $null, $Credential.Password, [Win32API.AdvApi32+LogonType]::INTERACTIVE, [Win32API.AdvApi32+LogonProvider]::DEFAULT ) [Uint32]$attrs = 0 } else { $token = [Win32API.Advapi32]::OpenProcessToken( [Win32API.Kernel32]::GetCurrentProcess(), [Win32API.Advapi32+ACCESS_MASK]::READ_CONTROL + [Win32API.Advapi32+ACCESS_MASK]::GENERIC_ALL ) } try { # retrieve the SID of the user [Uint32] $attrs = 0 $userSid = [Win32API.AdvApi32]::GetTokenUser($token, [ref] $attrs) # retrieve the DACL of the window station, # add a couple of ACE to it and set it back to the window station. $info = [Win32API.AdvApi32]::GetSecurityInfo( $winStn, [Win32API.AdvApi32+SE_OBJECT_TYPE]::WINDOW_OBJECT, [Win32API.AdvApi32+SECURITY_INFORMATION]::DACL ) try { $acl = (New-Object Win32API.AdvApi32+ACL $info.Dacl) $ace = (New-Object Win32API.AdvApi32+AccessAllowed @( [Win32API.AdvApi32+AceFlags]::NO_PROPAGATE_INHERIT_ACE, ([Win32API.AdvApi32+ACCESS_MASK]::GENERIC_ALL + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_EXECUTE + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_READ + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_WRITE), $userSid )) $acl.Insert(0, $ace) $ace = (New-Object Win32API.AdvApi32+AccessAllowed @( [Win32API.AdvApi32+AceFlags]([int][Win32API.AdvApi32+AceFlags]::OBJECT_INHERIT_ACE + [int][Win32API.AdvApi32+AceFlags]::CONTAINER_INHERIT_ACE + [int][Win32API.AdvApi32+AceFlags]::INHERIT_ONLY_ACE), ([Win32API.AdvApi32+ACCESS_MASK]::GENERIC_ALL + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_EXECUTE + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_READ + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_WRITE + [Win32API.AdvApi32+ACCESS_MASK]::DELETE + [Win32API.AdvApi32+ACCESS_MASK]::READ_CONTROL + [Win32API.AdvApi32+ACCESS_MASK]::WRITE_DAC + [Win32API.AdvApi32+ACCESS_MASK]::WRITE_OWNER), $userSid )) $acl.Insert(0, $ace) $info.Dacl = $acl [Win32API.AdvApi32]::SetSecurityInfo( $winStn, [Win32API.AdvApi32+SE_OBJECT_TYPE]::WINDOW_OBJECT, [Win32API.AdvApi32+SECURITY_INFORMATION]::DACL, $info ) } finally { $info.Dispose() } # for each desktop, @("Default") | % { # open the desktop handle $desk = [Win32API.User32]::OpenDesktop( $_, 0, $false, ( [Win32API.AdvApi32+ACCESS_MASK]::READ_CONTROL + [Win32API.AdvApi32+ACCESS_MASK]::WRITE_DAC + [Win32API.AdvApi32+ACCESS_MASK][Win32API.User32+DESKTOP_ACCESS_MASK]::DESKTOP_READOBJECTS + [Win32API.AdvApi32+ACCESS_MASK][Win32API.User32+DESKTOP_ACCESS_MASK]::DESKTOP_WRITEOBJECTS ) ) try { # retrieve the DACL of the desktop, # add an ACE and set it back to the desktop. $info = [Win32API.AdvApi32]::GetSecurityInfo( $desk, [Win32API.AdvApi32+SE_OBJECT_TYPE]::WINDOW_OBJECT, [Win32API.AdvApi32+SECURITY_INFORMATION]::DACL ) try { $acl = (New-Object Win32API.AdvApi32+ACL $info.Dacl) $ace = (New-Object Win32API.AdvApi32+AccessAllowed @( 0, ([Win32API.AdvApi32+ACCESS_MASK]::GENERIC_ALL + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_EXECUTE + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_READ + [Win32API.AdvApi32+ACCESS_MASK]::GENERIC_WRITE + [Win32API.AdvApi32+ACCESS_MASK]::READ_CONTROL + [Win32API.AdvApi32+ACCESS_MASK]::WRITE_DAC + [Win32API.AdvApi32+ACCESS_MASK]::WRITE_OWNER), $userSid )) $acl.Insert(0, $ace) $info.Dacl = $acl [Win32API.AdvApi32]::SetSecurityInfo( $desk, [Win32API.AdvApi32+SE_OBJECT_TYPE]::WINDOW_OBJECT, [Win32API.AdvApi32+SECURITY_INFORMATION]::DACL, $info ) } finally { $info.Dispose() } } finally { [Win32API.User32]::CloseDesktop($desk) } } # launch the process $startupInfo = New-Object Win32API.Kernel32+STARTUPINFO $startupInfo.dwFlags = $startupInfo.dwFlags + [Win32API.Kernel32+StartUpInfoFlags]::STARTF_USESHOWWINDOW $startupInfo.lpDesktop = "winsta0\default" if ($windowStyle -eq [System.Diagnostics.ProcessWindowStyle]::Hidden) { $startupInfo.wShowWindow = [Int16] [Win32API.User32+ShowWindowCommand]::SW_HIDE } elseif ($windowStyle -eq [System.Diagnostics.ProcessWindowStyle]::Maximized) { $startupInfo.wShowWindow = [Int16] [Win32API.User32+ShowWindowCommand]::SW_SHOWMAXIMIZED } elseif ($windowStyle -eq [System.Diagnostics.ProcessWindowStyle]::Minimized) { $startupInfo.wShowWindow = [Int16] [Win32API.User32+ShowWindowCommand]::SW_SHOWMINIMIZED } else { $startupInfo.wShowWindow = [Int16] [Win32API.User32+ShowWindowCommand]::SW_SHOWNORMAL } [Win32API.UserEnv+PROFILEINFO] $profile = (New-Object Win32API.UserEnv+PROFILEINFO) [IntPtr] $env = [IntPtr]::Zero [Win32API.Kernel32+PROCESS_INFORMATION] $processInfo = (New-Object Win32API.Kernel32+PROCESS_INFORMATION) try { # load the profile if necessary if ($LoadUserProfile) { $profile = [Win32API.UserEnv]::LoadUserProfile($token, $Credential.UserName) } if ($UseNewEnvironment) { $env = [Win32API.UserEnv]::CreateEnvironmentBlock($token, $true) } [Win32API.AdvApi32]::ImpersonateLoggedOnUser($token) $flags = [Win32API.Kernel32+CreateProcessFlags]::CREATE_UNICODE_ENVIRONMENT if (!$NoNewWindow) { $flags += [Win32API.Kernel32+CreateProcessFlags]::CREATE_NEW_CONSOLE } $commandLine = "`"${resolvedFilePath}`" " + ($ArgumentList -join " ") $processInfo = [Win32API.AdvApi32]::CreateProcessAsUser( $token, $resolvedFilePath, $commandLine, [IntPtr]::Zero, [IntPtr]::Zero, $true, $flags, $env, $_workingDirectory, [ref] $startupInfo ) $info = (New-Object StartProcessAsUser.Process $processinfo.hProcess) $ok = $false try { if ($Wait) { $info.WaitForExit() } $ok = $true if ($PassThru) { $info } } finally { if (!$ok) { $info.Dispose() } } } finally { if ($LoadUserProfile) { [Win32API.UserEnv]::UnLoadUserProfile($token, [ref] $profile) } if ($env -ne [IntPtr]::Zero) { [Win32API.UserEnv]::DestroyEnvironmentBlock($env) } if ($processInfo.hThread -ne [IntPtr]::Zero) { [Win32API.Kernel32]::CloseHandle($processInfo.hThread) } } } finally { [Win32API.Kernel32]::CloseHandle($token) } } } Export-ModuleMember -Function Start-ProcessAsUser -Cmdlet Start-ProcessAsUser # vim: sts=4 sw=4 ts=4 et ai |