For PowerShell on Windows, adding a security privilege to a user takes more effort than one would expect. Here we show a method with no third party dependencies.

Discussion

Basically we use PowerShell’s Add-Type cmdlet to add .NET (C#) code which calls a Win32 API function (LsaAddAccountRights) to the script’s context.

We then instantiate a wrapper class and call a method which calls the LsaAddAccountRights function (with necessary setup and cleanup).

The PowerShell bits are taken from An answer on Stack Overflow, and the Win32 code is taken from Another answer on Stack Overflow.

The original variation of this code was found at http://www.hightechtalks.com/csharp/lsa-functions-276626.html which no longer exists but can be found on the WayBack Machine at https://web.archive.org/web/20151218044501/http://www.hightechtalks.com/csharp/lsa-functions-276626.html

A discussion of the hitechtalks code which I found helpful is at https://www.roelvanlisdonk.nl/2010/03/24/how-to-grant-log-on-as-a-service-rights-to-an-user-account-using-powershell/.

A handy list of of UserRights can be found here: https://blakedrumm.com/blog/set-and-check-user-rights-assignment/#set-user-rights, and is reproduced below.

PowerShell

This bit in the code below should be edited to suit your script, and all of the code should be in a Try/Catch block.

Write-Host -NoNewline "Denying interactive (desktop) login with account SomeUser..."
[AnLsaSecurityWrapper.LsaWrapperCaller]::AddPrivileges("SomeUser", "SeDenyInteractiveLogonRight")
Write-Host "done."

Full code

# Code from https://www.wildtechgarden.com/doc/sysadmin-devops/powershell-on-windows-adding-security-privileges-to-a-user

# Which is based on PowerShell from https://stackoverflow.com/a/14469248 and .NET
# code from https://stackoverflow.com/a/10188472

# Code adds a Win32 API class to PowerShell so we can use one
# of its methods (LsaAddAccountRights) to add the
# SeDenyInteractiveLogonRight to a user which only exists for
# background processing

Add-Type @'
using System;

namespace AnLsaSecurityWrapper
{
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Principal;
    using LSA_HANDLE = IntPtr;

    [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")]
      internal static extern int LsaNtStatusToWinError(int NTSTATUS);

      [DllImport("advapi32")]
      internal static extern int LsaClose(IntPtr PolicyHandle);

    }

    sealed class Sid : IDisposable
    {
      public IntPtr pSid = IntPtr.Zero;
      public SecurityIdentifier sid = null;

      public Sid(string account)
      {
          sid = (SecurityIdentifier) (new NTAccount(account)).Translate(typeof(SecurityIdentifier));
          Byte[] buffer = new Byte[sid.BinaryLength];
          sid.GetBinaryForm(buffer, 0);

          pSid = Marshal.AllocHGlobal(sid.BinaryLength);
          Marshal.Copy(buffer, 0, pSid, sid.BinaryLength);
      }

      public void Dispose()
      {
          if (pSid != IntPtr.Zero)
          {
            Marshal.FreeHGlobal(pSid);
            pSid = IntPtr.Zero;
          }
          GC.SuppressFinalize(this);
      }
      ~Sid()
      {
          Dispose();
      }
    }

    public sealed class LsaWrapper : IDisposable
    {
      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 void AddPrivileges(string account, string privilege)
      {
          uint ret = 0;
          using (Sid sid = new Sid(account))
          {
            LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
            privileges[0] = InitLsaString(privilege);
            ret = Win32Sec.LsaAddAccountRights(lsaHandle, sid.pSid, privileges, 1);
          }
          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 void Dispose()
      {
          if (lsaHandle != IntPtr.Zero)
          {
            Win32Sec.LsaClose(lsaHandle);
            lsaHandle = IntPtr.Zero;
          }
          GC.SuppressFinalize(this);
      }
      ~LsaWrapper()
      {
          Dispose();
      }
      // helper functions

      static LSA_UNICODE_STRING InitLsaString(string s)
      {
          // Unicode strings max. 32KB
          if (s.Length > 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));
          return lus;
      }
    }

    public class LsaWrapperCaller
    {
        public static void AddPrivileges(string account, string privilege)
        {
            using (LsaWrapper lsaWrapper = new LsaWrapper())
            {
                lsaWrapper.AddPrivileges(account, privilege);
            }
        }
    }
}
'@

Write-Host -NoNewline "Denying interactive (desktop) login with account SomeUser..."
[AnLsaSecurityWrapper.LsaWrapperCaller]::AddPrivileges("SomeUser", "SeDenyInteractiveLogonRight")
Write-Host "done."

List of UserRights (privileges) which can be added

PrivilegePrivilegeName
SeAssignPrimaryTokenPrivilegeReplace a process level token
SeAuditPrivilegeGenerate security audits
SeBackupPrivilegeBack up files and directories
SeBatchLogonRightLog on as a batch job
SeChangeNotifyPrivilegeBypass traverse checking
SeCreateGlobalPrivilegeCreate global objects
SeCreatePagefilePrivilegeCreate a pagefile
SeCreatePermanentPrivilegeCreate permanent shared objects
SeCreateSymbolicLinkPrivilegeCreate symbolic links
SeCreateTokenPrivilegeCreate a token object
SeDebugPrivilegeDebug programs
SeDelegateSessionUserImpersonatePrivilegeObtain an impersonation token for another user in the same session
SeDenyBatchLogonRightDeny log on as a batch job
SeDenyInteractiveLogonRightDeny log on locally
SeDenyNetworkLogonRightDeny access to this computer from the network
SeDenyRemoteInteractiveLogonRightDeny log on through Remote Desktop Services
SeDenyServiceLogonRightDeny log on as a service
SeEnableDelegationPrivilegeEnable computer and user accounts to be trusted for delegation
SeImpersonatePrivilegeImpersonate a client after authentication
SeIncreaseBasePriorityPrivilegeIncrease scheduling priority
SeIncreaseQuotaPrivilegeAdjust memory quotas for a process
SeIncreaseWorkingSetPrivilegeIncrease a process working set
SeInteractiveLogonRightAllow log on locally
SeLoadDriverPrivilegeLoad and unload device drivers
SeLockMemoryPrivilegeLock pages in memory
SeMachineAccountPrivilegeAdd workstations to domain
SeManageVolumePrivilegePerform volume maintenance tasks
SeNetworkLogonRightAccess this computer from the network
SeProfileSingleProcessPrivilegeProfile single process
SeRelabelPrivilegeModify an object label
SeRemoteInteractiveLogonRightAllow log on through Remote Desktop Services
SeRemoteShutdownPrivilegeForce shutdown from a remote system
SeRestorePrivilegeRestore files and directories
SeSecurityPrivilegeManage auditing and security log
SeServiceLogonRightLog on as a service
SeShutdownPrivilegeShut down the system
SeSyncAgentPrivilegeSynchronize directory service data
SeSystemEnvironmentPrivilegeModify firmware environment values
SeSystemProfilePrivilegeProfile system performance
SeSystemtimePrivilegeChange the system time
SeTakeOwnershipPrivilegeTake ownership of files or other objects
SeTcbPrivilegeAct as part of the operating system
SeTimeZonePrivilegeChange the time zone
SeTrustedCredManAccessPrivilegeAccess Credential Manager as a trusted caller
SeUndockPrivilegeRemove computer from docking stations

[From https://blakedrumm.com/blog/set-and-check-user-rights-assignment/#set-user-rights]