Created
May 1, 2017 20:22
-
-
Save rgl/35b5f7d3e58aa96e598d2d6999b25bbe to your computer and use it in GitHub Desktop.
whoami for Windows Nano Server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # whoami for Nano Server. | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| trap { | |
| Write-Output "ERROR: $_" | |
| Write-Output (($_.ScriptStackTrace -split '\r?\n') -replace '^(.*)$','ERROR: $1') | |
| Write-Output (($_.Exception.ToString() -split '\r?\n') -replace '^(.*)$','ERROR EXCEPTION: $1') | |
| Exit 1 | |
| } | |
| Add-Type @' | |
| using System; | |
| using System.Linq; | |
| using System.Collections.Generic; | |
| using System.ComponentModel; | |
| using System.Diagnostics; | |
| using System.Runtime.InteropServices; | |
| using System.Text; | |
| public class whoami | |
| { | |
| public class Account | |
| { | |
| public string Sid { get; set; } | |
| public string Domain { get; set; } | |
| public string Name { get; set; } | |
| public string Type { get; set; } | |
| public override string ToString() | |
| { | |
| return string.Format( | |
| string.IsNullOrEmpty(Domain) | |
| ? @"{1} ({2}; {3})" | |
| : @"{0}\{1} ({2}; {3})", | |
| Domain, | |
| Name, | |
| Sid, | |
| Type); | |
| } | |
| } | |
| public class Whoami | |
| { | |
| public AccountAndAttributes User { get; internal set; } | |
| public AccountAndAttributes[] Groups { get; internal set; } | |
| public PrivilegeAndAttributes[] Privileges { get; internal set; } | |
| } | |
| public class AccountAndAttributes | |
| { | |
| public Account Account { get; set; } | |
| public string[] Attributes { get; set; } | |
| public override string ToString() | |
| { | |
| return string.Format("{0} {1}", Account.ToString(), string.Join("|", Attributes)); | |
| } | |
| } | |
| public class PrivilegeAndAttributes | |
| { | |
| public string Privilege { get; set; } | |
| public string[] Attributes { get; set; } | |
| public override string ToString() | |
| { | |
| return string.Format("{0} {1}", Privilege, string.Join("|", Attributes)); | |
| } | |
| } | |
| public static Whoami GetWhoami() | |
| { | |
| var processHandle = Process.GetCurrentProcess().SafeHandle.DangerousGetHandle(); | |
| var processToken = IntPtr.Zero; | |
| if (!OpenProcessToken(processHandle, TOKEN_QUERY, ref processToken)) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| try | |
| { | |
| return new Whoami | |
| { | |
| User = GetTokenUser(processToken), | |
| Groups = GetTokenGroups(processToken), | |
| Privileges = GetTokenPrivileges(processToken), | |
| }; | |
| } | |
| finally | |
| { | |
| if (!CloseHandle(processToken)) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| } | |
| } | |
| private const int TOKEN_QUERY = 0x00000008; | |
| [DllImport("advapi32", ExactSpelling = true, SetLastError = true)] | |
| private static extern IntPtr GetCurrentProcessToken(); | |
| [DllImport("advapi32", ExactSpelling = true, SetLastError = true)] | |
| public static extern bool OpenProcessToken(IntPtr processHandle, int desiredAccess, ref IntPtr processToken); | |
| [DllImport("advapi32")] | |
| private static extern bool ConvertSidToStringSid(IntPtr pSID, ref string pStringSid); | |
| [DllImport("advapi32")] | |
| private static extern bool ConvertStringSidToSid(string pStringSid, ref IntPtr pSID); | |
| [DllImport("kernel32", SetLastError = true)] | |
| private static extern bool CloseHandle(IntPtr handle); | |
| private enum SID_NAME_USE | |
| { | |
| User = 1, | |
| Group, | |
| Domain, | |
| Alias, | |
| WellKnownGroup, | |
| DeletedAccount, | |
| Invalid, | |
| Unknown, | |
| Computer, | |
| Label | |
| } | |
| private enum TOKEN_INFORMATION_CLASS | |
| { | |
| TokenUser = 1, | |
| TokenGroups, | |
| TokenPrivileges, | |
| TokenOwner, | |
| TokenPrimaryGroup, | |
| TokenDefaultDacl, | |
| TokenSource, | |
| TokenType, | |
| TokenImpersonationLevel, | |
| TokenStatistics, | |
| TokenRestrictedSids, | |
| TokenSessionId | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| private struct TOKEN_USER | |
| { | |
| public SID_AND_ATTRIBUTES User; | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| private struct TOKEN_GROUPS | |
| { | |
| public uint GroupCount; | |
| [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] | |
| public SID_AND_ATTRIBUTES[] Groups; | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| private struct SID_AND_ATTRIBUTES | |
| { | |
| public IntPtr Sid; | |
| public uint Attributes; | |
| } | |
| [DllImport("advapi32", SetLastError = true)] | |
| private static extern bool GetTokenInformation( | |
| IntPtr hToken, | |
| TOKEN_INFORMATION_CLASS tokenInfoClass, | |
| IntPtr tokenInformation, | |
| int tokenInformationLength, | |
| ref int requiredLength); | |
| [DllImport("advapi32", SetLastError = true)] | |
| private static extern bool LookupAccountSid( | |
| string systemName, | |
| IntPtr sid, | |
| StringBuilder name, | |
| ref int cbName, | |
| StringBuilder domainName, | |
| ref int cbDomainName, | |
| out SID_NAME_USE use); | |
| [DllImport("advapi32", SetLastError = true)] | |
| protected static extern bool LookupPrivilegeName(string lpSystemName, ref LUID lpLuid, StringBuilder lpName, ref int cchName); | |
| private static Account GetAccount(IntPtr sid) | |
| { | |
| var nameStringBuilder = new StringBuilder(255); | |
| var nameStringBuilderCapacity = nameStringBuilder.Capacity; | |
| var domainStringBuilder = new StringBuilder(255); | |
| var domainStringBuilderCapacity = domainStringBuilder.Capacity; | |
| SID_NAME_USE nameUse; | |
| if (!LookupAccountSid( | |
| null, | |
| sid, | |
| nameStringBuilder, | |
| ref nameStringBuilderCapacity, | |
| domainStringBuilder, | |
| ref domainStringBuilderCapacity, | |
| out nameUse)) | |
| { | |
| //throw new Win32Exception(); | |
| // NB in nano server the SID S-1-5-93-0 fails to be found for some reason... so we do not throw an exception on error. | |
| return new Account | |
| { | |
| Sid = SidToString(sid), | |
| Domain = "", | |
| Name = "!!NOT-FOUND!!", | |
| Type = "Unknown", | |
| }; | |
| } | |
| return new Account | |
| { | |
| Sid = SidToString(sid), | |
| Domain = domainStringBuilder.ToString(), | |
| Name = nameStringBuilder.ToString(), | |
| Type = nameUse.ToString(), | |
| }; | |
| } | |
| private static string SidToString(IntPtr sid) | |
| { | |
| string s = ""; | |
| ConvertSidToStringSid(sid, ref s); | |
| return s; | |
| } | |
| private static AccountAndAttributes GetTokenUser(IntPtr token) | |
| { | |
| const int bufferSize = 256; | |
| var buffer = Marshal.AllocHGlobal(bufferSize); | |
| try | |
| { | |
| int requiredSize = bufferSize; | |
| if (!GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenUser, buffer, bufferSize, ref requiredSize)) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| var tokenUser = (TOKEN_USER)Marshal.PtrToStructure(buffer, typeof(TOKEN_USER)); | |
| return new AccountAndAttributes | |
| { | |
| Account = GetAccount(tokenUser.User.Sid), | |
| Attributes = new string[0], | |
| }; | |
| } | |
| finally | |
| { | |
| Marshal.FreeHGlobal(buffer); | |
| } | |
| } | |
| private static AccountAndAttributes[] GetTokenGroups(IntPtr token) | |
| { | |
| const int bufferSize = 10*1024; | |
| var buffer = Marshal.AllocHGlobal(bufferSize); | |
| try | |
| { | |
| int requiredSize = bufferSize; | |
| if (!GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenGroups, buffer, bufferSize, ref requiredSize)) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| var tokenGroups = (TOKEN_GROUPS)Marshal.PtrToStructure(buffer, typeof(TOKEN_GROUPS)); | |
| var groups = new SID_AND_ATTRIBUTES[tokenGroups.GroupCount]; | |
| PtrToStructureArray(groups, new IntPtr(buffer.ToInt64() + Marshal.OffsetOf(typeof(TOKEN_GROUPS), "Groups").ToInt64())); | |
| return groups.Select( | |
| g => new AccountAndAttributes | |
| { | |
| Account = GetAccount(g.Sid), | |
| Attributes = GetGroupAttributes(g.Attributes), | |
| } | |
| ).ToArray(); | |
| } | |
| finally | |
| { | |
| Marshal.FreeHGlobal(buffer); | |
| } | |
| } | |
| [Flags] | |
| private enum GroupAttribute : uint | |
| { | |
| Enabled = 0x00000004, | |
| EnabledByDefault = 0x00000002, | |
| Integrity = 0x00000020, | |
| IntegrityEnabled = 0x00000040, | |
| LogonId = 0xc0000000, | |
| Mandatory = 0x00000001, | |
| Owner = 0x00000008, | |
| Resource = 0x20000000, | |
| UseForDenyOnly = 0x00000010, | |
| } | |
| [Flags] | |
| private enum PrivilegeAttribute : uint | |
| { | |
| EnabledByDefault = 0x00000001, | |
| Enabled = 0x00000002, | |
| Removed = 0X00000004, | |
| UsedForAccess = 0x80000000, | |
| } | |
| private static string[] GetGroupAttributes(uint attributes) | |
| { | |
| // TODO list unknown bits. | |
| return GetFlags((GroupAttribute)attributes).ToArray(); | |
| } | |
| private static IEnumerable<string> GetFlags(Enum input) | |
| { | |
| foreach (Enum value in Enum.GetValues(input.GetType())) | |
| { | |
| if (input.HasFlag(value)) | |
| { | |
| yield return value.ToString(); | |
| } | |
| } | |
| } | |
| static void PtrToStructureArray<T>(T[] arr, IntPtr start) | |
| { | |
| var stride = Marshal.SizeOf<T>(); | |
| var ptr = start.ToInt64(); | |
| for (int i = 0; i < arr.Length; i++, ptr += stride) | |
| { | |
| arr[i] = (T)Marshal.PtrToStructure(new IntPtr(ptr), typeof(T)); | |
| } | |
| } | |
| protected struct TOKEN_PRIVILEGES | |
| { | |
| public UInt32 PrivilegeCount; | |
| [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] | |
| public LUID_AND_ATTRIBUTES[] Privileges; | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| protected struct LUID_AND_ATTRIBUTES | |
| { | |
| public LUID Luid; | |
| public uint Attributes; | |
| } | |
| [StructLayout(LayoutKind.Sequential)] | |
| protected struct LUID | |
| { | |
| public uint LowPart; | |
| public int HighPart; | |
| } | |
| private static PrivilegeAndAttributes[] GetTokenPrivileges(IntPtr token) | |
| { | |
| const int bufferSize = 10 * 1024; | |
| var buffer = Marshal.AllocHGlobal(bufferSize); | |
| try | |
| { | |
| int requiredSize = bufferSize; | |
| if (!GetTokenInformation(token, TOKEN_INFORMATION_CLASS.TokenPrivileges, buffer, bufferSize, ref requiredSize)) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| var tokenGroups = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(buffer, typeof(TOKEN_PRIVILEGES)); | |
| var privileges = new LUID_AND_ATTRIBUTES[tokenGroups.PrivilegeCount]; | |
| PtrToStructureArray(privileges, new IntPtr(buffer.ToInt64() + Marshal.OffsetOf(typeof(TOKEN_PRIVILEGES), "Privileges").ToInt64())); | |
| return privileges.Select( | |
| p => new PrivilegeAndAttributes | |
| { | |
| Privilege = GetPrivilegeName(p.Luid), | |
| Attributes = GetPrivilegeAttributes(p.Attributes), | |
| } | |
| ).ToArray(); | |
| } | |
| finally | |
| { | |
| Marshal.FreeHGlobal(buffer); | |
| } | |
| } | |
| private static string GetPrivilegeName(LUID luid) | |
| { | |
| var nameStringBuilder = new StringBuilder(255); | |
| var nameStringBuilderCapacity = nameStringBuilder.Capacity; | |
| if (!LookupPrivilegeName(null, ref luid, nameStringBuilder, ref nameStringBuilderCapacity)) | |
| { | |
| throw new Win32Exception(); | |
| } | |
| return nameStringBuilder.ToString(); | |
| } | |
| private static string[] GetPrivilegeAttributes(uint attributes) | |
| { | |
| // TODO list unknown bits. | |
| return GetFlags((PrivilegeAttribute)attributes).ToArray(); | |
| } | |
| } | |
| '@ ` | |
| -ReferencedAssemblies ` | |
| System.Linq, | |
| System.Diagnostics.Process, | |
| System.Runtime.Handles, | |
| Microsoft.Win32.Primitives | |
| # NB see the available assemblies at C:\Windows\System32\DotNetCore\v1.0 | |
| $wai = [whoami]::GetWhoami() | |
| $FormatEnumerationLimit = -1 | |
| Write-Output 'User:' | |
| $wai.User ` | |
| | Format-Table -AutoSize ` | |
| | Out-String -Stream -Width ([int]::MaxValue) ` | |
| | ForEach-Object {$_.Trim()} | |
| Write-Output 'Groups:' | |
| $wai.Groups ` | |
| | Sort-Object -Property Account ` | |
| | Format-Table -AutoSize ` | |
| | Out-String -Stream -Width ([int]::MaxValue) ` | |
| | ForEach-Object {$_.Trim()} | |
| Write-Output 'Privileges:' | |
| $wai.Privileges ` | |
| | Sort-Object -Property Privilege ` | |
| | Format-Table -AutoSize ` | |
| | Out-String -Stream -Width ([int]::MaxValue) ` | |
| | ForEach-Object {$_.Trim()} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment