PowerShell 技能连载 - 查找缺省的 Outlook 配置文件
PowerShell 可以操作 COM 对象,例如 Outlook 应用程序。以下简单的两行代码能返回当前的 Outlook 配置文件名:
$outlookApplication = New-Object -ComObject Outlook.Application
$outlookApplication.Application.DefaultProfileName
PowerShell 可以操作 COM 对象,例如 Outlook 应用程序。以下简单的两行代码能返回当前的 Outlook 配置文件名:
$outlookApplication = New-Object -ComObject Outlook.Application
$outlookApplication.Application.DefaultProfileName
从 PowerShell 4.0 开始,方法名可以是一个变量。以下是一个简单的例子:
$method = 'ToUpper'
'Hello'.$method()
当您需要调用的方法须通过一段脚本计算得到的时候,这个特性十分有用。
function Convert-Text
{
param
(
[Parameter(Mandatory)]
$Text,
[Switch]$ToUpper
)
if ($ToUpper)
{
$method = 'ToUpper'
}
else
{
$method = 'ToLower'
}
$text.$method()
}
以下是用户调用该函数的方法:
PS> Convert-Text 'Hello'
hello
PS> Convert-Text 'Hello' -ToUpper
HELLO
缺省情况下,该函数将文本转换为小写。当指定了开关参数 -ToUpper
时,函数将文本转换为大写。由于动态方法特性的支持,该函数不需要为此写两遍代码。
译者注:在旧版本的 PowerShell 中,您可以通过 .NET 方法(而不是脚本方法)中的反射来实现相同的目的。虽然它不那么整洁,但它能运行在 PowerShell 4.0 以下的环境:
function Convert-Text
{
param
(
[Parameter(Mandatory)]
$Text,
[Switch]$ToUpper
)
if ($ToUpper)
{
$method = 'ToUpper'
}
else
{
$method = 'ToLower'
}
$methodInfo = $Text.GetType().GetMethod($method, [type[]]@())
$methodInfo.Invoke($Text, $null)
}
在 PowerShell 中,您可以使用变量来指代属性名。这段示例脚本定义了四个 profile 的属性名,然后在一个循环中分别查询这些属性值:
$list = 'AllUsersAllHosts','AllUsersCurrentHost','CurrentUserAllHosts','CurrentUserCurrentHost'
foreach ($property in $list)
{
$profile.$property
}
您也可以在一个管道中使用它:
'AllUsersAllHosts','AllUsersCurrentHost','CurrentUserAllHosts','CurrentUserCurrentHost' |
ForEach-Object { $profile.$_ }
通过这种方式,您可以检查和返回 PowerShell 当前使用的所有 profile:
'AllUsersAllHosts','AllUsersCurrentHost','CurrentUserAllHosts','CurrentUserCurrentHost' |
ForEach-Object { $profile.$_ } |
Where-Object { Test-Path $_ }
类似地,您可以首先使用 Get-Member
来获取一个指定对象包含的所有属性。以下代码可以返回 PowerShell 的“PrivateData”对象中所有名字包含“color”的属性:
$host.PrivateData | Get-Member -Name *color* | Select-Object -ExpandProperty Name
接下来,您可以用一行代码获取所有的颜色设置:
$object = $host.PrivateData
$object | Get-Member -Name *color* -MemberType *property | ForEach-Object {
$PropertyName = $_.Name
$PropertyValue = $object.$PropertyName
"$PropertyName = $PropertyValue"
} |
Out-GridView
如果您只是需要替换文本中字符出现的所有位置,这是很简单的。以下可以将文本中所有的“l”变为大写:
"Hello World".Replace('l', 'L')
然而有些时候,您需要替换特定位置的某几个字符。我们假设您的文本是一个比特掩码,并且您需要对某些比特置位或清除。以上代码是不能用的,因为它一口气改变了所有的位:
PS> "110100011110110".Replace('1', '0')
000000000000000
而且您也不能通过索引来改变字符。您可以读取一个字符(例如检查某一个位是否为“1”),但您无法改变它的值:
PS> "110100011110110"[-1] -eq '1'
False
PS> "110100011110110"[-2] -eq '1'
True
PS> "110100011110110"[-2] = '0'
无法对 System.String 类型的对象进行索引。
要改变一个字符串中的某些字符,请将它转换为一个 StringBuilder
:
PS> $sb = New-Object System.Text.StringBuilder("1101100011110110")
PS> $sb[-1]
0
PS> $sb[-1] -eq '1'
False
PS> $sb[-2] -eq '1'
True
PS> $sb[-2] = '0'
PS> $sb[-2] -eq '1'
False
PS> $sb.ToString()
110100011110100
以下是将二进制转换为十进制格式的方法:
PS> $sb.ToString()
110100011110100
PS> [System.Convert]::ToInt64($sb.ToString(), 2)
26868
试试以下的代码并且找到问题所在:
$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
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
当您使用 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 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) 键,这个参数仍会显示:
在帮助窗口,隐藏参数总是可以显示,您可以通过类似这种方式打开:
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 不同:
两者的 objectClass 都属于 “User”。
如果您已知账户的 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