🤖
dotnetでUnix、Windows両方の環境で管理者権限かどうかのチェック
とりま結論。
細かい文章校正は後でします。
ifディレクティブでUNIXかどうかの判定はPowershellのソースコードでも全体で使われている。
プロジェクトにUNIXとWindowsのソースコードがどうしてもかなり混ざる、大規模なソースコードだとほぼ必須。
ifディレクティブでUnixとわけないと視覚的にUnixのソースコードかどうかを見分けるのが大変(ディレクティブでなくてifという形で散らばるため。)。
ちゃんとやるとifディレクティブでソースコード折り畳めるようになるし、目にも優しくなる。
プロジェクトファイルに書いたが、各プロジェクトでなくてソリューション直下にpropsとして持っておくべき。
cli.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- #if UNIX, #if !UNIX とifディレクティブでソースコードを分けれるようにする。 -->
<PropertyGroup>
<DefineConstants>$(DefineConstants);CORECLR</DefineConstants>
<IsWindows Condition="'$(IsWindows)' =='true' or ( '$(IsWindows)' == '' and '$(OS)' == 'Windows_NT')">true</IsWindows>
</PropertyGroup>
<!-- Define non-windows, all configuration properties -->
<PropertyGroup Condition=" '$(IsWindows)' != 'true' ">
<DefineConstants>$(DefineConstants);UNIX</DefineConstants>
</PropertyGroup>
</Project>
using System.Diagnostics;
using System;
using System.Threading;
using System.Security.Permissions;
using System.Security.Principal;
using Microsoft.CSharp.RuntimeBinder;
internal class Program
{
/// <summary>
/// check Administrator priviledge.
/// </summary>
/// <exception cref="RuntimeBinderException">unix環境下においてuidの取得失敗</exception>
/// <exception cref="IOException">unix環境下においてuid取得時のIOError</exception>
/// <exception cref="NullReferenceException">windows環境下においてCurrentPrincipalの取得失敗</exception>
/// <returns>If Administrator or root user, return true.</returns>
public static bool IsAdministrator()
{
bool judge = false;
#if UNIX
ProcessStartInfo processStartInfo = new() {
FileName = "id",
Arguments = "-u",
CreateNoWindow = true,
RedirectStandardOutput = true,
UseShellExecute = false,
};
string uidln = string.Empty;
try
{
using Process process = new();
process.StartInfo = processStartInfo;
process.Start();
process.WaitForExit();
uidln = process.StandardOutput.ReadToEnd();
if (uidln.Length < 1 || process.ExitCode != 0)
{
throw new RuntimeBinderException($"External process exited with non-zero exit code: {process.ExitCode}");
}
}
catch (Exception)
{
throw;
}
// ReadToEndは最後の改行文字まで取得するので末尾を削除
string uid = uidln[..^1];
// uid が0ということはrootユーザー。
if ("0".Equals(uid))
{
judge = true;
}
#else
AppDomain domain = Thread.GetDomain();
domain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
WindowsPrincipal principal = (WindowsPrincipal?)Thread.CurrentPrincipal ?? throw new NullReferenceException();
judge = principal.IsInRole(WindowsBuiltInRole.Administrator);
#endif
return judge;
}
private static void Main(string[] args)
{
bool judge = false;
try
{
judge = IsAdministrator();
} catch (Exception e)
{
Console.WriteLine(e.ToString());
return;
}
if (judge)
{
Console.WriteLine("You are Administrator user!");
} else {
Console.WriteLine("You are not Administrator user!");
}
}
}
まとめ
windowsとunix環境両方でも動くものが必要ならひたすらこのようにifディレクティブでUnix, windows
と環境を分けて書くことになる。意外とメンテナンス性は良い。
必要とあらばここからruntimeごとにさらにifディレクティブで分けることになる。
.netframework系とdotnet core系をruntimeで分ける場合はほぼ、ソースコードが倍になることは覚悟しよう。
Discussion