Skip to content

Instantly share code, notes, and snippets.

@tg2k
Last active October 31, 2015 20:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tg2k/bc5cfc5087501f7664a4 to your computer and use it in GitHub Desktop.
Save tg2k/bc5cfc5087501f7664a4 to your computer and use it in GitHub Desktop.
cProcessWithCredentials DSC resource and supporting modules

2 PowerShell modules (Invoke-CommandWithCredential and Invoke-ScriptBlockWithCredential) for invoking commands using specified credentials. One referenced module (Get-CallerPreference)for propagating verbosity etc. One DSC module (cProcessWithCredentials) for executing a process with credentials.

#
# Module manifest for module 'cProcessWithCredentials'
#
# Generated by: Todd C. Gleason
#
# Generated on: 9/18/2015
#
@{
# Script module or binary module file associated with this manifest.
RootModule = 'cProcessWithCredentials'
# Version number of this module.
ModuleVersion = '1.0'
# ID used to uniquely identify this module
GUID = 'a6d77483-544a-47e8-9a71-3117f9a54132'
# Author of this module
Author = 'Todd C. Gleason'
# Company or vendor of this module
CompanyName = ''
# Copyright statement for this module
Copyright = '(c) 2015 Todd C. Gleason. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Module with DSC Resource for ProcessWithCredentials'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '4.0'
# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module
# CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()
# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = @()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = @()
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()
# Functions to export from this module
FunctionsToExport = '*.TargetResource'
# Cmdlets to export from this module
CmdletsToExport = '*'
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module
AliasesToExport = '*'
# List of all modules packaged with this module
# ModuleList = @()
# List of all files packaged with this module
# FileList = @()
# Private data to pass to the module specified in RootModule/ModuleToProcess
# PrivateData = ''
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
}
Import-Module Invoke-CommandWithCredential
Import-Module Get-CallerPreference
function Get-TargetResource
{
[CmdletBinding()]
[OutputType([System.Collections.Hashtable])]
param
(
[parameter(Mandatory = $true)]
[System.String]
$Name
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
<#
$returnValue = @{
Name = [System.String]
Credential = [System.Management.Automation.PSCredential]
Path = [System.String]
Arguments = [System.String]
WorkingDirectory = [System.String]
}
$returnValue
#>
}
function Set-TargetResource
{
[CmdletBinding()]
param
(
[parameter(Mandatory = $true)]
[System.String]
$Name,
[System.Management.Automation.PSCredential]
$Credential,
[System.String]
$Path,
[System.String]
$Arguments,
[System.String]
$WorkingDirectory
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
# Must manually inherit state such as verbosity
# See https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d
Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
Invoke-CommandWithCredential $Credential $Path $Arguments $WorkingDirectory
#Include this line if the resource requires a system reboot.
#$global:DSCMachineStatus = 1
}
function Test-TargetResource
{
[CmdletBinding()]
[OutputType([System.Boolean])]
param
(
[parameter(Mandatory = $true)]
[System.String]
$Name,
[System.Management.Automation.PSCredential]
$Credential,
[System.String]
$Path,
[System.String]
$Arguments,
[System.String]
$WorkingDirectory
)
#Write-Verbose "Use this cmdlet to deliver information about command processing."
#Write-Debug "Use this cmdlet to write debug information while troubleshooting."
<#
$result = [System.Boolean]
$result
#>
# Always require the Set to run
$false
}
Export-ModuleMember -Function *-TargetResource
[ClassVersion("1.0.0.0"), FriendlyName("cProcessWithCredentials")]
class cProcessWithCredentials : OMI_BaseResource
{
[Key] String Name;
[Write, EmbeddedInstance("MSFT_Credential"), Description("Credential to run the MFS commands under")] String Credential;
[Write, Description("The path to the executable")] String Path;
[Write, Description("The arguments to the executable")] String Arguments;
[Write, Description("The working directory")] String WorkingDirectory;
};
#requires -Version 2.0
function Get-CallerPreference
{
<#
.Synopsis
Fetches "Preference" variable values from the caller's scope.
.DESCRIPTION
Script module functions do not automatically inherit their caller's variables, but they can be
obtained through the $PSCmdlet variable in Advanced Functions. This function is a helper function
for any script module Advanced Function; by passing in the values of $ExecutionContext.SessionState
and $PSCmdlet, Get-CallerPreference will set the caller's preference variables locally.
.PARAMETER Cmdlet
The $PSCmdlet object from a script module Advanced Function.
.PARAMETER SessionState
The $ExecutionContext.SessionState object from a script module Advanced Function. This is how the
Get-CallerPreference function sets variables in its callers' scope, even if that caller is in a different
script module.
.PARAMETER Name
Optional array of parameter names to retrieve from the caller's scope. Default is to retrieve all
Preference variables as defined in the about_Preference_Variables help file (as of PowerShell 4.0)
This parameter may also specify names of variables that are not in the about_Preference_Variables
help file, and the function will retrieve and set those as well.
.EXAMPLE
Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
Imports the default PowerShell preference variables from the caller into the local scope.
.EXAMPLE
Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -Name 'ErrorActionPreference','SomeOtherVariable'
Imports only the ErrorActionPreference and SomeOtherVariable variables into the local scope.
.EXAMPLE
'ErrorActionPreference','SomeOtherVariable' | Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
Same as Example 2, but sends variable names to the Name parameter via pipeline input.
.INPUTS
String
.OUTPUTS
None. This function does not produce pipeline output.
.LINK
about_Preference_Variables
#>
[CmdletBinding(DefaultParameterSetName = 'AllVariables')]
param (
[Parameter(Mandatory = $true)]
[ValidateScript({ $_.GetType().FullName -eq 'System.Management.Automation.PSScriptCmdlet' })]
$Cmdlet,
[Parameter(Mandatory = $true)]
[System.Management.Automation.SessionState]
$SessionState,
[Parameter(ParameterSetName = 'Filtered', ValueFromPipeline = $true)]
[string[]]
$Name
)
begin
{
$filterHash = @{}
}
process
{
if ($null -ne $Name)
{
foreach ($string in $Name)
{
$filterHash[$string] = $true
}
}
}
end
{
# List of preference variables taken from the about_Preference_Variables help file in PowerShell version 4.0
$vars = @{
'ErrorView' = $null
'FormatEnumerationLimit' = $null
'LogCommandHealthEvent' = $null
'LogCommandLifecycleEvent' = $null
'LogEngineHealthEvent' = $null
'LogEngineLifecycleEvent' = $null
'LogProviderHealthEvent' = $null
'LogProviderLifecycleEvent' = $null
'MaximumAliasCount' = $null
'MaximumDriveCount' = $null
'MaximumErrorCount' = $null
'MaximumFunctionCount' = $null
'MaximumHistoryCount' = $null
'MaximumVariableCount' = $null
'OFS' = $null
'OutputEncoding' = $null
'ProgressPreference' = $null
'PSDefaultParameterValues' = $null
'PSEmailServer' = $null
'PSModuleAutoLoadingPreference' = $null
'PSSessionApplicationName' = $null
'PSSessionConfigurationName' = $null
'PSSessionOption' = $null
'ErrorActionPreference' = 'ErrorAction'
'DebugPreference' = 'Debug'
'ConfirmPreference' = 'Confirm'
'WhatIfPreference' = 'WhatIf'
'VerbosePreference' = 'Verbose'
'WarningPreference' = 'WarningAction'
}
foreach ($entry in $vars.GetEnumerator())
{
if (([string]::IsNullOrEmpty($entry.Value) -or -not $Cmdlet.MyInvocation.BoundParameters.ContainsKey($entry.Value)) -and
($PSCmdlet.ParameterSetName -eq 'AllVariables' -or $filterHash.ContainsKey($entry.Name)))
{
$variable = $Cmdlet.SessionState.PSVariable.Get($entry.Key)
if ($null -ne $variable)
{
if ($SessionState -eq $ExecutionContext.SessionState)
{
Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false
}
else
{
$SessionState.PSVariable.Set($variable.Name, $variable.Value)
}
}
}
}
if ($PSCmdlet.ParameterSetName -eq 'Filtered')
{
foreach ($varName in $filterHash.Keys)
{
if (-not $vars.ContainsKey($varName))
{
$variable = $Cmdlet.SessionState.PSVariable.Get($varName)
if ($null -ne $variable)
{
if ($SessionState -eq $ExecutionContext.SessionState)
{
Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false
}
else
{
$SessionState.PSVariable.Set($variable.Name, $variable.Value)
}
}
}
}
}
} # end
} # function Get-CallerPreference
Import-Module Get-CallerPreference
function Invoke-CommandWithCredential {
<#
.SYNOPSIS
Invokes a command with the specified credential.
.DESCRIPTION
The Invoke-CommandWithCredential function calls Invoke-ScriptBlockWithCredential
(which uses LogonUser and impersonation) and then executes
the specified command as the specified user.
It is useful from within DSC because DSC runs as the system, and using the typical means to
execute new processes results in failures described in
http://stackoverflow.com/questions/32573385/problems-starting-processes-with-credentials
.PARAMETER credential
The credential to run under
.PARAMETER filename
The full path of the file to run
.PARAMETER cmdArgs
Optional: command line arguments.
.PARAMETER workingDirectory
Optional: Working directory. If not specified, the path of the filename is used.
.EXAMPLE
Invoke-CommandWithCredential $(Get-Credential) "c:\windows\system32\attrib.exe" "-a c:\MyTempFile.txt"
#>
[CmdletBinding()]
param (
[parameter(Mandatory=$true)]
[PSCredential] $credential,
[parameter(Mandatory=$true)]
[string]$filename,
[string]$cmdArgs,
[string]$workingDirectory
)
PROCESS {
# Must manually inherit state such as verbosity
# See https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d
Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
$output = RunAndCapture $credential $filename $cmdArgs $workingDirectory
return $output
}
}
function RunAndCapture([PSCredential] $credential, $filename, $cmdArgs, $workingDirectory)
{
$userName = Split-Path $credential.UserName -Leaf
$domain = Split-Path $credential.UserName
Write-Debug "RunAndCapture(): Invoking with domain $domain and user $userName"
Write-Debug "RunAndCapture(): Invoking $filename in directory $workingDirectory with args $cmdArgs"
[String] $stdout = $null
[String] $stderr = $null
# Two alternatives; in my experience LaunchCommand_AsUser worked better
$exitCode = [PsInvoke.NativeMethods.Win32]::LaunchCommand_AsUser($filename, $cmdArgs, $workingDirectory, $domain, $userName, $credential.GetNetworkCredential().Password, [Ref] $stdout, [Ref] $stderr)
#$exitCode = [PsInvoke.NativeMethods.Win32]::LaunchCommand_WithLogon($filename, $cmdArgs, $workingDirectory, $domain, $userName, $credential.GetNetworkCredential().Password, [Ref] $stdout, [Ref] $stderr)
Write-Verbose ("exit code: " + $exitCode)
if (-not [string]::IsNullOrEmpty($stdout))
{
Write-Verbose ("Output: " + $stdout)
}
if (-not [string]::IsNullOrEmpty($stderr))
{
Write-Verbose ("Errors: " + $stderr)
}
return @{
ExitCode = $exitCode
stdout = $stdout
stderr = $stderr
}
}
$Win32File =
@'
using System;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
namespace PsInvoke.NativeMethods
{
// from http://blogs.msdn.com/b/alejacma/archive/2007/12/20/how-to-call-createprocesswithlogonw-createprocessasuser-in-net.aspx
public class Win32
{
#region Constants
const UInt32 INFINITE = 0xFFFFFFFF;
const UInt32 WAIT_FAILED = 0xFFFFFFFF;
#endregion
#region Enumerations
[Flags]
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
LOGON32_LOGON_NEW_CREDENTIALS = 9
}
[Flags]
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35,
LOGON32_PROVIDER_WINNT40,
LOGON32_PROVIDER_WINNT50
}
// http://pinvoke.net/default.aspx/advapi32.CreateProcessWithLogonW
[Flags]
public enum CreationFlags
{
CREATE_SUSPENDED = 0x00000004,
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
}
// http://pinvoke.net/default.aspx/advapi32.CreateProcessWithLogonW
[Flags]
public enum LogonFlags
{
NONE = 0x0,
LOGON_WITH_PROFILE = 0x00000001,
LOGON_NETCREDENTIALS_ONLY = 0x00000002
}
// http://pinvoke.net/default.aspx/Enums/STARTF.html
// was incomplete, added more from
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331%28v=vs.85%29.aspx
[Flags]
public enum STARTF : uint
{
STARTF_USESHOWWINDOW = 0x00000001,
STARTF_USESIZE = 0x00000002,
STARTF_USEPOSITION = 0x00000004,
STARTF_USECOUNTCHARS = 0x00000008,
STARTF_USEFILLATTRIBUTE = 0x00000010,
STARTF_RUNFULLSCREEN = 0x00000020, // ignored for non-x86 platforms
STARTF_FORCEONFEEDBACK = 0x00000040,
STARTF_FORCEOFFFEEDBACK = 0x00000080,
STARTF_USESTDHANDLES = 0x00000100,
STARTF_USEHOTKEY = 0x00000200,
STARTF_TITLEISLINKNAME = 0x00000800,
STARTF_TITLEISAPPID = 0x00001000,
STARTF_PREVENTPINNING = 0x00002000,
STARTF_UNTRUSTEDSOURCE = 0x00008000,
}
#endregion
#region Structs
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public String lpReserved;
public String lpDesktop;
public String lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
[MarshalAs(UnmanagedType.I4)]
public STARTF dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public SafeFileHandle stdInput;
public SafeFileHandle stdOutput;
public SafeFileHandle stdError;
//public IntPtr hStdInput;
//public IntPtr hStdOutput;
//public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessId;
public Int32 dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
public SECURITY_ATTRIBUTES()
{
nLength = 12;
lpSecurityDescriptor = IntPtr.Zero;
}
}
#endregion
#region P/Invoke Functions
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean LogonUser
(
String lpszUserName,
String lpszDomain,
String lpszPassword,
LogonType dwLogonType,
LogonProvider dwLogonProvider,
out IntPtr phToken
);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean CreateProcessAsUserW
(
IntPtr hToken,
String lpApplicationName,
String lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
Boolean bInheritHandles,
Int32 dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation
);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern Boolean CreateProcessWithLogonW
(
String lpszUsername,
String lpszDomain,
String lpszPassword,
[MarshalAs(UnmanagedType.U4)]
LogonFlags dwLogonFlags,
String applicationName,
String commandLine,
Int32 creationFlags,
IntPtr environment,
String currentDirectory,
ref STARTUPINFO sui,
out PROCESS_INFORMATION processInfo
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern UInt32 WaitForSingleObject
(
IntPtr hHandle,
UInt32 dwMilliseconds
);
[DllImport("kernel32", SetLastError = true)]
public static extern Boolean CloseHandle(IntPtr handle);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe,
SECURITY_ATTRIBUTES lpPipeAttributes, int nSize);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, SafeHandle hSourceHandle,
IntPtr hTargetProcess, out SafeFileHandle targetHandle, int dwDesiredAccess,
bool bInheritHandle, int dwOptions);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr GetCurrentProcess();
// http://www.pinvoke.net/default.aspx/kernel32.getexitcodeprocess
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetExitCodeProcess(IntPtr hProcess, out uint lpExitCode);
#endregion
#region Helpers
public static void CreatePipeWithSecurityAttributes(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe,
SECURITY_ATTRIBUTES lpPipeAttributes, int nSize)
{
hReadPipe = null;
if ((!CreatePipe(out hReadPipe, out hWritePipe, lpPipeAttributes, nSize) || hReadPipe.IsInvalid) || hWritePipe.IsInvalid)
throw new Exception();
}
public static void CreatePipe(out SafeFileHandle parentHandle, out SafeFileHandle childHandle, bool parentInputs)
{
SECURITY_ATTRIBUTES lpPipeAttributes = new SECURITY_ATTRIBUTES();
lpPipeAttributes.bInheritHandle = true;
SafeFileHandle hWritePipe = null;
try
{
if (parentInputs)
CreatePipeWithSecurityAttributes(out childHandle, out hWritePipe, lpPipeAttributes, 0);
else
CreatePipeWithSecurityAttributes(out hWritePipe, out childHandle, lpPipeAttributes, 0);
if (
!DuplicateHandle(GetCurrentProcess(), hWritePipe, GetCurrentProcess(), out parentHandle, 0, false, 2))
throw new Exception();
}
finally
{
if ((hWritePipe != null) && !hWritePipe.IsInvalid)
{
hWritePipe.Close();
}
}
}
private class AsyncTextStreamReaderAdapter
{
private Stream _stream;
private byte[] _buffer = new byte[0x2000];
private StringBuilder _sb = new StringBuilder();
ManualResetEvent _completedEvent = new ManualResetEvent(false);
private IAsyncResult _ar;
public AsyncTextStreamReaderAdapter(Stream stream)
{
_stream = stream;
GetNextChunk();
}
private void GetNextChunk()
{
_ar = _stream.BeginRead(_buffer, 0, _buffer.Length, ReadCallback, null);
}
private void ReadCallback(IAsyncResult ar)
{
int bytesRead = _stream.EndRead(ar);
if (bytesRead == 0)
{
// completed
_completedEvent.Set();
System.Diagnostics.Debug.WriteLine("Completion on read callback");
return;
}
string str = Console.OutputEncoding.GetString(_buffer, 0, bytesRead);
_sb.Append(str);
System.Diagnostics.Debug.WriteLine("Read: " + str);
GetNextChunk();
}
public void Wait()
{
_completedEvent.WaitOne();
}
public string Result
{
get
{
_ar.AsyncWaitHandle.WaitOne();
Wait();
return _sb.ToString();
}
}
}
private const int _bufferSize = 0x2000;
public static string QuoteIfNeeded(string s)
{
if (s.Contains(" "))
{
return string.Format("\"{0}\"", s);
}
else
{
return s;
}
}
#endregion
#region Public Interfaces
public static uint LaunchCommand_WithLogon(string applicationName, string commandLine, string workingDir, string domain, string userid, string password, out string stdout, out string stderr)
{
// Variables
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
STARTUPINFO startInfo = new STARTUPINFO();
bool bResult = false;
UInt32 uiResultWait = WAIT_FAILED;
// null out empty strings
if (workingDir == String.Empty)
workingDir = null;
// some of the pipes implementation from
// http://stackoverflow.com/questions/1973376/how-to-get-standard-output-from-createprocesswithlogonw
SafeFileHandle inputHandle = null;
SafeFileHandle outputHandle = null;
SafeFileHandle errorHandle = null;
CreatePipe(out inputHandle, out startInfo.stdInput, true);
CreatePipe(out outputHandle, out startInfo.stdOutput, false);
CreatePipe(out errorHandle, out startInfo.stdError, false);
startInfo.cb = Marshal.SizeOf(startInfo);
// get a lot of 0xc0000142 errors when using this
// not sure if it has anything to do with
// http://deshack.net/windows-how-to-solve-application-error-0xc0000142-and-0xc0000005/
// in which you need to remove things using autoruns and/or registry
//startInfo.lpDesktop = "winsta0\\default";
startInfo.dwFlags = STARTF.STARTF_USESTDHANDLES;
try
{
// Create process
bResult = CreateProcessWithLogonW(
userid,
domain,
password,
LogonFlags.LOGON_WITH_PROFILE,
// regarding command line argument processing:
// it seems that even if you specify the application name,
// you must still include it in the command line; otherwise it seems that
// the first argument gets stripped out (perhaps Windows wants to consume that one as well
// as being argv[0] or whatnot).
applicationName,
//commandLine,
string.Format("{0} {1}", QuoteIfNeeded(applicationName), commandLine),
0,
IntPtr.Zero,
workingDir,
ref startInfo,
out processInfo
);
if (!bResult) { throw new Exception("CreateProcessWithLogonW error #" + Marshal.GetLastWin32Error().ToString()); }
// deal with the pipes
// (could use TPL and ReadToEndAsync() but this didn't work for me)
StreamWriter standardInput = new StreamWriter(new FileStream(inputHandle, FileAccess.Write, _bufferSize, false), Console.InputEncoding, _bufferSize);
standardInput.AutoFlush = true;
var stdoutStream = new FileStream(outputHandle, FileAccess.Read, _bufferSize, false);
var stderrStream = new FileStream(errorHandle, FileAccess.Read, _bufferSize, false);
AsyncTextStreamReaderAdapter stdoutAdapter = new AsyncTextStreamReaderAdapter(stdoutStream);
AsyncTextStreamReaderAdapter stderrAdapter = new AsyncTextStreamReaderAdapter(stderrStream);
// Wait for process to end
uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);
if (uiResultWait == WAIT_FAILED) { throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error()); }
// finish with the pipes
standardInput.Close();
// per http://www.databaseforum.info/25/581868.aspx must close the handles sent to the child process
startInfo.stdInput.Close();
startInfo.stdOutput.Close();
startInfo.stdError.Close();
stderr = stderrAdapter.Result;
stdout = stdoutAdapter.Result;
uint exitCode;
if (!GetExitCodeProcess(processInfo.hProcess, out exitCode))
{
throw new Exception("GetExitCodeProcess error #" + Marshal.GetLastWin32Error());
}
return exitCode;
}
finally
{
// Close all handles
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
}
// handle creation:
// http://stackoverflow.com/questions/1973376/how-to-get-standard-output-from-createprocesswithlogonw
public static uint LaunchCommand_AsUser(string applicationName, string commandLine, string workingDir, string domain, string userid, string password, out string stdout, out string stderr)
{
// Variables
PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
STARTUPINFO startInfo = new STARTUPINFO();
Boolean bResult = false;
IntPtr hToken = IntPtr.Zero;
UInt32 uiResultWait = WAIT_FAILED;
// null out empty strings
if (workingDir == String.Empty)
workingDir = null;
// some of the pipes implementation from
// http://stackoverflow.com/questions/1973376/how-to-get-standard-output-from-createprocesswithlogonw
SafeFileHandle inputHandle = null;
SafeFileHandle outputHandle = null;
SafeFileHandle errorHandle = null;
CreatePipe(out inputHandle, out startInfo.stdInput, true);
CreatePipe(out outputHandle, out startInfo.stdOutput, false);
CreatePipe(out errorHandle, out startInfo.stdError, false);
try
{
LogonType logonType;
if (Environment.UserInteractive)
{
logonType = LogonType.LOGON32_LOGON_INTERACTIVE;
}
else
{
logonType = LogonType.LOGON32_LOGON_NEW_CREDENTIALS;
}
// Logon user
bResult = Win32.LogonUser(
userid,
domain,
password,
//Win32.LogonType.LOGON32_LOGON_INTERACTIVE,
logonType,
Win32.LogonProvider.LOGON32_PROVIDER_DEFAULT,
out hToken
);
if (!bResult) { throw new Exception("Logon error #" + Marshal.GetLastWin32Error()); }
// Create process
startInfo.cb = Marshal.SizeOf(startInfo);
// this caused problems with CreateProcessWithLogonW(); not sure about with CreateProcessAsUserW()
//startInfo.lpDesktop = "winsta0\\default";
startInfo.dwFlags = STARTF.STARTF_USESTDHANDLES;
bResult = Win32.CreateProcessAsUserW(
hToken,
// regarding command line argument processing:
// it seems that even if you specify the application name,
// you must still include it in the command line; otherwise it seems that
// the first argument gets stripped out (perhaps Windows wants to consume that one as well
// as being argv[0] or whatnot).
applicationName,
//commandLine,
string.Format("{0} {1}", QuoteIfNeeded(applicationName), commandLine),
IntPtr.Zero,
IntPtr.Zero,
// inherit handles must be set in order to redirect handles
// see STARTF_USESTDHANDLES
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686331%28v=vs.85%29.aspx
true, // false,
0,
IntPtr.Zero,
workingDir,
ref startInfo,
out processInfo
);
if (!bResult) { throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error()); }
// deal with the pipes
// (could use TPL and ReadToEndAsync() but this didn't work for me)
StreamWriter standardInput = new StreamWriter(new FileStream(inputHandle, FileAccess.Write, _bufferSize, false), Console.InputEncoding, _bufferSize);
standardInput.AutoFlush = true;
var stdoutStream = new FileStream(outputHandle, FileAccess.Read, _bufferSize, false);
var stderrStream = new FileStream(errorHandle, FileAccess.Read, _bufferSize, false);
AsyncTextStreamReaderAdapter stdoutAdapter = new AsyncTextStreamReaderAdapter(stdoutStream);
AsyncTextStreamReaderAdapter stderrAdapter = new AsyncTextStreamReaderAdapter(stderrStream);
// Wait for process to end
uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);
if (uiResultWait == WAIT_FAILED) { throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error()); }
// finish with the pipes
standardInput.Close();
// per http://www.databaseforum.info/25/581868.aspx must close the handles sent to the child process
startInfo.stdInput.Close();
startInfo.stdOutput.Close();
startInfo.stdError.Close();
stderr = stderrAdapter.Result;
stdout = stdoutAdapter.Result;
uint exitCode;
if (!GetExitCodeProcess(processInfo.hProcess, out exitCode))
{
throw new Exception("GetExitCodeProcess error #" + Marshal.GetLastWin32Error());
}
return exitCode;
}
finally
{
// Close all handles
CloseHandle(hToken);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
}
}
#endregion
}
}
'@
$Win32 = Add-Type -TypeDefinition $Win32File -PassThru
Import-Module Get-CallerPreference
function Invoke-ScriptBlockWithCredential {
<#
.SYNOPSIS
Invokes a scriptblock with the specified credential.
.DESCRIPTION
The Invoke-ScriptBlockWithCredential function uses LogonUser and impersonation and then executes
the specified script block as the specified user.
It is useful from within DSC because DSC runs as the system, and using the typical means to
execute new processes etc. results in failures described in
http://stackoverflow.com/questions/32573385/problems-starting-processes-with-credentials
.PARAMETER credential
The credential to run under
.PARAMETER filename
The full path of the file to run
.PARAMETER cmdArgs
The command line arguments to run.
.EXAMPLE
Invoke-ScriptBlockWithCredential $(Get-Credential) { dir }
#>
[CmdletBinding()]
param (
[parameter(Mandatory=$true)]
[PSCredential] $credential,
[parameter(Mandatory=$true)]
[ScriptBlock] $scriptBlock
)
PROCESS {
# Must manually inherit state such as verbosity
# See https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d
Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState
# References:
# https://gist.github.com/idavis/856603
# http://poshcode.org/1856
$userName = Split-Path $credential.UserName -Leaf
$domain = Split-Path $credential.UserName
Write-Verbose "Invoking with domain $domain and user $userName"
try
{
# #define LOGON32_PROVIDER_DEFAULT 0
# #define LOGON32_PROVIDER_WINNT50 3
$Logon32ProviderDefault = 3
# for reference (examples I saw used 9):
#define LOGON32_LOGON_INTERACTIVE 2
#define LOGON32_LOGON_NETWORK 3
#define LOGON32_LOGON_BATCH 4
#define LOGON32_LOGON_SERVICE 5
#define LOGON32_LOGON_NEW_CREDENTIALS 9
$Logon32LogonInteractive = 3
$tokenHandle = [IntPtr]::Zero
$userName = Split-Path $credential.UserName -Leaf
$domain = Split-Path $credential.UserName
$unmanagedString = [IntPtr]::Zero;
$success = $false
try
{
Write-Debug "Logon interactive: $Logon32LogonInteractive; logon provider: $Logon32ProviderDefault"
# note LogonUser prototype; could possibly use IntPtr as well
#$unmanagedString = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($credential.Password);
#$success = $AdvApi32::LogonUser($userName, $domain, $unmanagedString, $Logon32LogonInteractive, $Logon32ProviderDefault, [Ref] $tokenHandle)
$success = $AdvApi32::LogonUser($userName, $domain, $credential.GetNetworkCredential().Password, $Logon32LogonInteractive, $Logon32ProviderDefault, [Ref] $tokenHandle)
}
finally
{
[System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($unmanagedString);
}
if (!$success )
{
$retVal = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
Write-Verbose "LogonUser was unsuccessful. Error code: $retVal"
Write-Error "LogonUser was unsuccessful. Error code: $retVal"
return
}
Write-Debug "LogonUser was successful."
Write-Debug "Value of Windows NT token: $tokenHandle"
$identityName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
Write-Debug "Current Identity: $identityName"
$newIdentity = New-Object System.Security.Principal.WindowsIdentity( $tokenHandle )
$context = $newIdentity.Impersonate()
$newIdentityName = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
Write-Debug "Impersonated: $newIdentityName"
Write-Debug "Executing custom script"
$output = & $scriptBlock
Write-Verbose ("Exit code: " + $output['ExitCode'])
Write-Verbose ("Output: " + $output['stdout'])
Write-Verbose ("Errors: " + $output['stderr'])
return $output
}
#catch [System.Exception]
#{
# Write-Verbose $_.Exception.ToString()
#}
finally
{
if ( $context -ne $null )
{
$context.Undo()
}
if ( $tokenHandle -ne [System.IntPtr]::Zero )
{
$Kernel32::CloseHandle( $tokenHandle )
}
}
}
}
# for lpszPassword, could also use IntPtr
$logonUserSignature =
@'
[DllImport( "advapi32.dll" )]
public static extern bool LogonUser( String lpszUserName,
String lpszDomain,
String lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken );
'@
$AdvApi32 = Add-Type -MemberDefinition $logonUserSignature -Name "AdvApi32" -Namespace "PsInvoke.NativeMethods" -PassThru
$closeHandleSignature =
@'
[DllImport( "kernel32.dll", CharSet = CharSet.Auto )]
public static extern bool CloseHandle( IntPtr handle );
'@
$Kernel32 = Add-Type -MemberDefinition $closeHandleSignature -Name "Kernel32" -Namespace "PsInvoke.NativeMethods" -PassThru
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment