Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Add Logon As Service Right with PowerShell

Posted on February 20, 2014February 20, 2014

talkbubble I saw a comment on Twitter today about a limitation in PowerShell. Specifically the ability to grant the logon as a service right to a user account. Manually, if you use the Services management console and specify the user, Windows will automatically grant that right. But if you are trying to do this from a command line that is a bit more challenging. There has been an old resource kit tool called NTRights which can easily get the job done. And I have no problem calling a command line tool that is designed for a special purpose. But, you first need to get a hold of that utility.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

Another option, which I've also used in the past, is to use some scripting language to modify the local security policy on the fly. Typically this involves creating a database entry and then calling SECEDIT. I came across a PowerShell script that does this but you could easily do it with VBScript. There's nothing uniquely PowerShell about it.

Granting this privilege requires some arcane (at least to me) API calls. In my research I found several examples. Then I came across this great link. The author, Morgan, has a number of options for achieving this task. Unfortunately, his PowerShell solution requires a third party DLL. But fortunately, he also has a C# solution.

Why is this fortunate? Because PowerShell is a management engine with great depth. You can run commands interactively, you can create scripts and advanced functions, or you can leverage languages like C#. I can use Morgan's C# class definition and add it to PowerShell.

#http://www.morgantechspace.com/2013/11/Set-or-Grant-Logon-As-A-Service-right-to-User.html

$class=@"
public class LsaWrapper 
{
// Import the LSA functions
 
[DllImport("advapi32.dll", PreserveSig = true)]
private static extern UInt32 LsaOpenPolicy(
    ref LSA_UNICODE_STRING SystemName,
    ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
    Int32 DesiredAccess,
    out IntPtr PolicyHandle
    );
 
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern long LsaAddAccountRights(
    IntPtr PolicyHandle,
    IntPtr AccountSid,
    LSA_UNICODE_STRING[] UserRights,
    long CountOfRights);
 
[DllImport("advapi32")]
public static extern void FreeSid(IntPtr pSid);
 
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, PreserveSig = true)]
private static extern bool LookupAccountName(
    string lpSystemName, string lpAccountName,
    IntPtr psid,
    ref int cbsid,
    StringBuilder domainName, ref int cbdomainLength, ref int use);
 
[DllImport("advapi32.dll")]
private static extern bool IsValidSid(IntPtr pSid);
 
[DllImport("advapi32.dll")]
private static extern long LsaClose(IntPtr ObjectHandle);
 
[DllImport("kernel32.dll")]
private static extern int GetLastError();
 
[DllImport("advapi32.dll")]
private static extern long LsaNtStatusToWinError(long status);
 
// define the structures
 
private enum LSA_AccessPolicy : long
{
    POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
    POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
    POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
    POLICY_TRUST_ADMIN = 0x00000008L,
    POLICY_CREATE_ACCOUNT = 0x00000010L,
    POLICY_CREATE_SECRET = 0x00000020L,
    POLICY_CREATE_PRIVILEGE = 0x00000040L,
    POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
    POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
    POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
    POLICY_SERVER_ADMIN = 0x00000400L,
    POLICY_LOOKUP_NAMES = 0x00000800L,
    POLICY_NOTIFICATION = 0x00001000L
}
 
[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
    public int Length;
    public IntPtr RootDirectory;
    public readonly LSA_UNICODE_STRING ObjectName;
    public UInt32 Attributes;
    public IntPtr SecurityDescriptor;
    public IntPtr SecurityQualityOfService;
}
 
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
    public UInt16 Length;
    public UInt16 MaximumLength;
    public IntPtr Buffer;
}
/// 
//Adds a privilege to an account
 
/// Name of an account - "domain\account" or only "account"
/// Name ofthe privilege
/// The windows error code returned by LsaAddAccountRights
public long SetRight(String accountName, String privilegeName)
{
    long winErrorCode = 0; //contains the last error
 
    //pointer an size for the SID
    IntPtr sid = IntPtr.Zero;
    int sidSize = 0;
    //StringBuilder and size for the domain name
    var domainName = new StringBuilder();
    int nameSize = 0;
    //account-type variable for lookup
    int accountType = 0;
 
    //get required buffer size
    LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
 
    //allocate buffers
    domainName = new StringBuilder(nameSize);
    sid = Marshal.AllocHGlobal(sidSize);
 
    //lookup the SID for the account
    bool result = LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize,
                                    ref accountType);
 
    //say what you're doing
    Console.WriteLine("LookupAccountName result = " + result);
    Console.WriteLine("IsValidSid: " + IsValidSid(sid));
    Console.WriteLine("LookupAccountName domainName: " + domainName);
 
    if (!result)
    {
        winErrorCode = GetLastError();
        Console.WriteLine("LookupAccountName failed: " + winErrorCode);
    }
    else
    {
        //initialize an empty unicode-string
        var systemName = new LSA_UNICODE_STRING();
        //combine all policies
        var access = (int) (
                                LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
                                LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
                                LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
                                LSA_AccessPolicy.POLICY_CREATE_SECRET |
                                LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
                                LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
                                LSA_AccessPolicy.POLICY_NOTIFICATION |
                                LSA_AccessPolicy.POLICY_SERVER_ADMIN |
                                LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
                                LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
                                LSA_AccessPolicy.POLICY_TRUST_ADMIN |
                                LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
                                LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
                            );
        //initialize a pointer for the policy handle
        IntPtr policyHandle = IntPtr.Zero;
 
        //these attributes are not used, but LsaOpenPolicy wants them to exists
        var ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
        ObjectAttributes.Length = 0;
        ObjectAttributes.RootDirectory = IntPtr.Zero;
        ObjectAttributes.Attributes = 0;
        ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
        ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
 
        //get a policy handle
        uint resultPolicy = LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);
        winErrorCode = LsaNtStatusToWinError(resultPolicy);
 
        if (winErrorCode != 0)
        {
            Console.WriteLine("OpenPolicy failed: " + winErrorCode);
        }
        else
        {
            //Now that we have the SID an the policy,
            //we can add rights to the account.
 
            //initialize an unicode-string for the privilege name
            var userRights = new LSA_UNICODE_STRING[1];
            userRights[0] = new LSA_UNICODE_STRING();
            userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
            userRights[0].Length = (UInt16) (privilegeName.Length*UnicodeEncoding.CharSize);
            userRights[0].MaximumLength = (UInt16) ((privilegeName.Length + 1)*UnicodeEncoding.CharSize);
 
            //add the right to the account
            long res = LsaAddAccountRights(policyHandle, sid, userRights, 1);
            winErrorCode = LsaNtStatusToWinError(res);
            if (winErrorCode != 0)
            {
                Console.WriteLine("LsaAddAccountRights failed: " + winErrorCode);
            }
 
            LsaClose(policyHandle);
        }
        FreeSid(sid);
    }
 
    return winErrorCode;
}    
}
"@

Add-Type -MemberDefinition $class -Name LSAWrapper -UsingNamespace System.Text -namespace Win32Util

Using Add-Type I can load it into my PowerShell session. Once loaded, I can create an object based on the class.

$lsawrapper = new-object Win32Util.LSAWrapper+LsaWrapper

The class name looks a bit funny but it must be because of how the class is defined. I'm not much of a developer type to know if this could be done any differently, but it works. With the class I can now grant an account the necessary privilege.

$lsawrapper.SetRight("jh-win81-ent\svcTest","SeServiceLogonRight")

The method writes a result to the pipeline.

PS C:\> $w.SetRight("jh-win81-ent\svcTest","SeServiceLogonRight")
LookupAccountName result = True
IsValidSid: True
LookupAccountName domainName: JH-WIN81-ENT
0

The class has a number of Console.WriteLine commands I could remove if I didn't want this level of detail. But it is possible to do this in PowerShell, because of how flexible the engine and language can be. I'm not implying this is easy. In fact, if I were to take this to the next step I'd build some advanced functions around it so it would at least be easy to use for other people. This is a great example of why you should learn PowerShell and how it can impact your career.

Update
Based on a comment I've revised the original class.

$class=@"
using System.Text;
using System;
using System.Runtime.InteropServices;

public static class LsaWrapper 
{
// Import the LSA functions
 
[DllImport("advapi32.dll", PreserveSig = true)]
private static extern UInt32 LsaOpenPolicy(
    ref LSA_UNICODE_STRING SystemName,
    ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
    Int32 DesiredAccess,
    out IntPtr PolicyHandle
    );
 
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
private static extern long LsaAddAccountRights(
    IntPtr PolicyHandle,
    IntPtr AccountSid,
    LSA_UNICODE_STRING[] UserRights,
    long CountOfRights);
 
[DllImport("advapi32")]
public static extern void FreeSid(IntPtr pSid);
 
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true, PreserveSig = true)]
private static extern bool LookupAccountName(
    string lpSystemName, string lpAccountName,
    IntPtr psid,
    ref int cbsid,
    StringBuilder domainName, ref int cbdomainLength, ref int use);
 
[DllImport("advapi32.dll")]
private static extern bool IsValidSid(IntPtr pSid);
 
[DllImport("advapi32.dll")]
private static extern long LsaClose(IntPtr ObjectHandle);
 
[DllImport("kernel32.dll")]
private static extern int GetLastError();
 
[DllImport("advapi32.dll")]
private static extern long LsaNtStatusToWinError(long status);
 
// define the structures
 
private enum LSA_AccessPolicy : long
{
    POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
    POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
    POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
    POLICY_TRUST_ADMIN = 0x00000008L,
    POLICY_CREATE_ACCOUNT = 0x00000010L,
    POLICY_CREATE_SECRET = 0x00000020L,
    POLICY_CREATE_PRIVILEGE = 0x00000040L,
    POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
    POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
    POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
    POLICY_SERVER_ADMIN = 0x00000400L,
    POLICY_LOOKUP_NAMES = 0x00000800L,
    POLICY_NOTIFICATION = 0x00001000L
}
 
[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES
{
    public int Length;
    public IntPtr RootDirectory;
    public readonly LSA_UNICODE_STRING ObjectName;
    public UInt32 Attributes;
    public IntPtr SecurityDescriptor;
    public IntPtr SecurityQualityOfService;
}
 
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING
{
    public UInt16 Length;
    public UInt16 MaximumLength;
    public IntPtr Buffer;
}
/// 
//Adds a privilege to an account
 
/// Name of an account - "domain\account" or only "account"
/// Name ofthe privilege
/// The windows error code returned by LsaAddAccountRights
public static long SetRight(String accountName, String privilegeName)
{
    long winErrorCode = 0; //contains the last error
 
    //pointer an size for the SID
    IntPtr sid = IntPtr.Zero;
    int sidSize = 0;
    //StringBuilder and size for the domain name
    var domainName = new StringBuilder();
    int nameSize = 0;
    //account-type variable for lookup
    int accountType = 0;
 
    //get required buffer size
    LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
 
    //allocate buffers
    domainName = new StringBuilder(nameSize);
    sid = Marshal.AllocHGlobal(sidSize);
 
    //lookup the SID for the account
    bool result = LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize,
                                    ref accountType);
 
    //say what you're doing
    Console.WriteLine("LookupAccountName result = " + result);
    Console.WriteLine("IsValidSid: " + IsValidSid(sid));
    Console.WriteLine("LookupAccountName domainName: " + domainName);
 
    if (!result)
    {
        winErrorCode = GetLastError();
        Console.WriteLine("LookupAccountName failed: " + winErrorCode);
        //return winErrorCode;
    }
    else
    {
        //initialize an empty unicode-string
        var systemName = new LSA_UNICODE_STRING();
        //combine all policies
        var access = (int) (
                                LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
                                LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
                                LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
                                LSA_AccessPolicy.POLICY_CREATE_SECRET |
                                LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
                                LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
                                LSA_AccessPolicy.POLICY_NOTIFICATION |
                                LSA_AccessPolicy.POLICY_SERVER_ADMIN |
                                LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
                                LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
                                LSA_AccessPolicy.POLICY_TRUST_ADMIN |
                                LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
                                LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
                            );
        //initialize a pointer for the policy handle
        IntPtr policyHandle = IntPtr.Zero;
 
        //these attributes are not used, but LsaOpenPolicy wants them to exists
        var ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
        ObjectAttributes.Length = 0;
        ObjectAttributes.RootDirectory = IntPtr.Zero;
        ObjectAttributes.Attributes = 0;
        ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
        ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
 
        //get a policy handle
        uint resultPolicy = LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);
        winErrorCode = LsaNtStatusToWinError(resultPolicy);
 
        if (winErrorCode != 0)
        {
            Console.WriteLine("OpenPolicy failed: " + winErrorCode);
            //return winErrorCode;
        }
        else
        {
            //Now that we have the SID an the policy,
            //we can add rights to the account.
 
            //initialize an unicode-string for the privilege name
            var userRights = new LSA_UNICODE_STRING[1];
            userRights[0] = new LSA_UNICODE_STRING();
            userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
            userRights[0].Length = (UInt16) (privilegeName.Length*UnicodeEncoding.CharSize);
            userRights[0].MaximumLength = (UInt16) ((privilegeName.Length + 1)*UnicodeEncoding.CharSize);
 
            //add the right to the account
            long res = LsaAddAccountRights(policyHandle, sid, userRights, 1);
            winErrorCode = LsaNtStatusToWinError(res);
            if (winErrorCode != 0)
            {
                Console.WriteLine("LsaAddAccountRights failed: " + winErrorCode);
                //return winErrorCode;
            }
 
            LsaClose(policyHandle);
        }
        FreeSid(sid);
    }
 
    return winErrorCode;
}

}
"@

I'll admit this is beyond what I normally do in PowerShell but I'm willing to learn new things and did with this challenge. With this class it is much easier to add the type and invoke the method.


Behind the PowerShell Pipeline
PS C:\> Add-Type -TypeDefinition $class 
PS C:\> [lsawrapper]::SetRight("jh-win81-ent\svctest","SeServiceLogonRight")
LookupAccountName result = True
IsValidSid: True
LookupAccountName domainName: JH-WIN81-ENT
0

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

9 thoughts on “Add Logon As Service Right with PowerShell”

  1. jvierra says:
    February 20, 2014 at 3:15 pm

    Here are some tricks to use for API utility functions that make them easier to use.

    Change the beginning to:

    $class=@”
    using System.Text;
    using System;
    using System.Runtime.InteropServices;
    public static class LsaWrapper{

    Notice I addeded the references and the “static” declaration. This makes generation of the type cleaner.

    Now change the function to static:

    public static long SetRight(String accountName, String privilegeName)

    Now add the type like this:

    Add-Type $class

    And call it like this:

    [LsaWrapper]::SetRight(‘jh-win81-ent\svcTest’,’SeServiceLogonRight’)

    I combine dozens of these into one class. They are easy to load and easy to use.

    1. Jeffery Hicks says:
      February 20, 2014 at 3:26 pm

      Excellent. I was trying to use the class definition as I found it. Clearly it wasn’t written with PowerShell in mind.

  2. Pingback: Zajímavé novinky ze svÄ›ta IT z 8. týdne 2014 | Igorovo
  3. Jonathan Medd says:
    March 5, 2014 at 8:46 am

    Thanks for this, it’s great – I can now ditch ntrights.exe for this job. Just one note, it requires at least PowerShell v3 otherwise it complains about the var keyword. From what I can gather this keyword is only supported from .NET 3.5 and later, hence PowerShell needs to be targeting at least that version.

    1. Jeffery Hicks says:
      March 5, 2014 at 10:37 am

      Thanks for that detail. I didn’t test this on anything other than my Windows 8.1 system.

  4. splatteredbits says:
    March 20, 2014 at 6:49 pm

    I am the owner/creator/maintainer of the [Carbon](http://get-carbon.org) project, a DevOps module we use to automate the setup of our servers. The code in your post reminded me that I had written something similar.

    In fact, I have four privilege-related functions

    * [Get-Privilege](http://get-carbon.org/help/Get-Privilege.html)
    * [Grant-Privilege](http://get-carbon.org/help/Grant-Privilege.html)
    * [Revoke-Privilege](http://get-carbon.org/help/Revoke-Privilege.html)
    * [Test-Privilege](http://get-carbon.org/help/Test-Privilege.html)

    Enjoy and thanks for the great posts!

    1. Jeffery Hicks says:
      March 20, 2014 at 7:08 pm

      thanks. I will have to take a look.

  5. Eric says:
    April 11, 2014 at 10:23 am

    Just need to make sure that your CLR environment can use the .NET 4.0 runtimes: http://stackoverflow.com/questions/2094694/how-can-i-run-powershell-with-the-net-4-runtime

  6. Pingback: Get-Scripting Podcast Episode 38 – (Happy New Year) | CrypticZero

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d