Skip to content

Commit aba7e50

Browse files
committed
Adds PowerShell Script to manage service user credentials
1 parent fcfaa03 commit aba7e50

File tree

1 file changed

+388
-0
lines changed

1 file changed

+388
-0
lines changed

ServiceUserManagement.ps1

Lines changed: 388 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,388 @@
1+
$Source = @"
2+
/*
3+
* Copyright 2012 Aaron Jensen
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
using System;
19+
using System.Collections.Generic;
20+
using System.ComponentModel;
21+
using System.Runtime.InteropServices;
22+
using System.Security.Principal;
23+
using System.Text;
24+
25+
namespace PSCarbon
26+
{
27+
public sealed class Lsa
28+
{
29+
// ReSharper disable InconsistentNaming
30+
[StructLayout(LayoutKind.Sequential)]
31+
internal struct LSA_UNICODE_STRING
32+
{
33+
internal LSA_UNICODE_STRING(string inputString)
34+
{
35+
if (inputString == null)
36+
{
37+
Buffer = IntPtr.Zero;
38+
Length = 0;
39+
MaximumLength = 0;
40+
}
41+
else
42+
{
43+
Buffer = Marshal.StringToHGlobalAuto(inputString);
44+
Length = (ushort)(inputString.Length * UnicodeEncoding.CharSize);
45+
MaximumLength = (ushort)((inputString.Length + 1) * UnicodeEncoding.CharSize);
46+
}
47+
}
48+
49+
internal ushort Length;
50+
internal ushort MaximumLength;
51+
internal IntPtr Buffer;
52+
}
53+
54+
[StructLayout(LayoutKind.Sequential)]
55+
internal struct LSA_OBJECT_ATTRIBUTES
56+
{
57+
internal uint Length;
58+
internal IntPtr RootDirectory;
59+
internal LSA_UNICODE_STRING ObjectName;
60+
internal uint Attributes;
61+
internal IntPtr SecurityDescriptor;
62+
internal IntPtr SecurityQualityOfService;
63+
}
64+
65+
[StructLayout(LayoutKind.Sequential)]
66+
public struct LUID
67+
{
68+
public uint LowPart;
69+
public int HighPart;
70+
}
71+
72+
// ReSharper disable UnusedMember.Local
73+
private const uint POLICY_VIEW_LOCAL_INFORMATION = 0x00000001;
74+
private const uint POLICY_VIEW_AUDIT_INFORMATION = 0x00000002;
75+
private const uint POLICY_GET_PRIVATE_INFORMATION = 0x00000004;
76+
private const uint POLICY_TRUST_ADMIN = 0x00000008;
77+
private const uint POLICY_CREATE_ACCOUNT = 0x00000010;
78+
private const uint POLICY_CREATE_SECRET = 0x00000014;
79+
private const uint POLICY_CREATE_PRIVILEGE = 0x00000040;
80+
private const uint POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080;
81+
private const uint POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100;
82+
private const uint POLICY_AUDIT_LOG_ADMIN = 0x00000200;
83+
private const uint POLICY_SERVER_ADMIN = 0x00000400;
84+
private const uint POLICY_LOOKUP_NAMES = 0x00000800;
85+
private const uint POLICY_NOTIFICATION = 0x00001000;
86+
// ReSharper restore UnusedMember.Local
87+
88+
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
89+
public static extern bool LookupPrivilegeValue(
90+
[MarshalAs(UnmanagedType.LPTStr)] string lpSystemName,
91+
[MarshalAs(UnmanagedType.LPTStr)] string lpName,
92+
out LUID lpLuid);
93+
94+
[DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
95+
private static extern uint LsaAddAccountRights(
96+
IntPtr PolicyHandle,
97+
IntPtr AccountSid,
98+
LSA_UNICODE_STRING[] UserRights,
99+
uint CountOfRights);
100+
101+
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
102+
private static extern uint LsaClose(IntPtr ObjectHandle);
103+
104+
[DllImport("advapi32.dll", SetLastError = true)]
105+
private static extern uint LsaEnumerateAccountRights(IntPtr PolicyHandle,
106+
IntPtr AccountSid,
107+
out IntPtr UserRights,
108+
out uint CountOfRights
109+
);
110+
111+
[DllImport("advapi32.dll", SetLastError = true)]
112+
private static extern uint LsaFreeMemory(IntPtr pBuffer);
113+
114+
[DllImport("advapi32.dll")]
115+
private static extern int LsaNtStatusToWinError(long status);
116+
117+
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
118+
private static extern uint LsaOpenPolicy(ref LSA_UNICODE_STRING SystemName, ref LSA_OBJECT_ATTRIBUTES ObjectAttributes, uint DesiredAccess, out IntPtr PolicyHandle );
119+
120+
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
121+
static extern uint LsaRemoveAccountRights(
122+
IntPtr PolicyHandle,
123+
IntPtr AccountSid,
124+
[MarshalAs(UnmanagedType.U1)]
125+
bool AllRights,
126+
LSA_UNICODE_STRING[] UserRights,
127+
uint CountOfRights);
128+
// ReSharper restore InconsistentNaming
129+
130+
private static IntPtr GetIdentitySid(string identity)
131+
{
132+
var sid =
133+
new NTAccount(identity).Translate(typeof (SecurityIdentifier)) as SecurityIdentifier;
134+
if (sid == null)
135+
{
136+
throw new ArgumentException(string.Format("Account {0} not found.", identity));
137+
}
138+
var sidBytes = new byte[sid.BinaryLength];
139+
sid.GetBinaryForm(sidBytes, 0);
140+
var sidPtr = Marshal.AllocHGlobal(sidBytes.Length);
141+
Marshal.Copy(sidBytes, 0, sidPtr, sidBytes.Length);
142+
return sidPtr;
143+
}
144+
145+
private static IntPtr GetLsaPolicyHandle()
146+
{
147+
var computerName = Environment.MachineName;
148+
IntPtr hPolicy;
149+
var objectAttributes = new LSA_OBJECT_ATTRIBUTES
150+
{
151+
Length = 0,
152+
RootDirectory = IntPtr.Zero,
153+
Attributes = 0,
154+
SecurityDescriptor = IntPtr.Zero,
155+
SecurityQualityOfService = IntPtr.Zero
156+
};
157+
158+
const uint ACCESS_MASK = POLICY_CREATE_SECRET | POLICY_LOOKUP_NAMES | POLICY_VIEW_LOCAL_INFORMATION;
159+
var machineNameLsa = new LSA_UNICODE_STRING(computerName);
160+
var result = LsaOpenPolicy(ref machineNameLsa, ref objectAttributes, ACCESS_MASK, out hPolicy);
161+
HandleLsaResult(result);
162+
return hPolicy;
163+
}
164+
165+
public static string[] GetPrivileges(string identity)
166+
{
167+
var sidPtr = GetIdentitySid(identity);
168+
var hPolicy = GetLsaPolicyHandle();
169+
var rightsPtr = IntPtr.Zero;
170+
171+
try
172+
{
173+
174+
var privileges = new List<string>();
175+
176+
uint rightsCount;
177+
var result = LsaEnumerateAccountRights(hPolicy, sidPtr, out rightsPtr, out rightsCount);
178+
var win32ErrorCode = LsaNtStatusToWinError(result);
179+
// the user has no privileges
180+
if( win32ErrorCode == STATUS_OBJECT_NAME_NOT_FOUND )
181+
{
182+
return new string[0];
183+
}
184+
HandleLsaResult(result);
185+
186+
var myLsaus = new LSA_UNICODE_STRING();
187+
for (ulong i = 0; i < rightsCount; i++)
188+
{
189+
var itemAddr = new IntPtr(rightsPtr.ToInt64() + (long) (i*(ulong) Marshal.SizeOf(myLsaus)));
190+
myLsaus = (LSA_UNICODE_STRING) Marshal.PtrToStructure(itemAddr, myLsaus.GetType());
191+
var cvt = new char[myLsaus.Length/UnicodeEncoding.CharSize];
192+
Marshal.Copy(myLsaus.Buffer, cvt, 0, myLsaus.Length/UnicodeEncoding.CharSize);
193+
var thisRight = new string(cvt);
194+
privileges.Add(thisRight);
195+
}
196+
return privileges.ToArray();
197+
}
198+
finally
199+
{
200+
Marshal.FreeHGlobal(sidPtr);
201+
var result = LsaClose(hPolicy);
202+
HandleLsaResult(result);
203+
result = LsaFreeMemory(rightsPtr);
204+
HandleLsaResult(result);
205+
}
206+
}
207+
208+
public static void GrantPrivileges(string identity, string[] privileges)
209+
{
210+
var sidPtr = GetIdentitySid(identity);
211+
var hPolicy = GetLsaPolicyHandle();
212+
213+
try
214+
{
215+
var lsaPrivileges = StringsToLsaStrings(privileges);
216+
var result = LsaAddAccountRights(hPolicy, sidPtr, lsaPrivileges, (uint)lsaPrivileges.Length);
217+
HandleLsaResult(result);
218+
}
219+
finally
220+
{
221+
Marshal.FreeHGlobal(sidPtr);
222+
var result = LsaClose(hPolicy);
223+
HandleLsaResult(result);
224+
}
225+
}
226+
227+
const int STATUS_SUCCESS = 0x0;
228+
const int STATUS_OBJECT_NAME_NOT_FOUND = 0x00000002;
229+
const int STATUS_ACCESS_DENIED = 0x00000005;
230+
const int STATUS_INVALID_HANDLE = 0x00000006;
231+
const int STATUS_UNSUCCESSFUL = 0x0000001F;
232+
const int STATUS_INVALID_PARAMETER = 0x00000057;
233+
const int STATUS_NO_SUCH_PRIVILEGE = 0x00000521;
234+
const int STATUS_INVALID_SERVER_STATE = 0x00000548;
235+
const int STATUS_INTERNAL_DB_ERROR = 0x00000567;
236+
const int STATUS_INSUFFICIENT_RESOURCES = 0x000005AA;
237+
238+
private static readonly Dictionary<int, string> ErrorMessages = new Dictionary<int, string>
239+
{
240+
{STATUS_OBJECT_NAME_NOT_FOUND, "Object name not found. An object in the LSA policy database was not found. The object may have been specified either by SID or by name, depending on its type."},
241+
{STATUS_ACCESS_DENIED, "Access denied. Caller does not have the appropriate access to complete the operation."},
242+
{STATUS_INVALID_HANDLE, "Invalid handle. Indicates an object or RPC handle is not valid in the context used."},
243+
{STATUS_UNSUCCESSFUL, "Unsuccessful. Generic failure, such as RPC connection failure."},
244+
{STATUS_INVALID_PARAMETER, "Invalid parameter. One of the parameters is not valid."},
245+
{STATUS_NO_SUCH_PRIVILEGE, "No such privilege. Indicates a specified privilege does not exist."},
246+
{STATUS_INVALID_SERVER_STATE, "Invalid server state. Indicates the LSA server is currently disabled."},
247+
{STATUS_INTERNAL_DB_ERROR, "Internal database error. The LSA database contains an internal inconsistency."},
248+
{STATUS_INSUFFICIENT_RESOURCES, "Insufficient resources. There are not enough system resources (such as memory to allocate buffers) to complete the call."}
249+
};
250+
251+
private static void HandleLsaResult(uint returnCode)
252+
{
253+
var win32ErrorCode = LsaNtStatusToWinError(returnCode);
254+
255+
if( win32ErrorCode == STATUS_SUCCESS)
256+
return;
257+
258+
if( ErrorMessages.ContainsKey(win32ErrorCode) )
259+
{
260+
throw new Win32Exception(win32ErrorCode, ErrorMessages[win32ErrorCode]);
261+
}
262+
263+
throw new Win32Exception(win32ErrorCode);
264+
}
265+
266+
public static void RevokePrivileges(string identity, string[] privileges)
267+
{
268+
var sidPtr = GetIdentitySid(identity);
269+
var hPolicy = GetLsaPolicyHandle();
270+
271+
try
272+
{
273+
var currentPrivileges = GetPrivileges(identity);
274+
if (currentPrivileges.Length == 0)
275+
{
276+
return;
277+
}
278+
var lsaPrivileges = StringsToLsaStrings(privileges);
279+
var result = LsaRemoveAccountRights(hPolicy, sidPtr, false, lsaPrivileges, (uint)lsaPrivileges.Length);
280+
HandleLsaResult(result);
281+
}
282+
finally
283+
{
284+
Marshal.FreeHGlobal(sidPtr);
285+
var result = LsaClose(hPolicy);
286+
HandleLsaResult(result);
287+
}
288+
289+
}
290+
291+
private static LSA_UNICODE_STRING[] StringsToLsaStrings(string[] privileges)
292+
{
293+
var lsaPrivileges = new LSA_UNICODE_STRING[privileges.Length];
294+
for (var idx = 0; idx < privileges.Length; ++idx)
295+
{
296+
lsaPrivileges[idx] = new LSA_UNICODE_STRING(privileges[idx]);
297+
}
298+
return lsaPrivileges;
299+
}
300+
}
301+
}
302+
"@
303+
304+
Add-Type -TypeDefinition $Source -Language CSharp
305+
306+
$ServiceChangeErrors = @{}
307+
$ServiceChangeErrors.Add(1, "Not Supported")
308+
$ServiceChangeErrors.Add(2, "Access Denied")
309+
$ServiceChangeErrors.Add(3, "Dependent Services Running")
310+
$ServiceChangeErrors.Add(4, "Invalid Service Control")
311+
$ServiceChangeErrors.Add(5, "Service Cannot Accept Control")
312+
$ServiceChangeErrors.Add(6, "Service Not Active")
313+
$ServiceChangeErrors.Add(7, "Service Request Timeout")
314+
$ServiceChangeErrors.Add(8, "Unknown Failure")
315+
$ServiceChangeErrors.Add(9, "Path Not Found")
316+
$ServiceChangeErrors.Add(10, "Service Already Running")
317+
$ServiceChangeErrors.Add(11, "Service Database Locked")
318+
$ServiceChangeErrors.Add(12, "Service Dependency Deleted")
319+
$ServiceChangeErrors.Add(13, "Service Dependency Failure")
320+
$ServiceChangeErrors.Add(14, "Service Disabled")
321+
$ServiceChangeErrors.Add(15, "Service Logon Failure")
322+
$ServiceChangeErrors.Add(16, "Service Marked For Deletion")
323+
$ServiceChangeErrors.Add(17, "Service No Thread")
324+
$ServiceChangeErrors.Add(18, "Status Circular Dependency")
325+
$ServiceChangeErrors.Add(19, "Status Duplicate Name")
326+
$ServiceChangeErrors.Add(20, "Status Invalid Name")
327+
$ServiceChangeErrors.Add(21, "Status Invalid Parameter")
328+
$ServiceChangeErrors.Add(22, "Status Invalid Service Account")
329+
$ServiceChangeErrors.Add(23, "Status Service Exists")
330+
$ServiceChangeErrors.Add(24, "Service Already Paused")
331+
332+
333+
function SetUserLogonAsServiceRights($UserName)
334+
{
335+
$privilege = "SeServiceLogonRight"
336+
if (![PSCarbon.Lsa]::GetPrivileges($UserName).Contains($privilege))
337+
{
338+
[PSCarbon.Lsa]::GrantPrivileges($UserName, $privilege)
339+
}
340+
}
341+
342+
function Set-ServiceLogonCredentials
343+
{
344+
[CmdletBinding()]
345+
param
346+
(
347+
[parameter(Mandatory=$true, ValueFromPipeline=$true)]
348+
[string]$ServiceName,
349+
350+
[parameter(Mandatory=$true)]
351+
[PSCredential]$Credentials
352+
)
353+
PROCESS
354+
{
355+
$service = gwmi -class "Win32_Service" -Filter "Name = '$ServiceName'"
356+
if(!$service)
357+
{
358+
throw "Service $ServiceName not found"
359+
}
360+
361+
try
362+
{
363+
SetUserLogonAsServiceRights $Credentials.UserName
364+
}
365+
catch
366+
{
367+
throw "Failed to set SeServiceLogonRight rights for user '$($Credentials.UserName)'. " +
368+
"Ensure that the user exists and that you have the proper access rights"
369+
}
370+
371+
$serviceUserName = $Credentials.UserName
372+
if (!$serviceUserName.Contains("\") -or $serviceUserName.Contains("@"))
373+
{
374+
$serviceUserName = ".\$serviceUserName"
375+
}
376+
377+
$mbo = $service.Change($null, $null, $null, $null, $null, $null,
378+
$serviceUserName,
379+
$Credentials.GetNetworkCredential().Password)
380+
if ($mbo.ReturnValue)
381+
{
382+
throw "Setting the service credentials failed with error: " +
383+
$ServiceChangeErrors[$mbo.ReturnValue]
384+
}
385+
}
386+
}
387+
388+

0 commit comments

Comments
 (0)