Windows PowerShell4-函数和脚本

文章目录

  • 介绍
  • 函数
    • 介绍
    • 为函数添加参数
    • 接受管道输入
  • 你把这叫作脚本
    • 使得命令可重复执行
    • 参数化命令
    • 创建一个带有参数的脚本
    • 为脚本添加文档
    • 作用域初探
  • 优化可传参脚本
    • 介绍
    • 将参数定义为强制化参数及添加参数别名
    • 验证输入参数及添加详细输出以获取易用性体验
  • 总结

介绍

PowerShell作为Windows系统中的默认脚本,其实在开发中,我们用的很少。接下来的几篇文章都将PowerShell的相关知识,在上一章中我们介绍了PowerShell进行远程的相关知识,本章我们将深入了解其函数和脚本的相关知识。

函数

介绍

函数使用function关键字进行定义,后面跟一个由我们自定义的描述性名称,在后面是一对花括号,内放一个脚本块,供PowerShell执行。

function Install-Software { Write-Host 'I installed some software, Yippee!' }

Install-Software

可以在脚本中定义函数,也可以直接在控制台中输入。

函数的命名一般遵循动词+名称的方式进行

为函数添加参数

PowerShell函数可以有任意多个参数。自己编写函数时,可以选择是否添加参数以及添加什么样的参数。参数可以是强制的,也可以是可选择的,可以接收任何值或限定接收某个范围的值。

通过param块可以添加函数的参数,且所有参数都放在这个块中。接下来的例子,我们希望安装软件的不同版本。

function Install-Software 
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [string] $Version
    )

    Write-Host "I installed software version $Version, Yippee!"
}
Install-Software -Version 2

# 输出:I installed software version 2, Yippee!

如上,定义了一个string类型的$Version。此处的Parameter块是空的,但是它可以控制参数的多个属性,以改变参数的行为。如果想确保调用的函数必须传入参数,可以使用Mandatory定义,同时可以给出默认的参数值并添加相应的验证功能。

function Install-Software 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]  # 强制输入
        [ValidateSet('1','2')]  # 输入值验证,必须在1和2之中才可以,否则报错
        [string] $Version = 2   # 参数默认值2,即调用的时候可以不需要输入参数
    )

    Write-Host "I installed software version $Version, Yippee!"
}

接受管道输入

接下来,我们可以考虑让我们的函数接收管道的输入。首先我们来添加一个新的参数$ComputerName用来接收计算机的名称。

function Install-Software 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]  # 强制输入
        [ValidateSet('1','2')]  # 输入值验证,必须在1和2之中才可以,否则报错
        [string] $Version = 2   # 参数默认值2,即调用的时候可以不需要输入参数

        [Parameter(Mandatory)]
        [string] $ComputerName
    )

    Write-Host "I installed software version $Version on $ComputerName, Yippee!"
}

Install-Software -Version 2 ComputerName "SRV1"

PowerShell函数可以使用的管道有两种:ByValueByPropertyName。由于$ComputerName是字符串,因此可以使用ByValue方式传递这些字符串。如果想添加管道支持,相应的参数需要添加参数属性,开以在ValueFromPipelineValueFromPipelineByPropertyName两个关键字中二选一。

function Install-Software 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]  # 强制输入
        [ValidateSet('1','2')]  # 输入值验证,必须在1和2之中才可以,否则报错
        [string] $Version = 2   # 参数默认值2,即调用的时候可以不需要输入参数

        [Parameter(Mandatory,ValueFromPipeline)]
        [string] $ComputerName
    )

    Write-Host "I installed software version $Version on $ComputerName, Yippee!"
}

# 调用
$computers =@("SRV1","SRV2","SRV3")
$computers | Install-Software -Version 2 
# 输出: I installed software version 2 on SRV3, Yippee!

可以看到,函数只在数组中的最后一个字符串上执行了,为了解决这个问题,我们可以添加process块。

function Install-Software 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]  # 强制输入
        [ValidateSet('1','2')]  # 输入值验证,必须在1和2之中才可以,否则报错
        [string] $Version = 2   # 参数默认值2,即调用的时候可以不需要输入参数

        [Parameter(Mandatory,ValueFromPipeline)]
        [string] $ComputerName
    )

    process {
        Write-Host "I installed software version $Version on $ComputerName, Yippee!"
    }
}

# 调用
$computers =@("SRV1","SRV2","SRV3")
$computers | Install-Software -Version 2 

添加了process块后的输出。

I installed software version 2 on SRV1, Yippee!
I installed software version 2 on SRV2, Yippee!
I installed software version 2 on SRV3, Yippee!

process块中应该包含你相应执行的主代码。另外,还可以使用beginend块,以便在函数调用的开头和结尾执行代码。

你把这叫作脚本

使得命令可重复执行

PowerShell脚本背后的理念,首先是使得重复执行特定命令变得简单,而无需每次手动重复输入命令。我们以下面的例子来说明。请注意,我们接下来的代码都是在PowerShell ISE中编写的。当然,你可以选择自己喜欢的第三方编辑工具。

Get-WmiObject -Class Win32_LogicalDisk -ComputerName localhost `
-Filter "DriveType=3" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }

请记住,这里使用namelabel都是可以的,但是此处更建议使用name,它们可以分别简写为nlL的小写)

编辑好后,我们就保存命令——现在就可以把保存后的命令称为脚本。我们将其另存为Get-DiskInventory.ps1。我们以“动词-名称”这样的Cmdlet风格命名脚本。如有要运行脚本查看执行效果,直接按菜单栏上的绿色三角形或F5即可运行程序。

参数化命令

当你考虑到一遍遍运行同一个命令是,你或许会意识到命令的某些部分在每次运行时都会产生变化。就以当前示例,计算机名称是会变动的,因为不能你把脚本分享给你的同事使用时,还是执行的本地计算机的查询命令。所有我们考虑使用以下修改。

$computername='localhost'
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername `
-Filter "DriveType=3" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }

创建一个带有参数的脚本

既然我们已经识别出了脚本中每次执行可能变化的部分,那么我们就需要提供一种更让其他人赋予这些元素新值的方式。换句话说,我们需要将被赋予常量的$computername变量转变为一个输入参数。

Param (
$computername='localhost'
)
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername `
-Filter "DriveType=3" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }

我们只需要在变量声明代码附件添加一个param()块。这会将$computername定义为一个参数,并在未对该参数赋值时指定localhost作为默认值。可以使用以下方式调用

.Get-DiskInventory.ps1 server-r2
.Get-DiskInventory.ps1 -computername server-r2
.Get-DiskInventory.ps1 -comp server-r2

你可以通过逗号作为分隔符指定任意数量的参数。比如,我们希望将过滤条件设置为参数。当前脚本仅获取类型为3的驱动器,即硬盘。我们可以将其参数化。

Param (
$computername='localhost',
$drivetype=3
)
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername `
-Filter "DriveType=$drivetype" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }

使用该脚本的方式如下。

.Get-DiskInventory.ps1 server-r2 3
.Get-DiskInventory.ps1 -computername server-r2 -drive 3
.Get-DiskInventory.ps1 server-r2
.Get-DiskInventory.ps1 -drive 3

为脚本添加文档

PowerShell提供了简单的方式为脚本添加帮助,也就是注释。示例如下。

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or more computers.

.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk instances from one or
more computers. It displays each disk's drive letter, free space , total size, and 
percentage of free space.

.PARAMETER computername
The computer name, or names.Default: localhost.

.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation for values. 3 is fixed
disk, and is the default.

.EXAMPLE
Get-DiskInventory -computername SERVER-R2 -drivetype 3
#>
Param (
$computername='localhost',
$drivetype=3
)
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername `
-Filter "DriveType=$drivetype" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }

正常情况,PowerShell都会忽略以#开头的代码行,意味着#用于标识某行是注释。而我们使用<# #>块注释语法,这是由于我们需要多行注释,而不希望在每一行开始使用#。现在我们可以使用标准的控制台宿主,并通过运行Help Get-DiskInventory.ps1命令获取帮助。

在这里插入图片描述

PowerShell中,所有命令都在一个管道中执行,在脚本中也是同样的。在脚本中,任何产生管道输出结果的命令都会被写入同一个管道中:脚本自身运行的管道。正常来讲,你的脚本应该努力保持只输出一类对象,以便PowerShell能产生合理的文本输出格式。

作用域初探

我们最后需要讨论的一个主题是作用域(scope)。作用域是特定类型PowerShell元素的容器,这些元素主要是别名、变量和函数。

Shell本身具有最高级的作用域,称为全局域(global scope)。当运行一个脚本时,会在脚本范围内创建一个新的作用域,也就是所谓的脚本作用域(script scope)。脚本作用域是全局作用域的子集,函数还有其特有的私有作用域(private scope)。

作用域的生命周期只持续到作用域所需执行的最后一行代码之前。这意味着全局作用域只有在PowerShell运行时才有效,脚本作用域只有在脚本运行时有效。如你尝试访问一个作用域元素,PowerShell在当前作用域中查找;如果不存在则会查找其父作用域,依此类推,直到全局作用域。当在作用域内定义一个变量、别名或函数时,当前作用域就无法访问父作用域内的任何同名变量、别名或函数。

优化可传参脚本

介绍

我们还是使用之前创建的Get-DiskInventory.ps1文件内容,并且考虑在备注的代码后面,将[CmdletBinding()]指示符置于脚本的第一行非常重要。PowerShell只会在该位置查看该指示符。加上这个指示符后,脚本会正常运行,但我们已经启用了好几个功能,接下来将一一说明。

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or more computers.

.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk instances from one or
more computers. It displays each disk's drive letter, free space , total size, and 
percentage of free space.

.PARAMETER computername
The computer name, or names.Default: localhost.

.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation for values. 3 is fixed
disk, and is the default.

.EXAMPLE
Get-DiskInventory -computername SERVER-R2 -drivetype 3
#>
[CmdletBinding()]
Param (
$computername='localhost',
$drivetype=3
)
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername `
-Filter "DriveType=$drivetype" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }

注意:脚本最后用的是Select-Object而不是Format-Table,因为脚本的结果已经格式化不是一个好的主意,而应该将其交给使用者,让其决定以何种格式输出,如csvxml

将参数定义为强制化参数及添加参数别名

在此示例中,对于-ComputerName参数,我们更倾向于选择提示用户输入值,并考虑给它一个别名,所以进行了下面的修改。

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or more computers.

.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk instances from one or
more computers. It displays each disk's drive letter, free space , total size, and 
percentage of free space.

.PARAMETER computername
The computer name, or names.Default: localhost.

.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation for values. 3 is fixed
disk, and is the default.

.EXAMPLE
Get-DiskInventory -computername SERVER-R2 -drivetype 3
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True)]
[Alias('hostname')]
[string]$computername='localhost',
[int]$drivetype=3
)
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername `
-Filter "DriveType=$drivetype" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }

仅仅使用[Parameter(Mandatory=$True)]这样的描述,会使得当用户忘记提供计算机名称时,PowerShell就会提示用户输入该参数。有时也可以考虑使用[Parameter(Mandatory=$True,HelpMessage="Enter a computer anem to query")]也是不错的选择。为了识别用户传入参数,我们还给两个参数约束了数据类型,即在每个参数前面加上了[string][int]

下面是需要注意的重点。

  • 所有的参数都必须被包括在Param()代码段的括号内
  • 可以对一个参数添加多个修饰符,多个修饰符可以是一行,也可以多行,多行更易于阅读,但是重点是即使是多行,它们也是一个整体。Mandatory标签仅仅修饰-ComputerName参数,对-drivetype参数并没有影响
  • 除了最后一个参数外,所有参数之间以逗号隔开
  • 为了更好的阅读性,我们还喜欢在参数之间添加空格
  • 我们定义参数时,就好像参数是变量——$computername$drivetype——但是使用脚本的人会将其当作普通的PowerShell命令行参数

验证输入参数及添加详细输出以获取易用性体验

让我们来看看-drivetype参数。根据MSDNWin32_LogicalDisk这个WMI类的文档,驱动器类型3是本地磁盘,2是可移动磁盘。而驱动器类型1、4、5、6很少被使用,这种情况下,我们阻止用户输入这些类型。所以我们使用[ValidateSet(2,3)],将其值限制在23之间,并且默认值是3

<#
.SYNOPSIS
Get-DiskInventory retrieves logical disk information from one or more computers.

.DESCRIPTION
Get-DiskInventory uses WMI to retrieve the Win32_LogicalDisk instances from one or
more computers. It displays each disk's drive letter, free space , total size, and 
percentage of free space.

.PARAMETER computername
The computer name, or names.Default: localhost.

.PARAMETER drivetype
The drive type to query. See Win32_LogicalDisk documentation for values. 3 is fixed
disk, and is the default.

.EXAMPLE
Get-DiskInventory -computername SERVER-R2 -drivetype 3
#>
[CmdletBinding()]
Param (
[Parameter(Mandatory=$True)]
[Alias('hostname')]
[string]$computername='localhost',
[ValidateSet(2,3)]
[int]$drivetype=3
)
Write-Verbose "Cennecting to $computername"
Write-Verbose "Looking for drive type $drivetype"
Get-WmiObject -Class Win32_LogicalDisk -ComputerName $computername `
-Filter "DriveType=$drivetype" | 
Sort-Object -Property DeviceID | 
Select-Object -Property DeviceID,
        @{ label='FreeSpace(MB)';expression={ $_.FreeSpace / 1MB -as [int] } },
        @{ name='Size(GB)';expression={ $_.Size / 1GB -as [int]} },
        @{ name='%Free';expression={ $_.FreeSpace / $_.Size * 100 -as [int]} }
Write-Verbose "Finished running command"

而对于一些脚本使用者来说,他们希望看到脚本输出执行的进度,我们更倾向于使用Write-Verbose而不是Write-Host产生这些信息。如上面的示例,就可以得到详细输出——并且完全无需使用-Verbose参数的任何实现代码。当添加[CmdletBinding()]时,就可以无成本拥有详细输出。最妙的部分是,该标签还会激活脚本中所包含命令的详细输出!所以你使用的任何被设计可以产生详细输出的结果的命令都会被自动输出详细结果。

总结

以上是对PowerShell的函数和脚本进行介绍,在接下来的章节中我们将介绍PowerShell的其他相关知识。