PowerShell 技能连载 - 使用加密文件系统(EFS)来保护密码

如果您必须在脚本中以硬编码的方式包含密码和其它隐私信息(正常情况下应避免使用),那么您还可以通过 EFS(加密文件系统)的方式来保障安全性。加密的脚本只能被加密者读取(和执行),所以只有您在自己的机器上能运行该脚本。

一下是加密一个 PowerShell 脚本的简单方法:

# create some sample script
# replace path with some real-world existing script if you want
# and remove the line that creates the script
$path = "$env:temp\test.ps1"
"Write-Host 'I run only for my master.'" > $path

$file = Get-Item -Path $path
$file.Encrypt()

当您运行这段脚本时,它将在您的临时文件夹中创建一个用 EFS 加密的新的 PowerShell 脚本(如果您见到一条错误提示信息,那么很有可能您机器上的 EFS 不可用或者被禁用了)。

加密之后,该文件在 Windows 资源管理器中呈现绿色,并且只有您能够运行它。别人无法看见源代码。

请注意在许多企业环境中,EFS 系统是通过恢复密钥部署的。指定的维护人员可以通过主密钥解密文件。如果没有主密钥,一旦您丢失了您的 EFS 证书,就连您也无法查看或运行加密的脚本。

PowerShell 技能连载 - 验证 UNC 路径

Test-Path 命令可以检测指定的文件或文件夹是否存在。它对于使用盘符的路径工作正常,但是对于纯 UNC 路径则不可用。

最简单的情况下,这应该返回 $true,并且它的确返回了 $true(假设您没有禁用管理员共享):

$path = '\\127.0.0.1\c$'

Test-Path -Path $path

现在,同样的代码却返回 $false:

Set-Location -Path HKCU:\
$path = '\\127.0.0.1\c$'

Test-Path -Path $path

如果路径不是使用一个盘符,PowerShell 将使用当前路径,如果该路径指向一个非文件系统位置,Test-Path 将在该 provider 的上下文中解析 UNC 路径。由于注册表中没有这个路径,Test-Path 返回 $false。

要让 Test-Path 在 UNC 路径下可靠地工作,请确保您在 UNC 路径之前添加了 FileSystem provider。现在,无论当前位于哪个驱动器路径,结果都是正确的:

Set-Location -Path HKCU:\
$path = 'filesystem::\\127.0.0.1\c$'

Test-Path -Path $path

PowerShell 技能连载 - 启用 PowerShell 远程管理

如果您希望用 PowerShell 远程管理来执行另一台机器上的命令或脚本,那么您需要以完整管理员权限启用目标机器上的远程管理功能:

在客户端,当您在同一个域中并且使用同一个域用户登录时,您不需要做任何额外的事情。

如果您希望通过非 Kerberos 验证方式连接目标计算机时(目标计算机在另一个域中,或您希望使用 IP 地址或非完整限定 DNS 名来连接),那么您需要以管理员权限运行一次以下代码:

将信任的主机设置为“*”之后,PowerShell 将允许您连接任何 IP 或机器名,如果无法用 Kerberos 验证身份,将使用 NTLM 验证。所以该设置不影响哪些人可以和该主机通信(通过防火墙规则设置)。它只是告诉 PowerShell 您将在 Kerberos 不可用的时候使用(更不安全一些的)NTLM 验证方式。NTLM 更不安全一些,因为它无法知道目标计算机是否真的是您想要访问的计算机。Kerberos 认证有相互认证过程,而 NTLM 没有。您的凭据直接被发送到指定的计算机中。假如当一个攻击者有机会用他的机器替换掉目标机器,并且占据了它的 IP 地址,而您使用 NTLM 的话,不会得到任何通知。

注意:如果你打开了远程并设置了信任列表后想关闭远程请运行 Disable-PSRemoting,不禁用远程将可能被人利用。

当远程管理打开以后,您可以通过 Enter-PSSession 访问远程系统,并且您可以用 Invoke-Command 在这些机器上运行命令或脚本。

PowerShell 技能连载 - 启用传统远程控制

许多 cmdlet 有内置的远程功能,例如 Get-ServiceGet-Process 都具有 -ComputerName 参数,同样的还有 Get-WmiObject

然而,要真正地远程使用这些 cmdlet,还需要一些先决条件。多数使用传统远程技术的 cmdlet 需要在目标机器上启用“远程管理”防火墙规则。它允许 DCOM 通信。还有一些需要目标计算机运行远程注册表服务。

所以在多数场景中,当您拥有目标机器的管理员,并且运行以下命令,则管理员可以通过传统远程 cmdlet 访问目标机器:

注意新版的 Windows 中 netsh firewall 命令可能会被废弃,不过目前仍然可以用。该命令比新版的 netsh advfirewall 命令用起来更简单。

PowerShell 技能连载 - 用 PowerShell 导入导出凭据

凭据对象包含了用户名和密码。您可以用 Get-Credential 来创建它们,然后将该对象传递给任何包含 -Credential 参数的 cmdlet。

然而,您要怎么做才能不需要用户干预,并且确保安全呢?您不希望弹出一个凭据对话框,并且您不希望在脚本中保存密码信息的话。

以下是一个解决方案:使用 Export-Credential 函数将凭据保存到一个文件中:

function Export-Credential
{
   param
   (
     [Parameter(Mandatory=$true)]
     $Path,

     [System.Management.Automation.Credential()]
     [Parameter(Mandatory=$true)]
     $Credential
   )

  $CredentialCopy = $Credential | Select-Object *
  $CredentialCopy.Password = $CredentialCopy.Password | ConvertFrom-SecureString
  $CredentialCopy | Export-Clixml $Path
}

这段代码将 tobias 用户的凭据保存到一个文件中:

请注意当您进行这步操作时,将弹出凭据对话框并以安全的方式询问您的密码。该输出的文件包含 XML,并且密码是加密的。

现在,当您需要凭据时,使用 Import-Credential 来从文件中取回它:

function Import-Credential
{
   param
   (
     [Parameter(Mandatory=$true)]
     $Path
   )

  $CredentialCopy = Import-Clixml $path
  $CredentialCopy.password = $CredentialCopy.Password | ConvertTo-SecureString
  New-Object system.Management.Automation.PSCredential($CredentialCopy.username, $CredentialCopy.password)
}

使用方法如下:

加密解密的“奥秘”在于您的身份,所以只有您(导出凭据的用户)可以将它再次导入。无需在您的脚本中硬编码隐私信息。

PowerShell 技能连载 - 查找 U 盘信息

您知道吗,Windows 记录了您使用过的所有 U 盘信息。要从注册表中读取上述信息,只需要使用这个函数:

function Get-USBInfo
{
  param
  (
    $FriendlyName = '*'
  )

  Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBSTOR\*\*\' |
  Where-Object { $_.FriendlyName } |
  Where-Object { $_.FriendlyName -like $FriendlyName } |
  Select-Object -Property FriendlyName, Mfg |
  Sort-Object -Property FriendlyName
}

以下是输出的例子:

您还可以按厂商来查询:

PowerShell 技能连载 - 获取时间服务器(以及读取所有注册表键值)

也许您希望从注册表数据库中获取已登记的时间服务器列表。他们您可能需要运行类似这样的代码:

Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\DateTime\Servers'

$path = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\DateTime\Servers'

$key = Get-Item -Path $path
Foreach ($valuename in $key.GetValueNames())
{
  if ($valuename -ne '')
  {
    $key.GetValue($valuename)
  }
}

这段代码存取注册表键,然后使用它的方法来获取值的名称,然后取出值:

PowerShell 技能连载 - 查找过期的证书

PowerShell 通过 cert: 驱动器来存取您的证书存储。

您可以根据指定的规则用这个驱动器来查找证书。以下代码将列出所有 NotAfter 字段中有值并在今日之前(意味着证书已过期)的证书:

$today = Get-Date

Get-ChildItem -Path cert:\ -Recurse |
  Where-Object { $_.NotAfter -ne $null  } |
  Where-Object { $_.NotAfter -lt $today } |
  Select-Object -Property FriendlyName, NotAfter, PSParentPath, Thumbprint |
  Out-GridView

PowerShell 技能连载 - 传递参数给 EXE 文件

从 PowerShell 运行某些应用程序,例如 robocopy.exe 不是很方便。如何向 EXE 传递参数,并且确保通过 PowerShell 不会传错值呢?

方法很简单:确保所有的参数是字符串(所以如果参数不是字符串或包含其它特殊字符,那么用双引号把它们包起来)。并且,确保针对每个参数提交一个字符串,而不是单个大字符串。

以下代码将从 PowerShell 执行 robocopy.exe 并且递归地从 Windows 文件夹中拷贝所有的 JPG 图片到另一个 c:\jpegs 文件夹中,遇到错误不重试,并跳过 winsxs 文件夹。

$arguments = "$env:windir\", 'c:\jpegs\','*.jpg', '/R:0', '/S', '/XD', '*winsxs*'

Robocopy.exe $arguments

如您所见,所有的参数都是字符串,并且它们都以一个字符串数组的形式传递。

这种方法完美地运行于所有您希望通过 PowerShell 调用的 exe 程序。

PowerShell 技能连载 - 应用 NTFS 存取权限

有很多方法可以增加或修改 NTFS 权限。其中一个方法是复用现成的工具,例如 icacls.exe

这个函数将以缺省权限创建新的文件夹。该脚本使用 icacls.exe 来显式地为当前用户添加完全权限以及为本地管理员添加读取权限:

function New-Folder
{
  param
  (
    [String]
    $path,

    [String]
    $username = "$env:userdomain\$env:username"
  )

  If ( (Test-Path -Path $path) -eq $false )
  {
    New-Item $path -Type Directory | Out-Null
  }

  icacls $path /inheritance:r /grant '*S-1-5-32-544:(OI)(CI)R' ('{0}:(OI)(CI)F' -f $username)
}