[CmdletBinding()] param( [string]$ComputerName, [switch]$RevealPassword, [string]$InventoryRoot = "$env:ProgramData\SGU\RustDesk\Devices" ) $ErrorActionPreference = 'Stop' function Assert-Administrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Only a local administrator can read the RustDesk device inventory.' } } function Initialize-DataProtection { if (-not ('SguRustDeskDataProtection' -as [type])) { Add-Type -TypeDefinition @' using System; using System.ComponentModel; using System.Runtime.InteropServices; public static class SguRustDeskDataProtection { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct DataBlob { public int cbData; public IntPtr pbData; } [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool CryptProtectData(ref DataBlob input, string description, IntPtr entropy, IntPtr reserved, IntPtr prompt, int flags, out DataBlob output); [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool CryptUnprotectData(ref DataBlob input, IntPtr description, IntPtr entropy, IntPtr reserved, IntPtr prompt, int flags, out DataBlob output); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LocalFree(IntPtr memory); private const int CryptProtectLocalMachine = 0x4; private static DataBlob ToBlob(byte[] value) { var blob = new DataBlob { cbData = value.Length, pbData = IntPtr.Zero }; if (value.Length > 0) { blob.pbData = Marshal.AllocHGlobal(value.Length); Marshal.Copy(value, 0, blob.pbData, value.Length); } return blob; } private static byte[] FromBlob(DataBlob blob) { var value = new byte[blob.cbData]; if (blob.cbData > 0) Marshal.Copy(blob.pbData, value, 0, blob.cbData); return value; } public static byte[] Protect(byte[] value) { var input = ToBlob(value); var output = new DataBlob(); try { if (!CryptProtectData(ref input, null, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, CryptProtectLocalMachine, out output)) throw new Win32Exception(Marshal.GetLastWin32Error()); return FromBlob(output); } finally { if (input.pbData != IntPtr.Zero) Marshal.FreeHGlobal(input.pbData); if (output.pbData != IntPtr.Zero) LocalFree(output.pbData); } } public static byte[] Unprotect(byte[] value) { var input = ToBlob(value); var output = new DataBlob(); try { if (!CryptUnprotectData(ref input, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, 0, out output)) throw new Win32Exception(Marshal.GetLastWin32Error()); return FromBlob(output); } finally { if (input.pbData != IntPtr.Zero) Marshal.FreeHGlobal(input.pbData); if (output.pbData != IntPtr.Zero) LocalFree(output.pbData); } } } '@ -ErrorAction Stop } } Assert-Administrator Initialize-DataProtection if (-not (Test-Path -LiteralPath $InventoryRoot -PathType Container)) { return @() } $entries = @(Get-ChildItem -LiteralPath $InventoryRoot -Filter '*.json' -File | ForEach-Object { $metadata = Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json if ($ComputerName -and -not $metadata.ComputerName.Equals($ComputerName, [StringComparison]::OrdinalIgnoreCase)) { return } $result = [ordered]@{ ComputerName = [string]$metadata.ComputerName RustDeskId = [string]$metadata.RustDeskId RegisteredAt = [datetime]$metadata.RegisteredAt } if ($RevealPassword) { $secretPath = [string]$metadata.SecretPath if (-not (Test-Path -LiteralPath $secretPath -PathType Leaf)) { throw "The protected RustDesk credential for $($metadata.ComputerName) is missing." } $result.AccessPassword = [Text.Encoding]::UTF8.GetString( [SguRustDeskDataProtection]::Unprotect( [IO.File]::ReadAllBytes($secretPath))) } [pscustomobject]$result }) $entries | Sort-Object ComputerName