UserPrivilege/Invoke-RunAs.ps1

# taken from RunAs module and added some additional verbose information and error handling to try to
# help troubleshoot strange errors such as "directory does not exist"

try {
    Add-Type -ErrorAction SilentlyContinue -TypeDefinition @'
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class RunAs
{
    [StructLayout(LayoutKind.Sequential)]
    private struct PROCESS_INFORMATION
    {
        public IntPtr hProcess, hThread;
        public uint dwProcessId, dwThreadId;
    }
    [StructLayout(LayoutKind.Sequential)]
    private struct STARTUPINFO
    {
        public int cb;
        public string lpReserved, lpDesktop, lpTitle;
        public uint dwX, dwY, dwXSize, dwYSize, dwXCountChars, dwYCountChars, dwFillAttribute, dwFlags;
        public short wShowWindow, cbReserved2;
        public IntPtr lpReserved2, hStdInput, hStdOutput, hStdError;
    }
    [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool CreateProcessWithLogonW(string userName, string domain, string password, int logonFlags, string applicationName, string commandLine, int creationFlags, IntPtr environment, string currentDirectory, ref STARTUPINFO startupInfo, out PROCESS_INFORMATION processInformation);
    [DllImport("userenv")]
    private static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
    [DllImport("userenv")]
    private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
    [DllImport("kernel32")]
    private static extern bool CloseHandle(IntPtr hObject);
    private static void SplitUsername(string username, out string user, out string domain)
    {
        var parts = username.Split('\\', '@');
        if (parts.Length == 1)
        {
            user = parts[0];
            domain = Environment.UserDomainName;
        }
        else if (username.Contains("@"))
        {
            user = parts[0];
            domain = parts[1];
        }
        else
        {
            user = parts[1];
            domain = parts[0];
        }
    }
    public static void Start(PSCredential credential, bool noProfile, bool env, bool netOnly, string applicationName, string commandLine, string currentDirectory)
    {
        var s = new STARTUPINFO();
        s.dwFlags = 1; // STARTF_USESHOWWINDOW
        s.wShowWindow = 1; // SW_SHOWNORMAL
        s.cb = Marshal.SizeOf(typeof(STARTUPINFO));
        int logonFlags = 1; // LOGON_WITH_PROFILE
        if (noProfile) logonFlags = 0;
        if (netOnly) logonFlags = 2; // LOGON_NETCREDENTIALS_ONLY
        IntPtr lpEnvironment = IntPtr.Zero;
        int creationFlags = 0x04000000; // CREATE_DEFAULT_ERROR_MODE
        if (env)
        {
            CreateEnvironmentBlock(out lpEnvironment, IntPtr.Zero, false);
            creationFlags |= 0x00000400; // CREATE_UNICODE_ENVIRONMENT
        }
        try
        {
            string domain, username;
            SplitUsername(credential.UserName, out username, out domain);
            PROCESS_INFORMATION p;
            if (!CreateProcessWithLogonW(username, domain, credential.GetNetworkCredential().Password, logonFlags, applicationName, commandLine, creationFlags, lpEnvironment, currentDirectory, ref s, out p))
                throw new Win32Exception(Marshal.GetLastWin32Error());
            CloseHandle(p.hProcess);
            CloseHandle(p.hThread);
        }
        finally
        {
            if (env) DestroyEnvironmentBlock(lpEnvironment);
        }
    }
}
'@

}
catch {
    Write-Verbose "RunAs class already exists"
    # ignore if we already exist (TODO find a better solution to this)
}

<#
.Synopsis
A version of the Windows 'runas' command that accepts a PSCredential instead of prompting for a password.
.Description
Allows a user to run specific tools and programs with different permissions than the user's current logon provides.
.Parameter noprofile
Specifies that the user's profile should not be loaded. This causes the application to load more quickly, but can cause some applications to malfunction.
.Parameter env
To use current environment instead of user's.
.Parameter netonly
Use if the credentials specified are for remote access only.
.Parameter user
Username should be in form user@domain or domain\user.
.Parameter program
Command line for EXE. See below for examples.
.Example
...
runas -noprofile -user mymachine\administrator cmd
runas -env -user mydomain\admin mmc %windir%\system32\eventvwr.msc
runas -env -user user@domain.microsoft.com notepad "my file.txt"
#>

function Invoke-RunAs {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute(
        <#Category#>'PSUseSingularNouns', <#CheckId#>'',
        Justification = 'RunAs is singular'
    )]
    [CmdletBinding()]
    param(
        [switch]$noProfile,
        [switch]$env,
        [switch]$netOnly,
        [Parameter(Mandatory = $true)][PSCredential]$user,
        [Parameter(Mandatory = $true)][string]$program,
        [Parameter(ValueFromRemainingArguments = $true)][string]$arguments
    )
    try {
        if (!(test-path $program)) {
            $cmd = (get-command $program -ea ignore)
            if ($cmd -and $cmd.path) { $program = $cmd.path }
        }
        $commandLine = " $([Environment]::ExpandEnvironmentVariables($arguments))"
        $currentDirectory = $pwd.Path
        $allParams = @{
            credential       = $user
            noProfile        = $noProfile
            env              = $env
            netOnly          = $netOnly
            applicationName  = $program
            commandLine      = $commandLine
            currentDirectory = $currentDirectory
        }
        $allParams | Format-Table | Out-String | Write-Verbose
        [RunAs]::Start($user, $noProfile, $env, $netOnly, $program, $commandLine, $currentDirectory)
    }
    catch {
        throw
    }
}

# Export-ModuleMember -function RunAs