Re: Getting Windows "MACHINE SID" without fork() & exec()?

William Stewart via Cygwin <[email protected]> Tue, 28 Apr 2026 10:29:20 -0600
Newsgroups gmane.os.cygwin
Message-ID <CANV9t=Sakv8QKDb62aFPENh8vD+OwR45H7-ouz3NgH6XE8Qt5Q@mail.gmail.com>
On Tue, Apr 28, 2026 at 9:03 AM Takeshi Nishimura wrote:

> Why do you need the machine SID?
>
> Multiple reasons:
> Automated machine identification for cluster, looking up the localised
> Windows account names for builtin users, license information
> gathering, ...


Here's a PowerShell way that calls P/Invoke. If you're spawning a
powershell.exe (or pwsh.exe) process every time to get this, then naturally
it will be slow. Probably line breaks in the below will be wrong and will
need to be fixed manually.

-----[snip]-----
# Get-ComputerSID.ps1

#requires -version 5.1

[CmdletBinding()]
param(
  [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)]
  [String[]]
  $ComputerName = [Environment]::MachineName
)

begin {
  if ( [Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT ) {
    throw "Windows platform required"
  }

  Add-Type -MemberDefinition @"
  // [B374924426674F5EAACF740F97B4D2C5.NetApi32+USER_MODALS_INFO_2]
  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
  public struct USER_MODALS_INFO_2 {
    public string usrmod2_domain_name;
    public IntPtr usrmod2_domain_id;
  }

  // [B374924426674F5EAACF740F97B4D2C5.NetApi32]::NetApiBufferFree()
  [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError =
true)]
  public static extern uint NetApiBufferFree(IntPtr Buffer);

  // [B374924426674F5EAACF740F97B4D2C5.NetApi32]::NetUserModalsGet()
  [DllImport("netapi32.dll", CharSet = CharSet.Unicode, SetLastError =
true)]
  public static extern uint NetUserModalsGet(
    string servername,
    uint level,
    out IntPtr bufptr
  );
"@ -Namespace B374924426674F5EAACF740F97B4D2C5 -Name NetApi32

function GetComputerSID {
    [CmdletBinding()]
    param(
      [Parameter(Position = 0,Mandatory)]
      [String]
      $computerName
    )
    $bufPtr = [IntPtr]::Zero
    try {
      $result =
[B374924426674F5EAACF740F97B4D2C5.NetApi32]::NetUserModalsGet(
        $computerName,  # servername
        2,              # level
        [Ref] $bufPtr   # bufptr
      )
      if ( $result -eq 0 ) {
        $umi = [Runtime.InteropServices.Marshal]::PtrToStructure($bufPtr,
          [Type]
[B374924426674F5EAACF740F97B4D2C5.NetApi32+USER_MODALS_INFO_2])
        New-Object
Security.Principal.SecurityIdentifier($umi.usrmod2_domain_id)
      }
      else {
        $msg = ([ComponentModel.Win32Exception] ($result -as [Int])).Message
        Write-Error ("Error {0} connecting to '{1}' - {2}" -f
$result,$computerName,$msg)
      }
    }
    finally {
      if ( $bufPtr -ne [IntPtr]::Zero ) {
        [Void]
[B374924426674F5EAACF740F97B4D2C5.NetApi32]::NetApiBufferFree($bufPtr)
      }
    }
  }
}

process {
  foreach ( $ComputerNameItem in $ComputerName ) {
    $SID = GetComputerSID $ComputerNameItem
    if ( $null -ne $SID ) {
      [PSCustomObject] @{
        "ComputerName" = $ComputerNameItem
        "SID"          = $SID
      }
    }
  }
}
-----[snip]----

One advantage here is the support for the PowerShell pipeline, so you can
pipe computer names to the script and it will output objects containing the
computer names and SIDs.

-- 
Problem reports:      https://cygwin.com/problems.html
FAQ:                  https://cygwin.com/faq/
Documentation:        https://cygwin.com/docs.html
Unsubscribe info:     https://cygwin.com/ml/#unsubscribe-simple