PowerShell 技能连载 - 怪异的文本格式化(以及解决方法)

试试以下的代码并且找到问题所在:

$desc = Get-Process -Id $pid | Select-Object -Property Description
"PowerShell process description: $desc"

这段代码的目的是获取 PowerShell 宿主进程并且读取进程的描述信息,然后输出到字符串。它的结果看起来是怪异的:

PowerShell process description: @{Description=Windows PowerShell}

这是因为代码中选择了整个 Description 属性,而且结果不仅是描述字符串,而且包括了整个属性:

PS> $desc

Description
-----------
Windows PowerShell ISE

当您只选择一个属性时,请确保使用 -ExpandProperty 而不是 -Property。前者避免产生一个属性列,并且字符串看起来正常了:

PS> $desc = Get-Process -Id $pid | Select-Object -ExpandProperty Description
PS> "PowerShell process description: $desc"
PowerShell process description: Windows PowerShell ISE

PowerShell 技能连载 - 在 PowerShell 中查找服务

Get-Service 可以列出计算机上的所有服务,但是返回的信息十分少。您无法很容易地看出一个服务做什么、它是一个 Microsoft 服务还是一个第三方服务,以及服务所对应的可执行程序。

通过合并一些信息,您可以获取许多更丰富的信息。以下是一个 Find-Service 函数,可以返回一系列丰富的信息:

function Find-Service
{
    param
    (
        $Name = '*',
        $DisplayName = '*',
        $Started
    )
    $pattern = '^.*\.exe\b'

    $Name = $Name.Replace('*','%')
    $DisplayName = $DisplayName.Replace('*','%')

    Get-WmiObject -Class Win32_Service -Filter "Name like '$Name' and DisplayName like '$DisplayName'"|
      ForEach-Object {

        if ($_.PathName -match $pattern)
        {
            $Path = $matches[0].Trim('"')
            $file = Get-Item -Path $Path
            $rv = $_ | Select-Object -Property Name, DisplayName, isMicrosoft, Started, StartMode, Description, CompanyName, ProductName, FileDescription, ServiceType, ExitCode, InstallDate, DesktopInteract, ErrorControl, ExecutablePath, PathName
            $rv.CompanyName = $file.VersionInfo.CompanyName
            $rv.ProductName = $file.VersionInfo.ProductName
            $rv.FileDescription = $file.VersionInfo.FileDescription
            $rv.ExecutablePath = $path
            $rv.isMicrosoft = $file.VersionInfo.CompanyName -like '*Microsoft*'
            $rv
        }
        else
        {
            Write-Warning ("Service {0} has no EXE attached. PathName='{1}'" -f $_.PathName)
        }
      }
}

Find-Service | Out-GridView

PowerShell 技能连载 - 获取 1000 个以上 Active Directory 结果

当您使用 ADSISearcher 时,默认情况下,Active Directory 只返回前 1000 个搜索结果。这是一个防止意外的 LDAP 查询导致域控制器负荷过重的安全保护机制。

如果您需要完整的搜索结果,并且明确地知道它将超过 1000 条记录,请设置 PageSize 为 1000。通过这种方式,ADSISearcher 每一批返回 1000 个搜索结果元素。

以下查询将会返回您域中的所有用户账户(在运行这个查询之前,您也许需要联系一下您的域管理员):

$searcher = [ADSISearcher]"sAMAccountType=$(0x30000000)"

# get all results, do not stop at 1000 results
$searcher.PageSize = 1000

$searcher.FindAll() |
  ForEach-Object { $_.GetDirectoryEntry() } |
  Select-Object -Property * |
  Out-GridView

PowerShell 技能连载 - 在智能感知中隐藏参数

从 PowerShell 4.0 开始,脚本作者可以决定隐藏某些参数,使之不在智能感知中出现。通过这种方式,可以在 ISE 的智能感知上下文菜单中隐藏不常用的参数。

function Test-Function
{
    param(
        $Name,
        [Parameter(DontShow)]
        [Switch]
        $IAmSecret
    )

    if ($IAmSecret)
    {
     "Doing secret things with $Name"
    }
    else
    {
      "Regular behavior with $Name"
    }
}

当您在 PowerShell 4.0 ISE 中运行这个函数时,只有 “Name” 参数会出现在智能感知上下文菜单中。然而,如果您事先知道这个隐藏参数,并且键入了第一个字母,然后按下 (TAB) 键,这个参数仍会显示:

在帮助窗口,隐藏参数总是可以显示,您可以通过类似这种方式打开:

PowerShell 技能连载 - 快速查找 Active Directory 用户账户

LDAP 查询条件越明确,查询速度就越快,占用的资源就越少,并且查询结果越清晰。

例如,许多人使用 objectClass 来限制查询结果为某个指定的对象类型。若只需要查询用户账户,他们常常使用 "objectClass=user" 的写法。许多人不知道计算机账户也共享这个对象类型。让我们来验证这一点:

这个例子将会查找所有 SamAccountName 以 “a” 开头,并且 objectClass=”user” 的账户。

# get all users with a SamAccountName that starts with "a"
$searcher = [ADSISearcher]"(&(objectClass=User)(sAMAccountName=a*))"

# see how long this takes
$result = Measure-Command {
  $all = $searcher.FindAll()
  $found = $all.Count
}

$seconds = $result.TotalSeconds

"The search returned $found objects and took $sec seconds."

然后使用这行来代替上面的代码:

$searcher = [ADSISearcher]"(&(sAMAccountType=$(0x30000000))(sAMAccountName=a*))"

当您换成这行代码以后,查询速度显著提升了。并且结果更清晰。这是因为普通用户账户和计算机账户的 SamAccountType 不同:

  • SAM_NORMAL_USER_ACCOUNT 0x30000000
  • SAM_MACHINE_ACCOUNT 0x30000001

两者的 objectClass 都属于 “User”。

PowerShell 技能连载 - 通过 SID 查找 Active Directory 账户

如果您已知账户的 SID 并且希望找到相应的 Active Directory 账户,那么 LDAP 查询并不适合这项工作。为了使它能工作,您需要将 SID 的格式改成符合 LDAP 规则的格式,这不是一个简单的过程。

以下是一个更简单的使用 LDAP 路径的办法。假设您使用 $SID 变量保存了一个 SID 字符串,并且您希望查找出和它关联的 Active Directory 账户。试试以下的代码:

$SID = '<enter SID here>'   # like S-1-5-21-1234567-...
$account = [ADSI]"LDAP://<SID=$SID>"
$account
$account.distinguishedName

PowerShell 技能连载 - 在不同的 Domain 中查找

当你那使用 ADSISearcher 类型加速器来查找 Active Directory 账户时,它缺省情况下在您当前登录的域中查找。如果您需要在一个不同的域中查找,请确保相应地定义了搜索的根路径。

This example will find all accounts with a SamAccountName that starts with “tobias”, and it searches the domain “powershell.local” (adjust to a real domain name, of course):
这个例子将查找所有 SamAccountName 以 “tobias” 开头的账户,并且它在 “powershell.local” 域中搜索(当然,请根据实际情况调整名字):

# get all users with a SamAccountName that starts with "tobias"
$searcher = [ADSISearcher]"(&(objectClass=User)(objectCategory=person)(sAMAccountName=tobias*))"

# use powershell.local for searching
$domain = New-Object System.DirectoryServices.DirectoryEntry('DC=powershell,DC=local')
$searcher.SearchRoot = $domain

# execute the query
$searcher.FindAll()

PowerShell 技能连载 - 从 DN 中获得 Domain

“DN” 指的是是 Active Directory 对象的路径,看起来大概如下:

'CN=Tobias,OU=Authors,DC=powershell,DC=local'

要获取 DN 中的域部分,请使用如下代码:

$DN = 'CN=Tobias,OU=Authors,DC=powershell,DC=local'
$pattern = '(?i)DC=\w{1,}?\b'

([RegEx]::Matches($DN, $pattern) | ForEach-Object { $_.Value }) -join ','

这段代码用一个正则表达式来查找 DN 的所有 DC= 部分然后将它们用逗号分隔符连接起来。

执行结果如下:

DC=powershell,DC=local

PowerShell 技能连载 - 将二进制 SID 转换为 SID 字符串

Active Directory 账户有一个二进制形式存储的 SID。要将字节数组转换为字符串的表达形式,可以用如下的 .NET 函数:

# get current user
$searcher = [ADSISearcher]"(&(objectClass=User)(objectCategory=person)(sAMAccountName=$env:username))"
$user = $searcher.FindOne().GetDirectoryEntry()

# get binary SID from AD account
$binarySID = $user.ObjectSid.Value

# convert to string SID
$stringSID = (New-Object System.Security.Principal.SecurityIdentifier($binarySID,0)).Value

$binarySID
$stringSID

在这个例子中,一个 ADSI 搜索器获取当前的用户账户(返回当前登录到一个域中的用户)。然后,将二进制的 SID 转换为 SID 字符串。

PowerShell 技能连载 - 查找当前的脚本文件夹

从 PowerShell 3.0 开始,有一个很简单的办法来确定一个脚本所在的文件夹:$PSScriptRoot。这个变量总是保存了指定脚本所存放的文件夹路径。

通过这种方法,可以很方便地加载额外的资源,比如说其它脚本。以下代码将读取位于同一个文件夹中,一个名为 myFunctions.ps1 的脚本文件:

"$PSScriptRoot\myFunctions.ps1"

别忘了用“dot-source”语法(在路径之前加点号)。否则只会输出该路径名(而不是执行该路径表示的脚本)。