PowerShell 技能连载 - 隐藏参数

In the previous tip we explained how you can dump all the legal values for a PowerShell attribute. Today we’ll take a look at the [Parameter()] attribute and its value DontShow. Take a look at this function:
在前一个技能中我们介绍了如何导出一个 PowerShell 属性的所有合法值。今天我们将关注 [Parameter()] 属性和它的值 DontShow。我们来看看这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Test-Something
{
param
(
[string]
[Parameter(Mandatory)]
$Name,

[Parameter(DontShow)]
[Switch]
$Internal
)

"You entered: $name"
if ($Internal)
{
Write-Warning "We are in secret test mode!"
}
}

当您运行这个函数时,IntelliSense 只暴露 -Name 参数。-Internal switch 参数并没有显示,然而您仍然可以使用它。它只是一个隐藏的参数:

1
2
3
4
5
6
PS> Test-Something -Name tobias
You entered: tobias

PS> Test-Something -Name tobias -Internal
You entered: tobias
WARNING: We are in secret test mode!

PowerShell 技能连载 - 探索 PowerShell 属性值

您也许知道,您可以对变量和参数添加属性来更有针对性地定义它们。例如,以下代码定义了一个包含只允许三个字符串之一的必选参数的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Test-Attribute
{
[CmdletBinding()]
param
(
[string]
[Parameter(Mandatory)]
[ValidateSet("A","B","C")]
$Choice
)

"Choice: $Choice"
}

如果您想知道这些属性有哪些选择,以下是实现方法。您所需要了解的是代表属性的真实名称。PowerShell 自身的属性都位于 System.Management.Automation 命名空间。以下是两个最常用的:

1
2
[Parameter()] = [System.Management.Automation.ParameterAttribute]
[CmdletBinding()] = [System.Management.Automation.CmdletBindingAttribute]

要查看某个指定属性的合法数值,只需要实例化一个制定类型的对象,并查看它的属性:

1
2
3
[System.Management.Automation.ParameterAttribute]::new() |
Get-Member -MemberType *Property |
Select-Object -ExpandProperty Name

这将返回一个包含所有 [Parameter()] 属性所有合法属性值的列表:

DontShow
HelpMessage
HelpMessageBaseName
HelpMessageResourceId
Mandatory
ParameterSetName
Position
TypeId
ValueFromPipeline
ValueFromPipelineByPropertyName
ValueFromRemainingArguments

当您向每个值添加期望的数据类型时,该列表就更有用了:

1
2
3
4
5
6
7
8
[System.Management.Automation.ParameterAttribute]::new() |
Get-Member -MemberType *Property |
ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Type = ($_.Definition -split ' ')[0]
}
}

现在它看起来类似这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
Name                            Type
---- ----
DontShow bool
HelpMessage string
HelpMessageBaseName string
HelpMessageResourceId string
Mandatory bool
ParameterSetName string
Position int
TypeId System.Object
ValueFromPipeline bool
ValueFromPipelineByPropertyName bool
ValueFromRemainingArguments bool

例如这是 [CmdletBinding()] 的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[System.Management.Automation.CmdletBindingAttribute]::new() |
Get-Member -MemberType *Property |
ForEach-Object {
[PSCustomObject]@{
Name = $_.Name
Type = ($_.Definition -split ' ')[0]
}
}



Name Type
---- ----
ConfirmImpact System.Management.Automation.ConfirmImpact
DefaultParameterSetName string
HelpUri string
PositionalBinding bool
RemotingCapability System.Management.Automation.RemotingCapability
SupportsPaging bool
SupportsShouldProcess bool
SupportsTransactions bool
TypeId System.Object

PowerShell 技能连载 - 使用动态参数

大多数 PowerShell 函数使用静态参数。它们是定义在 param() 代码块中的,并且始终存在。一个不太为人所知的情况是您也可以快速地编程添加动态参数。动态参数的最大优势是您可以完全控制它们什么时候可以出现,以及它们可以接受什么类型的数值。它的缺点是需要使用大量底层的代码来“编程”参数属性。

以下是一个示例函数。它只有一个名为 “Company“ 的静态参数。仅当您选择了一个公司,该函数才会添加一个名为 “Department“ 的新的动态参数。新的动态参数 -Department 根据选择的公司暴露出一个可用值的列表。实质上,根据选择的公司,-Department 参数被关联到一个独立的 ValidateSet 属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
function Test-Department
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[ValidateSet('Microsoft','Amazon','Google','Facebook')]
$Company
)

dynamicparam
{
# this hash table defines the departments available in each company
$data = @{
Microsoft = 'CEO', 'Marketing', 'Delivery'
Google = 'Marketing', 'Delivery'
Amazon = 'CEO', 'IT', 'Carpool'
Facebook = 'CEO', 'Facility', 'Carpool'
}

# check to see whether the user already chose a company
if ($Company)
{
# yes, so create a new dynamic parameter
$paramDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary
$attributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute]

# define the parameter attribute
$attribute = New-Object System.Management.Automation.ParameterAttribute
$attribute.Mandatory = $false
$attributeCollection.Add($attribute)

# create the appropriate ValidateSet attribute, listing the legal values for
# this dynamic parameter
$attribute = New-Object System.Management.Automation.ValidateSetAttribute($data.$Company)
$attributeCollection.Add($attribute)

# compose the dynamic -Department parameter
$Name = 'Department'
$dynParam = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter($Name,
[string], $attributeCollection)
$paramDictionary.Add($Name, $dynParam)

# return the collection of dynamic parameters
$paramDictionary
}
}

end
{
# take the dynamic parameters from $PSBoundParameters
$Department = $PSBoundParameters.Department

"Chosen department for $Company : $Department"
}
}

请注意!当您在选择的编辑器中键入 Test-Department,初始情况下只有一个参数:Tbc-Company。当您选择了四个可用得公司之一,第二个参数 -Department 将变得可用,并且显示当前选中的公司可用的部门。

PowerShell 技能连载 - 读取网站内容

通常情况下,通过 PowerShell 的 Invoke-WebRequest 命令来获取原始的 HTML 网站内容是很常见的情况。脚本可以处理 HTML 内容并对它做任意操作,例如用正则表达式从中提取信息:

1
2
3
$url = "www.tagesschau.de"
$w = Invoke-WebRequest -Uri $url -UseBasicParsing
$w.Content

然而,有些时候一个网站的内容是通过客户端脚本代码动态创建的。那么,Invoke-WebRequest` 并不能返回浏览器中所见的完整 HTML 内容。如果仍要获取 HTML 信息,您需要借助一个真实的 WEB 浏览器。一个简单的方法是使用内置的 Internet Explorer:

1
2
3
4
5
6
7
8
$ie = New-Object -ComObject InternetExplorer.Application
$ie.Navigate($url)
do
{
Start-Sleep -Milliseconds 200
} while ($ie.ReadyState -ne 4)

$ie.Document.building.innerHTML

PowerShell 技能连载 - 接受不同的参数类型

个别情况下,您可能会希望创建一个可以接受不同参数类型的函数。假设您希望用户既可以传入一个雇员姓名,也可以传入一个 Active Directory 对象。

在 PowerShell 中有一个固定的原则:变量不能在同一时刻有不同的数据类型。由于参数是变量,所以一个指定的参数只能有一个唯一的类型。

然而,您可以使用参数集来定义互斥的参数,这是一种解决多个输入类型的好方法。以下是一个既可以输入服务名,也可以输入服务对象的示例函数。这基本上是 Get-Service 内部的工作原理,以下示例展示了它的实现方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function Get-MyService
{
[CmdletBinding(DefaultParameterSetName="String")]
param
(
[String]
[Parameter(Mandatory,Position=0,ValueFromPipeline,ParameterSetName='String')]
$Name,

[Parameter(Mandatory,Position=0,ValueFromPipeline,ParameterSetName='Object')]
[System.ServiceProcess.ServiceController]
$Service
)

process
{
# if the user entered a string, get the real object
if ($PSCmdlet.ParameterSetName -eq 'String')
{
$Service = Get-Service -Name $Name
}
else
{
# else, if the user entered (piped) the expected object in the first place,
# you are good to go
}

# this call tells you which parameter set was invoked
$PSCmdlet.ParameterSetName

# at the end, you have an object
$Service
}

}

我们看看该函数的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
PS> Get-MyService -Name spooler
String

Status Name DisplayName
------ ---- -----------
Running spooler Print Spooler



PS> $spooler = Get-Service -Name Spooler

PS> Get-MyService -Service $spooler
Object

Status Name DisplayName
------ ---- -----------
Running Spooler Print Spooler



PS> "Spooler" | Get-MyService
String

Status Name DisplayName
------ ---- -----------
Running Spooler Print Spooler



PS> $spooler | Get-MyService
Object

Status Name DisplayName
------ ---- -----------
Running Spooler Print Spooler

如您所见,用户可以传入一个服务名或是 Service 对象。Get-MyService 函数模仿 Get-Service 内部的实现机制,并且返回一个 Service 对象,无论输入什么类型。以下是上述函数的语法:

1
2
3
**Syntax**
Get-MyService [-Name] <string> [<CommonParameters>]
Get-MyService [-Service] <ServiceController> [<CommonParameters>]

PowerShell 技能连载 - 将 VBScript 翻译为 PowerShell

大多数旧的 VBS 脚本可以容易地翻译为 PowerShell。VBS 中关键的命令是 “CreateObject“。它能让您操作系统的库。PowerShell 将 “CreateObject“ 翻译为 “New-Object -ComObject“,而对象模型和成员名称保持相同:

当把这段代码保存为扩展名为 “.vbs” 的文本文件后,这段 VBS 脚本就可以发出语音:

1
2
3
Set obj = CreateObject("Sapi.SpVoice")

obj.Speak "Hello World!"

对应的 PowerShell 代码类似这样:

1
2
$obj = New-Object -ComObject "Sapi.SpVoice"
$obj.Speak("Hello World!")

只有少量的 VBS 内置成员,例如 MsgBoxInputBox。要翻译这些代码,您需要引入 “Microsoft.VisualBasic.Interaction“ 类型。以下是调用 MsgBoxInputBox 的 PowerShell 代码:

1
2
3
4
5
6
Add-Type -AssemblyName Microsoft.VisualBasic

$result = [Microsoft.VisualBasic.Interaction]::MsgBox("Do you want to restart?", 3, "Question")
$result

$result = [Microsoft.VisualBasic.Interaction]::InputBox("Your name?", $env:username, "Enter Name")

以下是支持的 Visual Basic 成员的完整列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
PS> [Microsoft.VisualBasic.Interaction] | Get-Member -Stati


TypeName: Microsoft.VisualBasic.Interaction

Name MemberType Definition
---- ---------- ----------
AppActivate Method static void AppActivate(int ProcessId), static vo...
Beep Method static void Beep()
CallByName Method static System.Object CallByName(System.Object Obj...
Choose Method static System.Object Choose(double Index, Params ...
Command Method static string Command()
CreateObject Method static System.Object CreateObject(string ProgId, ...
DeleteSetting Method static void DeleteSetting(string AppName, string ...
Environ Method static string Environ(int Expression), static str...
Equals Method static bool Equals(System.Object objA, System.Obj...
GetAllSettings Method static string[,] GetAllSettings(string AppName, s...
GetObject Method static System.Object GetObject(string PathName, s...
GetSetting Method static string GetSetting(string AppName, string S...
IIf Method static System.Object IIf(bool Expression, System....
InputBox Method static string InputBox(string Prompt, string Titl...
MsgBox Method static Microsoft.VisualBasic.MsgBoxResult MsgBox(...
Partition Method static string Partition(long Number, long Start, ...
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, S...
SaveSetting Method static void SaveSetting(string AppName, string Se...
Shell Method static int Shell(string PathName, Microsoft.Visua...
Switch Method static System.Object Switch(Params System.Object[...



PS>

PowerShell 技能连载 - 通过 PowerShell 调用 Excel 宏

PowerShell 可以调用 Microsoft Excel 工作表,并执行其中的宏。由于这只对可见的 Excel 程序窗口有效,所以当您尝试进行安全敏感操作,例如宏时,最好保持 Excel 打开(参见以下代码)来查看警告信息:

1
2
3
4
5
6
7
8
9
# file path to your XLA file with macros
$FilePath = "c:\test\file.xla"
# macro name to run
$Macro = "AddData"

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true
$wb = $excel.Workbooks.Add($FilePath)
$excel.Run($Macro)

PowerShell 技能连载 - 编程列出所有 Cmdlet 或函数参数的列表

是否曾好奇如何列出一个函数或 cmdlet 暴露出的所有属性?以下是实现方法:

1
Get-Help Get-Service -Parameter * | Select-Object -ExpandProperty name

Get-Help 提供了一系列关于参数的有用的信息和元数据。如果您只希望转储支持管道输入的参数,以下是实现方法:

1
2
3
Get-Help Get-Service -Parameter * |
Where-Object { $_.pipelineInput.Length -gt 10 } |
Select-Object -Property name, pipelineinput, parameterValue

pipelineInput“ 属性暴露了通过管道接收到的一个属性的类型。不幸的是,它包含了一个本地化的字符串,所以一个区分的好方法是取字符串的长度。

输出的结果类似这样,并且可以从管道上游的命令中接受管道的输入,以及接受数据类型:

1
2
3
4
5
name         pipelineInput                  parameterValue
---- ------------- --------------
ComputerName True (ByPropertyName) String[]
InputObject True (ByValue) ServiceController[]
Name True (ByPropertyName, ByValue) String[]

PowerShell 技能连载 - 编程检查对象属性

当您用 Import-Csv 将一个 CSV 列表导入 PowerShell,或用任何其它类型的对象来处理时:如何自动确定对象的属性?以下是一个简单的方法:

1
2
3
4
5
# take any object, and dump a list of its properties
Get-Process -Id $pid |
Get-Member -MemberType *property |
Select-Object -ExpandProperty Name |
Sort-Object

为什么这种方法有用?有许多使用场景。例如,您可以检测一个注册表键的名称,支持用通配符转储所有的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$RegPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"

# get actual registry values from path
$Values = Get-ItemProperty -Path $RegPath

# exclude default properties
$default = 'PSChildName','PSDrive','PSParentPath','PSPath','PSProvider'

# each value surfaces as object property
# get property (value) names
$keyNames = $Values |
Get-Member -MemberType *Property |
Select-Object -ExpandProperty Name |
Where-Object { $_ -notin $default } |
Sort-Object

# dump autostart programs
$keyNames | ForEach-Object {
$values.$_
}

PowerShell 技能连载 - 存取隐藏(私有)成员

对象和类型中包括方法和属性等成员,但只有少数是公开可见和可使用的。还有许多隐藏(私有)的成员。在生产系统上使用这些成员是不明智的,当它们更新版本的时候,您并不会得到通知,所以可能会工作不正常。所以对于高级的 PowerShell 开发者来说是一个很好奇的地方。

有一个免费的 PowerShell 模块名为 ImpliedReflection,能将私有的成员变为可见,甚至在 PowerShell ISE 和 Visual Studio Code 的 IntelliSense 中,而且您可以运行那些成员。

例如,以下公有的类型只暴露了一个公有的方法,您可以用它来构造 PowerShell 的模块路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PS> [System.Management.Automation.ModuleIntrinsics]::GetModulePath

OverloadDefinitions
-------------------
static string GetModulePath(string currentProcessModulePath, string hklmMachineModulePath, string
hkcuUserModulePath)




PS> [System.Management.Automation.ModuleIntrinsics] | Get-Member -Static


TypeName: System.Management.Automation.ModuleIntrinsics

Name MemberType Definition
---- ---------- ----------
Equals Method static bool Equals(System.Object objA, System.Object objB)
GetModulePath Method static string GetModulePath(string currentProcessModulePath, string hklmMa...
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, System.Object objB)

现在我们像这样安装 ImpliedReflection

1
Install-Module -Name ImpliedReflection -Scope CurrentUser

当该模块安装以后,您需要先允许该扩展:

1
PS> Enable-ImpliedReflection -Force

现在,当您重新访问该类型并查看它的成员时,仍然只显示其公有成员。只有当您交互式输出该类型时,该扩展才起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
PS> [System.Management.Automation.ModuleIntrinsics] | Get-Member -Static


TypeName: System.Management.Automation.ModuleIntrinsics

Name MemberType Definition
---- ---------- ----------
Equals Method static bool Equals(System.Object objA, System.Object objB)
GetModulePath Method static string GetModulePath(string currentProcessModulePath, string hklmMa...
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, System.Object objB)



PS> [System.Management.Automation.ModuleIntrinsics]

IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False ModuleIntrinsics System.Object



PS> [System.Management.Automation.ModuleIntrinsics] | Get-Member -Static


TypeName: System.Management.Automation.ModuleIntrinsics

Name MemberType Definition
---- ---------- ----------
AddToPath Method static string AddToPath(string basePath, string pathToAdd, ...
CombineSystemModulePaths Method static string CombineSystemModulePaths()
Equals Method static bool Equals(System.Object objA, System.Object objB)
ExportModuleMembers Method static void ExportModuleMembers(System.Management.Automatio...
GetDscModulePath Method static string GetDscModulePath()
GetExpandedEnvironmentVariable Method static string GetExpandedEnvironmentVariable(string name, S...
GetManifestGuid Method static guid GetManifestGuid(string manifestPath)
GetManifestModuleVersion Method static version GetManifestModuleVersion(string manifestPath)
GetModuleName Method static string GetModuleName(string path)
GetModulePath Method static string GetModulePath(string currentProcessModulePath...
GetPersonalModulePath Method static string GetPersonalModulePath()
GetSystemwideModulePath Method static string GetSystemwideModulePath()
IsModuleMatchingModuleSpec Method static bool IsModuleMatchingModuleSpec(psmoduleinfo moduleI...
IsPowerShellModuleExtension Method static bool IsPowerShellModuleExtension(string extension)
NewAliasInfo Method static System.Management.Automation.AliasInfo NewAliasInfo(...
PathContainsSubstring Method static int PathContainsSubstring(string pathToScan, string ...
PatternContainsWildcard Method static bool PatternContainsWildcard(System.Collections.Gene...
ProcessOneModulePath Method static string ProcessOneModulePath(System.Management.Automa...
ReferenceEquals Method static bool ReferenceEquals(System.Object objA, System.Obje...
RemoveNestedModuleFunctions Method static void RemoveNestedModuleFunctions(psmoduleinfo module)
SetModulePath Method static string SetModulePath()
SortAndRemoveDuplicates Method static void SortAndRemoveDuplicates[T](System.Collections.G...
_ctor Method static System.Management.Automation.ModuleIntrinsics _ctor(...
MaxModuleNestingDepth Property static int MaxModuleNestingDepth {get;}
PSModuleExtensions Property static string[] PSModuleExtensions {get;set;}
PSModuleProcessableExtensions Property static string[] PSModuleProcessableExtensions {get;set;}
SystemWideModulePath Property static string SystemWideModulePath {get;set;}
Tracer Property static System.Management.Automation.PSTraceSource Tracer {g...

现在您可以使用私有的成员了,好像它们是公有的一样:

1
2
3
4
5
PS> [System.Management.Automation.ModuleIntrinsics]::GetPersonalModulePath()
C:\Users\tobwe\Documents\WindowsPowerShell\Modules

PS> [System.Management.Automation.ModuleIntrinsics]::SystemWideModulePath
c:\windows\system32\windowspowershell\v1.0\Modules

再次强调,这仅仅适用于希望更深入了解对象和类型内部的工作机制的高级用户。ImpliedReflection 模块用于操作私有成员。在生产环境下,您需要十分谨慎地操作私有成员。