0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Powershellで別のユーザーでプロセスを実行する

Last updated at Posted at 2023-12-31

行ったこと

Windowsサーバーのログイン中のユーザーで、とある処理をシステム権限で実行する必要があったので、Powershellを利用して別のユーザーでプロセスを実行する処理を作成してみました。

イメージ図
image.png

psexec

今回行ったことはpsexecを使用すれば簡単に実装できますが、どうしてもそれを利用できない場合の代替手段として利用しました。

利用した機能

Powershellの標準的な機能では、別のユーザーのセッション内にアプリケーションを起動させることができないため、Windows APIを利用しました。

Add-Type

Powershellで他のソース C#やVBなどのコードを呼び出すために使用するコマンドレットです。今回はAdd-Typeを使用して、C#のコードでWindows APIを利用するための関数の定義を行いました。

Add-Type @"
    using System;
    using System.Runtime.InteropServices;

    public class Win32API
    {
        ここにWindowsAPIを定義
    }
"@

CreateProcessAsUser

今回使用したメインのAPI関数です。指定したユーザーセッションでアプリケーションを実行することができます。起動時にいくつかパラメータが必要です。STARTUPINFOは構造体で渡してやる必要があります。

dwCreationFlagsに1024(CREATE_UNICODE_ENVIRONMENT)を指定しています。私の使用した環境では環境変数を読み込ませるためにUnicodeの指定が必要でした。これを指定しないとパラメータエラーとなってしまっていました。

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CreateProcessAsUser(
    IntPtr hToken,
    string lpApplicationName,
    string lpCommandLine,
    ref SECURITY_ATTRIBUTES lpProcessAttributes,
    ref SECURITY_ATTRIBUTES lpThreadAttributes,
    bool bInheritHandles,
    uint dwCreationFlags,
    IntPtr lpEnvironment,
    string lpCurrentDirectory,
    ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation);

構造体定義部分(一部)

    public struct STARTUPINFO
    {
        public int cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public ushort wShowWindow;
        public ushort cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

WTSQueryUserToken

query session等で取得したセッションIDをもとに、ユーザーセッションのハンドルを取得します。これには特殊な権限が必要なため、Local Systemで実行しています。普通のAdministratorでは権限がないため、下図のようなエラーになります。
image.png

[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(UInt32 sessionId, out IntPtr TokenHandle);

CreateEnvironmentBlock

起動したプロセスに環境変数を渡すために利用します。サンプルコードのメモ帳は不要ですが、ほとんどのアプリケーションは環境変数が必要になると思います。

[DllImport("userenv.dll", SetLastError = true)]
public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

DestroyEnvironmentBlock

CreateEnvironmentBlockは、環境変数をメモリ上の別の領域に確保するようなので、処理の終わりに開放します。

[DllImport("userenv.dll", SetLastError = true)]
public static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);

指定したアプリケーションを起動

以下の部分で、起動するアプリケーションや引数、作業ディレクトリを指定しています。

    $si = New-Object System.Diagnostics.ProcessStartInfo
    $si.FileName = "C:\Windows\System32\notepad.exe"
    $si.Arguments = "C:\temp\test.txt"
    $si.WorkingDirectory = "C:\Windows"

使用するときは、Local Systemで実行

Windows タスクスケジューラーを使用しました。
image.png

image.png

完成版

完成版のコードはここにおいています。
https://github.com/mirutaro/cpau_sample/blob/main/sample.ps1

まとめ

今回はPowershellを使用して、ユーザーセッション内でアプリケーションをシステム権限で実行することをやってみました。PowershellでWindowsAPIを操作できることを初めて知りましたが、非常に協力なツールであると思います。WindowsAPI自体がかなり奥が深く、使いこなせるといろいろなことが出来てしまうんだろうなと思います。しかし、使い方を間違えるとセキュリティ上の問題になるなど、使用にあたっては注意が必要かなと思いました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?