【问题标题】:Granting SeServiceLogonRight to a user from PowerShell从 PowerShell 向用户授予 SeServiceLogonRight
【发布时间】:2012-04-17 08:38:25
【问题描述】:

在我目前正在编写的脚本中,我创建了一个专用用户来启动我们内部开发的一些 Windows 服务。为了启动这些服务,我们的“专用”用户需要 SeServiceLogonRight 权限。目前,我正在使用 ntrights.exe 分配该权限,并从我的 PowerShell 脚本中进行以下调用:

{.$global:RootInstallDir\..\Common\SupportTools\ntrights.exe -m $env:COMPUTERNAME -u $HealthLinkUser +r SeServiceLogonRight }

但是,我对此并不满意。 PowerShell 必须有一种更简洁的方法。

【问题讨论】:

标签: powershell


【解决方案1】:

此脚本包含运行良好且没有任何依赖关系的函数:

https://gallery.technet.microsoft.com/scriptcenter/Grant-Revoke-Query-user-26e259b0

除此之外,这里是您的问题的具体解决方案:

function Add-ServiceLogonRight([string] $Username) {
    Write-Host "Enable ServiceLogonRight for $Username"

    $tmp = New-TemporaryFile
    secedit /export /cfg "$tmp.inf" | Out-Null
    (gc -Encoding ascii "$tmp.inf") -replace '^SeServiceLogonRight .+', "`$0,$Username" | sc -Encoding ascii "$tmp.inf"
    secedit /import /cfg "$tmp.inf" /db "$tmp.sdb" | Out-Null
    secedit /configure /db "$tmp.sdb" /cfg "$tmp.inf" | Out-Null
    rm $tmp* -ea 0
}

【讨论】:

  • 谢谢!但是,我不得不从 gc 和 sc 中删除“-Encoding ascii”以支持用户名中的非 ascii 字符(例如 äëöüï)。
  • 感谢您的来信。应该是这样的。
  • 回到这个答案我想知道:最后一个 secedit 命令中的 /cfg 参数不是多余的吗?我们已经通过上一行的 /import 命令将模板导入到数据库中。
  • @funforums 这不是因为 import 只是将模板导入数据库。 Configure 实际上将该数据库应用于服务器。
【解决方案2】:

这对我有用。您可以决定哪个更干净;-) 关键是 LsaAddAccountRights windows API 函数。

Add-Type @'
using System;
using System.Collections.Generic;
using System.Text;

namespace MyLsaWrapper
{
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Management;
    using System.Runtime.CompilerServices;
    using System.ComponentModel;

    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", 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);

    }
    /// <summary>
    /// This class is used to grant "Log on as a service", "Log on as a batchjob", "Log on localy" etc.
    /// to a user.
    /// </summary>
    public sealed class LsaWrapper : IDisposable
    {
        [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;
        }

        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 void AddPrivileges(string account, string privilege)
        {
            IntPtr pSid = GetSIDInformation(account);
            LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
            privileges[0] = InitLsaString(privilege);
            uint ret = Win32Sec.LsaAddAccountRights(lsaHandle, 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

        IntPtr GetSIDInformation(string account)
        {
            LSA_UNICODE_STRING[] names = new LSA_UNICODE_STRING[1];
            LSA_TRANSLATED_SID2 lts;
            IntPtr tsids = IntPtr.Zero;
            IntPtr tdom = IntPtr.Zero;
            names[0] = InitLsaString(account);
            lts.Sid = IntPtr.Zero;
            Console.WriteLine("String account: {0}", names[0].Length);
            int ret = Win32Sec.LsaLookupNames2(lsaHandle, 0, 1, names, ref tdom, ref tsids);
            if (ret != 0)
                throw new Win32Exception(Win32Sec.LsaNtStatusToWinError(ret));
            lts = (LSA_TRANSLATED_SID2)Marshal.PtrToStructure(tsids,
            typeof(LSA_TRANSLATED_SID2));
            Win32Sec.LsaFreeMemory(tsids);
            Win32Sec.LsaFreeMemory(tdom);
            return lts.Sid;
        }

        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);
            }
        }
    }
}
'@

[MyLsaWrapper.LsaWrapperCaller]::AddPrivileges("andy", "SeServiceLogonRight")

【讨论】:

  • 我使用它是因为我不想依赖 ntrights.exe。它有效,但仅在第二次尝试时有效。第一次尝试时,它在 AddPrivileges 中引发 Win32Exception -“参数不正确”。还没弄清楚原因。
【解决方案3】:

这确实应该是对安迪回答的评论,但我没有评论所需的代表。

提供的代码将访问已释放的内存——这可能“有效”(有时)但总是不好的做法。请参阅我对另一个关于相同代码问题的回答:Why might LsaAddAccountRights return STATUS_INVALID_PARAMETER?

看来这段代码已经流传了好几年了。

【讨论】:

    【解决方案4】:

    使用 C# 调用系统 DLL 过程的 P/Invoke 技术基本相同,但这个示例可能更容易理解:

    $code = @"
    using System;
    using System.Runtime.InteropServices;
    
     public class TokenManipulator
     {
    
      [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
      ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
    
      [DllImport("kernel32.dll", ExactSpelling = true)]
      internal static extern IntPtr GetCurrentProcess();
    
      [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
      internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr
      phtok);
    
      [DllImport("advapi32.dll", SetLastError = true)]
      internal static extern bool LookupPrivilegeValue(string host, string name,
      ref long pluid);
    
      [StructLayout(LayoutKind.Sequential, Pack = 1)]
      internal struct TokPriv1Luid
      {
       public int Count;
       public long Luid;
       public int Attr;
      }
    
      internal const int SE_PRIVILEGE_DISABLED = 0x00000000;
      internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
      internal const int TOKEN_QUERY = 0x00000008;
      internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
    
      public static bool AddPrivilege(string privilege)
      {
       try
       {
        bool retVal;
        TokPriv1Luid tp;
        IntPtr hproc = GetCurrentProcess();
        IntPtr htok = IntPtr.Zero;
        retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
        tp.Count = 1;
        tp.Luid = 0;
        tp.Attr = SE_PRIVILEGE_ENABLED;
        retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
        retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
        return retVal;
       }
       catch (Exception ex)
       {
        throw ex;
       }
      }
    
      public static bool RemovePrivilege(string privilege)
      {
       try
       {
        bool retVal;
        TokPriv1Luid tp;
        IntPtr hproc = GetCurrentProcess();
        IntPtr htok = IntPtr.Zero;
        retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok);
        tp.Count = 1;
    
        tp.Luid = 0;
        tp.Attr = SE_PRIVILEGE_DISABLED;
        retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid);
        retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
        return retVal;
       }
       catch (Exception ex)
       {
        throw ex;
       }
      }
    
     }
    "@
    
    add-type $code
    
    "`nInitial privileges"
    whoami /priv | Select-String "SeRestorePrivilege"
    whoami /priv | Select-String "SeBackupPrivilege"
    whoami /priv | Select-String "SeTakeOwnershipPrivilege"
    
    "`nAdding privileges"
    #Activate necessary admin privileges to make changes without NTFS perms
    [void][TokenManipulator]::AddPrivilege("SeRestorePrivilege") #Necessary to set Owner Permissions
    [void][TokenManipulator]::AddPrivilege("SeBackupPrivilege") #Necessary to bypass Traverse Checking
    [void][TokenManipulator]::AddPrivilege("SeTakeOwnershipPrivilege") #Necessary to override FilePermissions 
    whoami /priv | Select-String "SeRestorePrivilege"
    whoami /priv | Select-String "SeBackupPrivilege"
    whoami /priv | Select-String "SeTakeOwnershipPrivilege"
    
    "`nRemoving privileges just added before terminating"
    [void][TokenManipulator]::AddPrivilege("SeRestorePrivilege") #Necessary to set Owner Permissions
    [void][TokenManipulator]::AddPrivilege("SeBackupPrivilege") #Necessary to bypass Traverse Checking
    [void][TokenManipulator]::AddPrivilege("SeTakeOwnershipPrivilege") #Necessary to override 
    whoami /priv | Select-String "SeRestorePrivilege"
    whoami /priv | Select-String "SeBackupPrivilege"
    whoami /priv | Select-String "SeTakeOwnershipPrivilege"
    

    这让我很困惑,并且有很多论坛主题以“使用第 3 方”应用程序结尾的例子。如果 .Net 包含这个必要的功能会很好,但是尽管此代码示例很长,但重现起来非常简单:) 干杯,巴纳比

    【讨论】:

      【解决方案5】:

      我就是这样解决的:

      基于:this article

      您可以下载Carbon from here

      首先导入Carbon模块如下:

      Import-Module -Name $Path_To_Carbon -Global -Prefix CA
      
      [array]$UserPrivileges = Get-CAPrivileges -Identity $UserName;
      [bool]$LogOnAsAServiceprivilegeFound = $false;
      
      if ($UserPrivileges.Length > 0)
      {
          if ($UserPrivileges -contains "SeServiceLogonRight")
          {
              $LogOnAsAServiceprivilegeFound = $true;
          }
      }
      
      if ($LogOnAsAServiceprivilegeFound -eq $false)
      {
          Grant-CAPrivilege -Identity $UserName "SeServiceLogonRight"
      }
      

      【讨论】:

        【解决方案6】:

        现在(PowerShell 5.1 以后)您可以使用Add-LocalGroupMember 添加,并使用Get-LocalGroupMember 进行检查。例如,要添加用户并执行(基本)验证,您可以使用以下内容:

        $addGroup = "SeServiceLogonRight"
        $HealthLinkUser = "whatever"
        Add-LocalGroupMember -Group "$addGroup" -Member "$HealthLinkUser"
        if(Get-LocalGroupMember -Group "$addGroup" -Member "$HealthLinkUser")
        {
            Write-Host "Added:`tMember: `"$HealthLinkUser`" to group: `"$addGroup`""
        }
        else
        {
            Write-Host "ERROR: Unable to add:`tMember: `"$HealthLinkUser`" to group: `"$addGroup`""
        }
        

        【讨论】:

        • 当我用 PS 5.1 甚至 7.1.1 运行它时,我只会得到 Group SeServiceLogonRight was not found.
        • 你在什么操作系统上尝试这个?如果是 Windows 10 家庭版(或 Win8),我相信这将不可用。我认为您需要 Win 10 Pro(或 Windows 服务器操作系统)。
        • 我在 Windows Server 2016 和 Windows 10 Enterprise 上运行了这个。
        • SeServiceLogonRight 不是组,将用户添加到组并不能解决原来的问题。
        猜你喜欢
        • 2010-10-31
        • 1970-01-01
        • 1970-01-01
        • 2019-04-13
        • 2020-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多