PowerShell 技能连载 - 检测 64 位操作系统

适用于 Windows 7/Server 2008 R2

要检测一个脚本是运行在 32 位环境还是 64 位环境是十分简单的:只需要检查指针的大小,看是等于 4 字节还是 8 字节:

if ([IntPtr]::Size -eq 8)
{
    '64-bit'
}
else
{
    '32-bit'
}

不过这并不会告诉您操作系统的类型。这是由于 PowerShell 脚本可以在 64 位机器中运行在 32 位进程里。

要检测 OS 类型,请试试这段代码:

if ([Environment]::Is64BitOperatingSystem)
{
    '64-bit'
}
else
{
    '32-bit'
}

而且,Environment 类也可以检查您的进程类型:

if ([Environment]::Is64BitProcess)
{
    '64-bit'
}
else
{
    '32-bit'
}

PowerShell 技能连载 - 创建 NTFS 安全报告

适用于 PowerShell 所有版本

如果您希望审计您文件系统中的 NTFS 权限,以下是您起步的建议。

这个脚本递归扫描 Windows 目录和子目录。只要将 $Path 替换为其它路径就可以扫描您文件系统的其它路径。

$Path = 'C:\Windows'

Get-ChildItem -Path $Path -Recurse -Directory -ErrorAction SilentlyContinue |
  ForEach-Object {
    $result = $_ | Select-Object -Property FullName, ExplicitePermissions, Count, Preview
    $result.ExplicitePermissions = (Get-Acl -Path $_.FullName -ErrorAction SilentlyContinue).Access |
      Where-Object { $_.isInherited -eq $false }
    $result.Count = $result.ExplicitePermissions.Count
    $result.Preview = $result.ExplicitePermissions.IdentityReference -join ','
    if ($result.ExplicitePermissions.Count -gt 0)
    {
      $result
    }
  } | Out-GridView

该脚本读取每个子文件夹的安全描述符并查找非继承的安全控制项。如果找到了,那么就加这个信息加入文件夹对象。

结果将输出到一个网格视图窗口。如果您移除掉 Out-GridView,那么您会得到类似如下的信息:

PS> G:\

FullName                   ExplicitePermissions                          Count Preview
--------                   --------------------                          ----- -------
C:\windows\addins          {System.Security.Access...                        9 CREATOR OWNER,NT AUTHOR...
C:\windows\AppPatch        {System.Security.Access...                        9 CREATOR OWNER,NT AUTHOR...
C:\windows\Boot            {System.Security.Access...                        8 NT AUTHORITY\SYSTEM,NT ...
C:\windows\Branding        {System.Security.Access...                        9 CREATOR OWNER,NT AUTHOR...
C:\windows\Cursors         {System.Security.Access...                        9 CREATOR OWNER,NT AUTHOR...
C:\windows\de-DE           {System.Security.Access...                        9 CREATOR OWNER,NT AUTHOR...
C:\windows\diagnostics     {System.Security.Access...                        8 NT AUTHORITY\SYSTEM,NT ...
C:\windows\Downloaded P... {System.Security.Access...                       11 CREATOR OWNER,NT AUTHOR...

您可以将这个例子作为更深入的工具的基础。例如,您可以将缺省受信任者(例如“CREATOR”,或“SYSTEM”)加入一个列表,并从结果中排除这个列表。

PowerShell 技能连载 - 查找非继承的权限

适用于 PowerShell 所有版本

通常,文件系统的 NTFS 权限是继承的。然而,您可以显式地为文件和文件夹添加权限。

您可以使用这段示例代码查找何处禁用了继承以及何处添加了权限项目:

Get-ChildItem c:\Windows -Recurse -Directory -ErrorAction SilentlyContinue |
  Where-Object { (Get-Acl -Path $_.FullName -ErrorAction SilentlyContinue).Access |
  Where-Object { $_.isInherited -eq $false } }

在这个例子中,Get-ChildItem 在 Windows 文件夹中搜索所有子文件夹。您可以将“C:\Windows”改为您想测试的任意文件夹。

然后,该脚本读取每个文件夹的安全描述符并查看是否有 isInherited 属性被设为 $false 的存取控制记录。

如果结果为真,该文件夹会汇报给您。

PowerShell 技能连载 - 不通过 ProgID 操作 COM 对象

适用于 PowerShell 所有版本

通常在操作 COM 对象时,它们需要将自身在注册表中注册,并且 PowerShell 需要注册的 ProgID 字符串来加载对象。

以下是一个例子:

$object = New-Object -ComObject Scripting.FileSystemObject
$object.Drives

若不使用 New-Object 命令,您也可以用 .NET 方法来实现相同的目的:

$type = [Type]::GetTypeFromProgID('Scripting.FileSystemObject')
$object = [Activator]::CreateInstance($type)
$object.Drives

采用后一种方法,您甚至可以实例化一个未暴露 ProgID 的 COM 对象。您所需的只是 GUID:

$clsid = New-Object Guid '0D43FE01-F093-11CF-8940-00A0C9054228'
$type = [Type]::GetTypeFromCLSID($clsid)
$object = [Activator]::CreateInstance($type)
$object.Drives

PowerShell 技能连载 - 处理隐藏文件

适用于 PowerShell 3.0 及更高版本

当您使用 Get-ChildItem 来列出文件时,缺省情况下不包含隐藏文件。

要包含隐藏文件,请使用 -Force 参数:

PS> Get-ChildItem -Path $home -Force

要只列出隐藏文件,请使用 -Hidden 参数。这个参数是在 PowerShell 3.0 引入的:

PS> Get-ChildItem -Path $home -Hidden


    Directory: C:\Users\Tobias


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d--h-        08.01.2012     10:38            AppData
d--hs        08.01.2012     10:38            Application Data
d--hs        08.01.2012     10:38            Cookies
d--hs        08.01.2012     10:38            Local Settings
d--hs        08.01.2012     10:38            My Documents
d--hs        08.01.2012     10:38            NetHood
(...)

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。