There are lots of “solutions” out there that just shell out to ntrights.exe or secedit or something else not powershell, and say “but powershell calls it so it counts!” No it doesn’t.
There is no native NET or COM interface to manage local user rights assignment. You have to use P/Invoke to call the API. It’s a pain. But I swiped some code from Roel van Lisdonk (http://www.roelvanlisdonk.nl/?p=1151) and got it working. Looks like he got the code from Willy Denoyette (see http://www.pinvoke.net/default.aspx/advapi32.lsalookupsids) which I kept intact except for a minor error correction (it was missing a semicolon and wouldn’t compile). See http://www.hightechtalks.com/csharp/lsa-functions-276626.html for what seems to be Willy’s original source.
$LsaWrapper = @' using System; using System.Collections.Generic; using System.Text; namespace LsaSecurity { /* * LsaWrapper class credit: Willy Denoyette [MVP] * * http://www.hightechtalks.com/csharp/lsa-functions-276626.html * * Added support for: * * LsaLookupSids * * for the purposes of providing a working example. * * * */ using System.Runtime.InteropServices; using System.Security; using System.Management; using System.Runtime.CompilerServices; using System.ComponentModel; using LSA_HANDLE = IntPtr; public class Program { public static void Main() { using (LsaWrapper lsaSec = new LsaWrapper()) { string[] accounts = lsaSec.GetUsersWithPrivilege("SeNetworkLogonRight"); } } } [StructLayout(LayoutKind.Sequential)] struct LSA_OBJECT_ATTRIBUTES { internal int Length; internal IntPtr RootDirectory; internal IntPtr ObjectName; internal int Attributes; internal IntPtr SecurityDescriptor; internal IntPtr SecurityQualityOfService; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct LSA_UNICODE_STRING { internal ushort Length; internal ushort MaximumLength; [MarshalAs(UnmanagedType.LPWStr)] internal string Buffer; } sealed class Win32Sec { [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern uint LsaOpenPolicy( LSA_UNICODE_STRING[] SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, int AccessMask, out IntPtr PolicyHandle ); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern uint LsaAddAccountRights( LSA_HANDLE PolicyHandle, IntPtr pSID, LSA_UNICODE_STRING[] UserRights, int CountOfRights ); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern uint LsaRemoveAccountRights( LSA_HANDLE PolicyHandle, IntPtr pSID, bool allRights, LSA_UNICODE_STRING[] UserRights, int CountOfRights ); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern uint LsaEnumerateAccountsWithUserRight( LSA_HANDLE PolicyHandle, LSA_UNICODE_STRING[] UserRights, out IntPtr EnumerationBuffer, out int CountReturned ); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern uint LsaLookupSids( LSA_HANDLE PolicyHandle, int count, IntPtr buffer, out LSA_HANDLE domainList, out LSA_HANDLE nameList ); [DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] internal static extern int LsaLookupNames2( LSA_HANDLE PolicyHandle, uint Flags, uint Count, LSA_UNICODE_STRING[] Names, ref IntPtr ReferencedDomains, ref IntPtr Sids ); [DllImport("advapi32")] internal static extern int LsaNtStatusToWinError(int NTSTATUS); [DllImport("advapi32")] internal static extern int LsaClose(IntPtr PolicyHandle); [DllImport("advapi32")] internal static extern int LsaFreeMemory(IntPtr Buffer); } public sealed class LsaWrapper : IDisposable { private bool _writeToConsole = false; [StructLayout(LayoutKind.Sequential)] struct LSA_TRUST_INFORMATION { internal LSA_UNICODE_STRING Name; internal IntPtr Sid; } [StructLayout(LayoutKind.Sequential)] struct LSA_TRANSLATED_SID2 { internal SidNameUse Use; internal IntPtr Sid; internal int DomainIndex; uint Flags; } //[StructLayout(LayoutKind.Sequential)] //struct LSA_REFERENCED_DOMAIN_LIST //{ // internal uint Entries; // internal LSA_TRUST_INFORMATION Domains; //} // Commented by KaushalendraATgmail.com [StructLayout(LayoutKind.Sequential)] internal struct LSA_REFERENCED_DOMAIN_LIST { internal uint Entries; internal IntPtr Domains; } [StructLayout(LayoutKind.Sequential)] struct LSA_ENUMERATION_INFORMATION { internal LSA_HANDLE PSid; } [StructLayout(LayoutKind.Sequential)] struct LSA_SID { internal uint Sid; } [StructLayout(LayoutKind.Sequential)] struct LSA_TRANSLATED_NAME { internal SidNameUse Use; internal LSA_UNICODE_STRING Name; internal int DomainIndex; } enum SidNameUse : int { User = 1, Group = 2, Domain = 3, Alias = 4, KnownGroup = 5, DeletedAccount = 6, Invalid = 7, Unknown = 8, Computer = 9 } enum Access : int { POLICY_READ = 0x20006, POLICY_ALL_ACCESS = 0x00F0FFF, POLICY_EXECUTE = 0X20801, POLICY_WRITE = 0X207F8 } const uint STATUS_ACCESS_DENIED = 0xc0000022; const uint STATUS_INSUFFICIENT_RESOURCES = 0xc000009a; const uint STATUS_NO_MEMORY = 0xc0000017; IntPtr lsaHandle; public LsaWrapper() : this(null) { } // // local system if systemName is null public LsaWrapper(string systemName) { LSA_OBJECT_ATTRIBUTES lsaAttr; lsaAttr.RootDirectory = IntPtr.Zero; lsaAttr.ObjectName = IntPtr.Zero; lsaAttr.Attributes = 0; lsaAttr.SecurityDescriptor = IntPtr.Zero; lsaAttr.SecurityQualityOfService = IntPtr.Zero; lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES)); lsaHandle = IntPtr.Zero; LSA_UNICODE_STRING[] system = null; if (systemName != null) { system = new LSA_UNICODE_STRING[1]; system[0] = InitLsaString(systemName); } uint ret = Win32Sec.LsaOpenPolicy(system, ref lsaAttr, (int)Access.POLICY_ALL_ACCESS, out lsaHandle); if (ret == 0) return; if (ret == STATUS_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY)) { throw new OutOfMemoryException(); } throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret)); } public string[] GetUsersWithPrivilege(string privilege) { LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1]; privileges[0] = InitLsaString(privilege); IntPtr buffer; int count; uint ret = Win32Sec.LsaEnumerateAccountsWithUserRight(lsaHandle, privileges, out buffer, out count); if (ret != 0) { if (ret == STATUS_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } if (ret == STATUS_INSUFFICIENT_RESOURCES || ret == STATUS_NO_MEMORY) { throw new OutOfMemoryException(); } throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret)); } LSA_ENUMERATION_INFORMATION[] lsaInfo = new LSA_ENUMERATION_INFORMATION[count]; for (int i = 0, elemOffs = (int)buffer; i < count; i++) { lsaInfo[i] = (LSA_ENUMERATION_INFORMATION)Marshal.PtrToStructure((IntPtr)elemOffs, typeof(LSA_ENUMERATION_INFORMATION)); elemOffs += Marshal.SizeOf(typeof(LSA_ENUMERATION_INFORMATION)); } LSA_HANDLE domains; LSA_HANDLE names; ret = Win32Sec.LsaLookupSids(lsaHandle, lsaInfo.Length, buffer, out domains, out names); if (ret != 0) { if (ret == STATUS_ACCESS_DENIED) { throw new UnauthorizedAccessException(); } if (ret == STATUS_INSUFFICIENT_RESOURCES || ret == STATUS_NO_MEMORY) { throw new OutOfMemoryException(); } throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret)); } /*string[] retNames = new string[count]; LSA_TRANSLATED_NAME[] lsaNames = new LSA_TRANSLATED_NAME[count]; for (int i = 0, elemOffs = (int)names; i < count; i++) { lsaNames[i] = (LSA_TRANSLATED_NAME)Marshal.PtrToStructure((LSA_HANDLE)elemOffs, typeof(LSA_TRANSLATED_NAME)); elemOffs += Marshal.SizeOf(typeof(LSA_TRANSLATED_NAME)); LSA_UNICODE_STRING name = lsaNames[i].Name; retNames[i] = name.Buffer.Substring(0, name.Length / 2); }*/ // Following code also fetches Domains and associates domains and usernames string[] retNames = new string[count]; List currentDomain = new List(); int domainCount = 0; LSA_TRANSLATED_NAME[] lsaNames = new LSA_TRANSLATED_NAME[count]; for (int i = 0, elemOffs = (int)names; i < count; i++) { lsaNames[i] = (LSA_TRANSLATED_NAME)Marshal.PtrToStructure((LSA_HANDLE)elemOffs, typeof(LSA_TRANSLATED_NAME)); elemOffs += Marshal.SizeOf(typeof(LSA_TRANSLATED_NAME)); LSA_UNICODE_STRING name = lsaNames[i].Name; retNames[i] = name.Buffer.Substring(0, name.Length / 2); if (!currentDomain.Contains(lsaNames[i].DomainIndex)) { domainCount = domainCount + 1; currentDomain.Add(lsaNames[i].DomainIndex); } } string[] domainPtrNames = new string[count]; LSA_REFERENCED_DOMAIN_LIST[] lsaDomainNames = new LSA_REFERENCED_DOMAIN_LIST[count]; for (int i = 0, elemOffs = (int)domains; i < count; i++) { lsaDomainNames[i] = (LSA_REFERENCED_DOMAIN_LIST)Marshal.PtrToStructure((LSA_HANDLE)elemOffs, typeof(LSA_REFERENCED_DOMAIN_LIST)); elemOffs += Marshal.SizeOf(typeof(LSA_REFERENCED_DOMAIN_LIST)); } LSA_TRUST_INFORMATION[] lsaDomainName = new LSA_TRUST_INFORMATION[count]; string[] domainNames = new string[domainCount]; for (int i = 0, elemOffs = (int)lsaDomainNames[i].Domains; i < domainCount; i++) { lsaDomainName[i] = (LSA_TRUST_INFORMATION)Marshal.PtrToStructure((LSA_HANDLE)elemOffs, typeof(LSA_TRUST_INFORMATION)); elemOffs += Marshal.SizeOf(typeof(LSA_TRUST_INFORMATION)); LSA_UNICODE_STRING tempDomain = lsaDomainName[i].Name; //if(tempDomain.Buffer != null) //{ domainNames[i] = tempDomain.Buffer.Substring(0, tempDomain.Length / 2); //} } string[] domainUserName = new string[count]; for (int i = 0; i 0x7ffe) throw new ArgumentException("String too long"); LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING(); lus.Buffer = s; lus.Length = (ushort)(s.Length * sizeof(char)); lus.MaximumLength = (ushort)(lus.Length + sizeof(char)); // If unicode issues then do this instead of previous two line //lus.Length = (ushort)(s.Length * 2); // Unicode char is 2 bytes //lus.MaximumLength = (ushort)(lus.Length + 2) return lus; } public bool WriteToConsole { set { this._writeToConsole = value; } } } public class LsaWrapperCaller { public static void AddPrivileges(string account, string privilege) { using (LsaWrapper lsaWrapper = new LsaWrapper()) { lsaWrapper.AddPrivileges(account, privilege); } } public static void RemovePrivileges(string account, string privilege) { using (LsaWrapper lsaWrapper = new LsaWrapper()) { lsaWrapper.RemovePrivileges(account, privilege); } } } } '@ Add-Type $LsaWrapper $account = "computername\name" # Will default to local computer if you do not specify computername. Can also accept domain name there. Name is a user name or group name. $right = "SeServiceLogonRight" # see http://msdn.microsoft.com/en-us/library/windows/desktop/bb545671(v=vs.85).aspx for a list. [LsaSecurity.LsaWrapperCaller]::RemovePrivileges($account,$right);
Hi,
When I found your post I was really happy, because it was exactly what I was looking for!
I tried to use your code in one of my scripts, but I keep getting this error:
‘LsaSecurity.LsaWrapper’ does not implement interface member ‘System.IDisposable.Dispose()’
Unfortunately, my knowledge of C# is not nearly enough to know how to fix that.
I can see the line it fails on, but I have no idea how to fix it.
This is from a direct copy of your code above…
Hope you can help me out :)
Thanks,
Arjan.
It looks like wordpress ate some of the code. It doesn’t work when I copy it out, and I can’t find my original files. It’s going to take me a while to clean it up again… I’m not so hot at C# either. The problem you’re seeing is that it doesn’t implement a dispose member. But there are others that will come up after that. I’ll work on cleaning it up (and thanks for letting me know), but in the meantime you might try this: https://stackoverflow.com/questions/26392151/enabling-a-local-user-right-assignment-in-powershell
Did you happen to rebuild this yet… I would love to get my hands on it…
I have a scaled down version. Only does logon as batch. But it should be easy enough to make it do any other right. Try this: https://jordanmillsv2.blob.core.windows.net/public/Add-UserToLoginAsBatch.ps1