PowerShell 技能连载 - 改变 GPO 描述/备注

需要 GroupPolicy 模块

当您创建了一个新的组策略,您可以设置一个备注(或描述)。然而,没有一种明显的办法来修改这个备注值。

以下这段代码用于获取一个组策略,然后读取并/或修改它的描述。请确保您将“PolicyName”的名称修改为在您环境中实际存在的一个组策略名:

Import-Module -Name GroupPolicy
$policy = Get-Gpo -Name 'PolicyName'
$policy.Description
$policy.Description = 'New Description'

请注意只有当您重新打开了组策略客户端工具时,修改的内容才会反映到 UI 中。还请注意您需要 GroupPolicy PowerShell 模块。它随 Microsoft 的 RSAT 工具免费发布。在客户端,GroupPolicy 模块需要在控制面板/程序中启用才可以使用。

PowerShell 技能连载 - 检查 PowerShell 安全性

适用于 PowerShell 2.0 及以上版本

这段代码在指定的驱动器里查找所有 PowerShell 脚本,然后检查这些脚本是否有合法的数字签名,并汇报哪些脚本没有签名,或签名非法:

Get-ChildItem C:\ -Filter *.ps1 -Recurse |
  Where-Object { $_.Extension -eq '.ps1' } |
  Get-AuthenticodeSignature |
  Where-Object { $_.Status -ne 'Valid' }

“好吧”,也许您会辩论,“可是我们没有签名证书或 PKI”。这不是问题。数字签名只和信任有关。所以即便是用免费的自签名证书也可以信任。您只需要声明信任谁即可。

相对于依赖需要昂贵的官方代码签名的 Windows “根证书认证”,在您的内部安全审计中,您可以使用类似这样的自制方案:

$whitelist = @('D3037720F7E5CF2A9DBA855B65D98C2FE1387AD9',
               '6262A18EC19996DD521F7BDEAA0E079544B84241')

Get-ChildItem y:\Advanced -Filter *.ps1 -Recurse |
  Where-Object { $_.Extension -eq '.ps1' } |
  Get-AuthenticodeSignature |
  Select-Object -ExpandProperty SignerCertificate |
  Where-Object { $whitelist -notcontains $_.Thumbprint -or $_.Status -eq 'HashMismatch'  }

只需要将任何您信任的证书的唯一的证书指纹添加到白名单中。该证书是否是自签名的并不重要。白名单是最重要的,并且它是您私人的“吊销列表”:如果您不再信任某个证书,或某个证书丢失了,只需要将它的指纹从您的白名单中移除即可。

生成的报告包括所有未使用您的白名单中的证书合法地签名的脚本。如果某个脚本使用白名单中的一个证书签过名,但是后来改变过,它也会出现在报告中。

PowerShell 技能连载 - 处理 %ERRORLEVEL%

适用于 PowerShell 所有版本

当您在脚本里运行原生的 EXE 控制台命令,这些命令通常返回一个数字型的返回值。这个值被称为“ErrorLevel”。在批处理文件里,您可以通过 %ERRORLEVEL% 访问这个返回值。

让我们看看 PowerShell 如何获取和存放这个数字型返回值,以及一个 PowerShell 脚本如何返回自己的“ErrorLevel”——它将被 PowerShell 脚本的调用者接收到:

ping 1.2.3.4 -n 1 -w 500
$result1 = $LASTEXITCODE

ping 127.0.0.1 -n 1 -w 500
$result2 = $LASTEXITCODE

$result1
$result2

if ($result1 -eq 0 -and $result2 -eq 0)
{
  exit 0
}
else
{
  exit 1
}

在这个例子里,代码中 ping 了两个 IP 地址。第一个调用失败了,第二个调用成功了。该脚本将 $LASTEXITCODE 的返回值保存在两个变量里。

然后它计算出这些返回值的影响结果。在这个例子中,如果所有调用返回 0,PowerShell 脚本将 ErrorLevel 代码设置为 0,否则为 1。

当然,这只是一个简单的例子。您可以配合您自己的原生命令使用。只需要确保调用原生应用程序之后立刻保存 $LASTEXITCODE 值,因为它会被后续的调用覆盖。

PowerShell 技能连载 - 压缩路径

适用于 PowerShell 2.0 及以上版本

有些时候报表中的路径名太长了。要缩短一个路径,您当然可以将字符串截断成指定的长度,但是这将导致路径丧失可读性。一个更好的办法是使用内置的 Windows API 函数来更智能地缩短路径。

以下例子也演示了如何在 PowerShell 脚本中使用 C# 代码:

$newType = @'
using System;
using System.Text;
using System.Runtime.InteropServices;

namespace WindowsAPILib
{
    public class Helper
    {
        [DllImport("shlwapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
        internal static extern bool PathCompactPathEx(System.Text.StringBuilder pszOut, string pszSrc, Int32 cchMax, Int32 dwFlags);

        public static string CompactPath(string Path, int DesiredLength)
        {
            StringBuilder sb = new StringBuilder(260);
            if (PathCompactPathEx(sb, Path, DesiredLength + 1, 0))
            { return sb.ToString(); }
            else
            { return Path; }
        }
    }
}
'@

Add-Type -TypeDefinition $newType

当您执行了该代码,就创建了一个名为 WindowsAPILib 的新的 .NET 类型,从而得到一个新的名为 CompactPath 的新的静态方法。您现在可以这样的使用:

PS> $pshome
C:\Windows\System32\WindowsPowerShell\v1.0

PS> [WindowsAPILib.Helper]::CompactPath($pshome, 12)
C:\W...\v1.0

PS> [WindowsAPILib.Helper]::CompactPath($pshome, 18)
C:\Windows...\v1.0

PS> [WindowsAPILib.Helper]::CompactPath($pshome, 22)
C:\Windows\Sys...\v1.0

PowerShell 技能连载 - 从 LDAP 路径获取 OU

适用于 PowerShell 所有版本

要从原始字符串从截取特定的部分,您常常需要使用一系列文本分割和取子串的命令。

例如,要从一个 LDAP 陆军中截取最后一个 OU 的名字,一下是一种办法:

$dn = 'OU=Test,OU=People,CN=Testing,OU=Everyone,DC=Company,DC=com'

($dn.Split(',') -like 'OU=*' ).Substring(3)[0]

这段代码将返回该 LDAP 路径(LDAP 路径是从右往左读的,所以最后一个 OU 是字符串中的第一个 OU),而且稍作修改就可以读取其它部分。例如,将下标从 0 改为 -1 就可以读取路径中的第一个 OU。

PowerShell 技能连载 - 创建一大堆测试文件

适用于 PowerShell 所有版本

如果您需要对系统进行压力测试,或因为别的原因需要大量测试文件,以下是在瞬间创建大量文件(可以是大文件)的代码:

$Path = "$env:temp\hugefile.txt"
$Size = 200MB

$stream = New-Object System.IO.FileStream($Path, [System.IO.FileMode]::CreateNew)
$stream.Seek($Size, [System.IO.SeekOrigin]::Begin)
$stream.WriteByte(0)
$Stream.Close()

explorer.exe "/select,$Path"

PowerShell 技能连载 - 限制 String 的最大长度

适用于 PowerShell 所有版本

要限制输出的文本不会过长,您可以使用类似这样的的逻辑来缩短超过指定长度的文本:

if ($text.Length -gt $MaxLength)
{
  $text.Substring(0,$MaxLength) + '...'
}
else
{
  $text
}

PowerShell 技能连载 - 设置 AD 或 Windows 的权限

需要 ActiveDirectory 模块

我们之前已经演示了如何用 Get/Set-Acl 来读写文件和文件夹的权限。

实际上这两个 cmdlet 可以处理所有合法的 PowerShell 路径。说以您可以同样地在 Windows 注册表中使用相同的方法来读取、克隆和写入属性。

这个例子从一个注册表键中读取已有的安全信息,并应用到另一个键上:

# both Registry keys must exist
$KeyToCopySecurityFrom = 'HKLM:\Software\Key1'
$KeyToCopySecurityTo = 'HKLM:\Software\Key1'

$securityDescriptor = Get-Acl -Path $KeyToCopySecurityFrom
Set-Acl -Path $KeyToCopySecurityTo -AclObject $securityDescriptor

类似地,如果您从微软安装了 RSAT 工具并启用了 ActiveDirectory PowerShell 模块,您就可以使用它的 PowerShell 驱动器 AD: 来对 AD 对象做类似的操作,例如,从一个 OU 克隆委派权限到另一个 OU 上。

您现在可以根据需要读取、修改或重新应用如委派控制、防止意外删除等 Active Directory 特性。

Import-Module ActiveDirectory

# both OUs must exist
$OUtoCopyFrom = 'AD:\OU=Employees,DC=TRAINING,DC=POWERSHELL'
$OUtoCopyTo = 'AD:\OU=TestEmployees,DC=TRAINING,DC=POWERSHELL'

$securityDescriptor = Get-Acl -Path $OUtoCopyFrom
Set-Acl -Path $OUtoCopyTo -AclObject $securityDescriptor

您现在可以对任何 AD 对象通过这种方式读取和写入安全信息,包括 DNS 信息。您所需要的只是知道您想读写的对象的 LDAP 路径。

PowerShell 技能连载 - 捕获本地 EXE 的错误(第 2 部分)

适用于 PowerShell 所有版本

以下是检测控制台程序发出的错误的另一种方法:

$ErrorActionPreference = 'Continue'
$result = net.exe user UserDoesNotExist 2>&1

# $? is $false when something went wrong
if ($? -eq $false) {
    # read last error:
    $errMsg = $result.Exception.Message -join ','
    Write-Host "Something went wrong: $errMsg"
} else {
    Write-Host 'All is fine.'
}

请注意 $ErrorActionPreference 的用法:当它设置为‘Stop’时,错误将被转换为一个 .NET 异常。$ErrorActionPreference 的缺省设置是‘Continue’。通过这个设置,脚本可以通过 $err 获得错误信息。

如果最后一次调用失败了,内置的 $? 变量将会返回 $false。在这种情况下,代码将会返回一条错误信息(或者做其它事情,例如写日志文件)。

PowerShell 技能连载 - 用 SDDL 替换 NTFS 权限

适用于 PowerShell 所有版本

您可以通过 Get-Acl 命令将文件和文件夹的安全信息导出成 SDDL 格式(安全描述定义语言)的纯文本文件:

$FolderToRead = 'C:\folder1'

$securityDescriptor = Get-Acl -Path $FolderToRead
$securityDescriptor.GetSecurityDescriptorSddlForm('All')

您可以将 SDDL 通过管道输出到剪贴板,然后将它粘贴到另一个脚本中:

$FolderToRead = 'C:\folder1'

$securityDescriptor = Get-Acl -Path $FolderToRead
$securityDescriptor.GetSecurityDescriptorSddlForm('All') | clip.exe

类似这样将 SDDL 加入脚本中,例如(请注意 SDDL 总是只有一行,所以请不要添加换行符):

$sddl = 'O:S-1-5-21-2649034417-1209187175-3910605729-1000G:S-1-5-21-2649034417-1209187175-3910605729-513D:(A;ID;FA;;;BA)(A;OICIIOID;GA;;;BA)(A;ID;FA;;;SY)(A;OICIIOID;GA;;;SY)(A;OICIID;0x1200a9;;;BU)(A;ID;0x1301bf;;;AU)(A;OICIIOID;SDGXGWGR;;;AU)'


$FolderToConfigure = 'C:\folder2'

$securityDescriptor = Get-Acl -Path $FolderToConfigure
$securityDescriptor.SetSecurityDescriptorSddlForm($sddl)
Set-Acl -Path $FolderToConfigure -AclObject $securityDescriptor

将 SDDL 插入脚本之后,您就不再需要生成 SDDL 用的模板文件夹了。您可以将安全信息应用到其它文件系统对象中,例如设置基本 NTFS 权限,或先编辑 SDDL 再应用它。

为您提供一些启示,在域迁移的场景中,您可以比如创建一个转换表,用于将旧的 SID 转换为新的 SID。然后,将旧的 SID 替换成新的 SID,然后将记录下的安全信息克隆到一个新的(或测试的)域中。