通过本地程序调用 PowerShell 自定义脚本,我们可以有周期、计划性的执行一些扩展的操作,这在一定程度上提高了本地程序自身逻辑的扩展性。而在编写一段 PowerShell 脚本的过程中,也难免会遇到需要一段脚本在另一台计算机上远程执行的情形。例如,我们在执行一段 PowerShell 脚本时,需要向某个 AD Group 中添加一个 AD User,而这一操作需要在域控机器上才能执行,而此时,我们就会用到 PowerShell 远程调用执行代码。
在调用之前,我们需要保证 PowerShell 的远程调用设置是开启的。
1 启用远端计算机的 Windows Remote Management (WS-Management) service.
(1) 通过 services.msc 启动服务管理器
(2) 在其中找到 Windows Remote Management (WS-Management) 这一项
(3) 确定该项服务正处于运行状态,并且启动类型被设置为 Automatic
2 设定本地组策略.
(1) 通过 gpedit.msc 开启本地组策略编辑器
(2) 找到 Computer Configuration > Administrative Templates > Windows Components > Windows Remote Management (WinRM) > WinRM
Service
(3) 在右侧的项目中,双击 “Allow remote server management through WinRM”
(4) 在新弹出的窗口中,选择Enabled,从而启用策略,然后再Options的设置区域,位IPv4和IPv6 filter添加规则”*”,从而使所有IP都不受到过滤器的限制。当然,这里也可以根据具体的环境进行配置,相关配置可参考下图。
(6) 应用并保存配置后即可。
随后,我们就可以通过 PowerShell 的 Invoke-Command 命令使 PowerShell 脚本片段在远程机器上执行,我们同样也可以向远程脚本传递参数。
# 定义传递给远端服务器脚本的参数,这里我们定义了一个字典对象来统一存放这些参数。 $tempParams = @{} $tempParams.Add("adGroups", $adGroups) $tempParams.Add("targetUsers", $targetUsers) # 如果下边不希望指定计算机名,而是IP,则必须提供Credential参数 # 援引 MSDN 的描述: # To use an IP address in the value of the ComputerName parameter, # the command must include the Credential parameter. Also, # the computer must be configured for HTTPS transport or # the IP address of the remote computer must be included # in the WinRM TrustedHosts list on the local computer. $results = Invoke-Command -Credential $登录认证凭据对象 -ComputerName $远程执行的计算机名 –ArgumentList $tempParams -ScriptBlock { # 这里开始编写用于远程执行的 PowerShell 脚本 Param($tempParams) try { # 引用远端计算机上的某个Module,以完成后续的操作,这里假定我们的后续操作是修改AD Import-module ActiveDirectory # todo ... return "success" } catch { return $_ } }
关于 Invoke-Command 的详细参数介绍,可参考MSDN。
下面给出了一个操纵AD Group的示例,我将一些AD用户从指定的AD组中移除。
* 这里 $ADDomainAdminPasswordEncryptedText 保存的是实际密码的密文,这样可以防止用户密码被显式的保存在脚本中。关于这一机制的介绍,可以参考我的另一篇blog.
# # Configurations # $ADDomainControllerComputerName = "DCHostName" $ADDomainAdminUsername = "DOMAINDomainAdminUsername" $ADDomainAdminPasswordEncryptedText = "01000000d08c9ddf0115d1118c7a00c04fc297eb0100000053c3945ec9b08c4b8d63f85a8edcd1190000000002000000000003660000c00000001000000014a97ff6bf4780b14e38d3ef9ae549640000000004800000a0000000100000006f8b17e3fad7da6f892a895a683d8f75180000007fe0d199ace1ed4306af34cdbb9fdcc2745a5bc9bd3abda8140000001bc56921787de674117e8c49f368745ac62400aa" # $logger = (Split-Path -Parent $MyInvocation.MyCommand.Definition) + "debug.log" # Main try { # ... ... # Add User to AD Group $targetUsers = $selectedUser.Split(";") $adGroups = $selectedADGroup.Split(";") $domainAdminPassword = $ADDomainAdminPasswordEncryptedText | ConvertTo-SecureString $dcCred = New-Object -TypeName System.Management.Automation.PSCredential($ADDomainAdminUsername, $domainAdminPassword) $tempParams = @{} $tempParams.Add("adGroups", $adGroups) $tempParams.Add("targetUsers", $targetUsers) $results = Invoke-Command -Credential $dcCred -ComputerName $ADDomainControllerComputerName -ScriptBlock { Param($tempParams) try { Import-module ActiveDirectory $adGroups = $tempParams["adGroups"] $targetUsers = $tempParams["targetUsers"] foreach($adGroup in $adGroups) { if(!$adGroup.Equals("")) { if($adGroup.Contains("")) { $adGroup = $adGroup.Split("")[1]; } foreach($targetUser in $targetUsers) { try { if($targetUser.Contains("")) { $targetUser = $targetUser.Split("")[1] } $checkIsADGroup = $null try { $checkIsADGroup = Get-ADGroup '$adGroup' -ErrorAction Continue } catch {} # if the user is a AD Group and it has the different name with target AD group if($checkIsADGroup -eq $null -or $checkIsADGroup -ne $null -and $adGroup -ne $targetUser) { Add-ADGroupMember -Identity $adGroup -Member $targetUser } } catch { if(-not $_.Exception.Message.ToLower().Contains("The specified account name is already a member of the group".ToLower())) { throw $_ } } } } } return "success" } catch { return $_ } } –ArgumentList $tempParams if($results -eq "success") { ac $logger "Add user to AD group successful." } else { ac $logger "Add user to AD group failed." ac $logger $results } } catch { ac $logger "Main Error: $_" }