diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index c8986ab7089..61d4232705d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -1466,6 +1466,14 @@ public class SetServiceCommand : ServiceOperationBaseCommand } internal string displayName = null; + /// + /// Account under which the service should run + /// + /// + [Parameter] + [Credential()] + public PSCredential Credential { get; set; } + /// @@ -1589,6 +1597,7 @@ protected override void ProcessRecord() { ServiceController service = null; string ServiceComputerName = null; + IntPtr password = IntPtr.Zero; foreach (string computer in ComputerName) { bool objServiceShouldBeDisposed = false; @@ -1679,9 +1688,9 @@ protected override void ProcessRecord() continue; } - // modify startup type or display name + // Modify startup type or display name or credential if (!String.IsNullOrEmpty(DisplayName) - || (ServiceStartMode)(-1) != StartupType) + || (ServiceStartMode)(-1) != StartupType || null != Credential) { DWORD dwStartType = NativeMethods.SERVICE_NO_CHANGE; switch (StartupType) @@ -1701,6 +1710,13 @@ protected override void ProcessRecord() "bad StartupType"); break; } + + string username = null; + if (null != Credential) + { + username = Credential.UserName; + password = Marshal.SecureStringToCoTaskMemUnicode(Credential.Password); + } bool succeeded = NativeMethods.ChangeServiceConfigW( hService, NativeMethods.SERVICE_NO_CHANGE, @@ -1710,8 +1726,8 @@ protected override void ProcessRecord() null, IntPtr.Zero, null, - null, - IntPtr.Zero, + username, + password, DisplayName ); if (!succeeded) @@ -1847,6 +1863,10 @@ protected override void ProcessRecord() } //End try finally { + if (IntPtr.Zero != password) + { + Marshal.ZeroFreeCoTaskMemUnicode(password); + } if (objServiceShouldBeDisposed) { service.Dispose(); diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index 5a30db2fe0b..10ae344eb79 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -110,6 +110,39 @@ Describe "Set/New-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows" $newServiceCommand.$parameter | Should Be $value } + It "Set-Service can change credentials of a service" { + try { + $startUsername = "user1" + $endUsername = "user2" + $testPass = "Secret123!" + $servicename = "testsetcredential" + net user $startUsername $testPass /add > $null + net user $endUsername $testPass /add > $null + $password = ConvertTo-SecureString $testPass -AsPlainText -Force + $creds = [pscredential]::new(".\$startUsername", $password) + $parameters = @{ + Name = $servicename; + BinaryPathName = "$PSHOME\powershell.exe"; + StartupType = "Manual"; + Credential = $creds + } + $service = New-Service @parameters + $service | Should Not BeNullOrEmpty + $service = Get-CimInstance Win32_Service -Filter "name='$servicename'" + $service.StartName | Should BeExactly $creds.UserName + + $creds = [pscredential]::new(".\$endUsername", $password) + Set-Service -Name $servicename -Credential $creds + $service = Get-CimInstance Win32_Service -Filter "name='$servicename'" + $service.StartName | Should BeExactly $creds.UserName + } + finally { + Get-CimInstance Win32_Service -Filter "name='$servicename'" | Remove-CimInstance -ErrorAction SilentlyContinue + net user $startUsername /delete > $null + net user $endUsername /delete > $null + } + } + It "New-Service can create a new service called ''" -TestCases @( @{name = "testautomatic"; startupType = "Automatic"; description = "foo" ; displayname = "one"}, @{name = "testmanual" ; startupType = "Manual" ; description = "bar" ; displayname = "two"},