Private/Grant-IDBridgeBatchLogonRight.ps1

<#
.SYNOPSIS
Grant an account the local 'Log on as a batch job' right (internal).

.DESCRIPTION
Internal helper for Register-IDBridgeScheduledTask. Grants SeBatchLogonRight — the right
a scheduled-task principal needs — to the given account in the local security policy,
through a thin P/Invoke wrapper over advapi32.dll (LsaOpenPolicy / LsaAddAccountRights);
there is no in-box cmdlet for user rights. The type is compiled once per session.
Granting a right the account already holds succeeds unchanged, so the call is idempotent.

The grant is LOCAL: when a GPO manages 'Log on as a batch job', the GPO's list overwrites
it on the next policy refresh — the caller warns about that. Requires an elevated session.

.PARAMETER Identity
Account to grant the right, e.g. 'DOMAIN\gMSA-IDBridge$'.

.EXAMPLE
Grant-IDBridgeBatchLogonRight -Identity 'DOMAIN\gMSA-IDBridge$'

.NOTES
   Created by: Sam Cattanach
   Modified: 2026-08-27
#>

function Grant-IDBridgeBatchLogonRight {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string]$Identity
    )

    if (-not ('IDBridge.LsaRights' -as [type])) {
        Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;

namespace IDBridge
{
    public static class LsaRights
    {
        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_OBJECT_ATTRIBUTES
        {
            public int Length; public IntPtr RootDirectory; public IntPtr ObjectName;
            public uint Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct LSA_UNICODE_STRING
        {
            public ushort Length; public ushort MaximumLength; public IntPtr Buffer;
        }

        [DllImport("advapi32.dll")]
        private static extern uint LsaOpenPolicy(IntPtr systemName, ref LSA_OBJECT_ATTRIBUTES objectAttributes, uint desiredAccess, out IntPtr policyHandle);

        [DllImport("advapi32.dll")]
        private static extern uint LsaAddAccountRights(IntPtr policyHandle, byte[] accountSid, LSA_UNICODE_STRING[] userRights, uint countOfRights);

        [DllImport("advapi32.dll")]
        private static extern uint LsaNtStatusToWinError(uint status);

        [DllImport("advapi32.dll")]
        private static extern uint LsaClose(IntPtr policyHandle);

        private const uint POLICY_CREATE_ACCOUNT = 0x00000010;
        private const uint POLICY_LOOKUP_NAMES = 0x00000800;

        public static void AddRight(byte[] sid, string rightName)
        {
            var attributes = new LSA_OBJECT_ATTRIBUTES();
            IntPtr policy;
            uint status = LsaOpenPolicy(IntPtr.Zero, ref attributes, POLICY_CREATE_ACCOUNT | POLICY_LOOKUP_NAMES, out policy);
            if (status != 0) throw new System.ComponentModel.Win32Exception((int)LsaNtStatusToWinError(status));
            try
            {
                var right = new LSA_UNICODE_STRING
                {
                    Buffer = Marshal.StringToHGlobalUni(rightName),
                    Length = (ushort)(rightName.Length * 2),
                    MaximumLength = (ushort)((rightName.Length + 1) * 2)
                };
                try
                {
                    status = LsaAddAccountRights(policy, sid, new[] { right }, 1);
                    if (status != 0) throw new System.ComponentModel.Win32Exception((int)LsaNtStatusToWinError(status));
                }
                finally { Marshal.FreeHGlobal(right.Buffer); }
            }
            finally { LsaClose(policy); }
        }
    }
}
'@

    }

    try {
        $sid = ([System.Security.Principal.NTAccount]$Identity).Translate([System.Security.Principal.SecurityIdentifier])
        $sidBytes = [byte[]]::new($sid.BinaryLength)
        $sid.GetBinaryForm($sidBytes, 0)
        [IDBridge.LsaRights]::AddRight($sidBytes, 'SeBatchLogonRight')
        Write-Log -Message "Task: Granted 'Log on as a batch job' (SeBatchLogonRight) to '$Identity'."
    }
    catch { Throw "Granting 'Log on as a batch job' to '$Identity' failed (elevated session required): $($_)" }
}