WindowsPowerShell2.0创建调⽤脚本⽂件
在PowerShell中不存在⽂件和⽬录的概念,涉及⽂件和⽬录的操作总是转换为项(item)处理,即Get-Item、Get-ChildItem和Get-ItemProperty。在PowerShell中的dir命令以Get-ChildItem的别名出现,通过项操作对象的原因是PowerShell作⽤的任何类型的对象均以项的形式存在。⽽不像类Unix系统中将所有的对象都抽象成⽂件,即使这个对象很明显不是⽂件的情况。
项是包含内容的属性对象,这些对象⼜可以包含其他项。这个定义可以很容易扩展到⽂件和⽬录,即它们是具有各⾃内容和属性的项。项的概念可以应⽤于多个系统对象,提供程序(provider)⽤来创建、回、修改和移除项。它是官⽅提供的重要的Shell扩展机制之⼀,Shell 与⼀些内置的提供程序⼀起发布给⽤户,其中提供了提供程序的接⼝。
1 创建脚本
典型的PowerShell脚本是⽂本类型的⽂件,可以使⽤任何⽂本编辑器来创建。通常情况下,具有.ps1的⽂件后缀。最好使⽤⼀些语法⾼亮,甚⾄是智能补全功能的编辑器,如创建本书中脚本⽂件的Notepad++编辑器。这是⼀个功能强⼤的编辑器,⽀持多种语⾔,并且提供语法⾼亮及关键字补全等;另外可以在PowerShell控制台下使⽤Set-Content这个cmdlet来创建脚本⽂件。下⾯使⽤该命令创建⼀个脚本⽂件:
PS C:\> $code = @"
>> Write-Host "Hello World!!"
>> "@
>>
PS C:\> Set-Content hello-world.ps1 $code
PS C:\> type hello-world.ps1
Write-Host "Hello World!!"
hello-world.ps1以普通的⽂本⽂件形式存在,可以使⽤type命令(以Get-Content命令的别名存在)来查看其内容。
1.1 调⽤脚本
在PowerShell中调⽤脚本⽂件时应在PATH环境变量中包含的路径中搜索,当前⽬录并不在系统的PATH中。调⽤当前⽬录中的脚本⽂件需要添加前缀,调⽤当前的脚本⽂件的⽅式应该是.\hello-world.
ps1。
如果当前⽬录默认包含在PATH中,则允许攻击者采⽤恶意代码覆盖同名的默认程序。假设恶意⽤户在共享⽬录中写⼊与内置cmdlet同名的脚本,当管理员访问这个共享⽬录时并调⽤了该脚本,则可导致恶意操作。因此在PowerShell和Unix的Shell中需要⽤户通过在当前路径前增加.\前缀,显式地指明需要执⾏当前⽬录中的程序。
调⽤创建的hello-world.ps1脚本⽂件提⽰以下错误:
PS C:\> .\hello-world.ps1
File C:\hello-world.ps1 cannot be
loaded because the execution of scripts is
disabled on this system. Please see "get-help
about_signing" for more details.
At line:1 char:18
+ .\hello-world.ps1 <<<<
+ CategoryInfo : NotSpecified: (:) [], PSSecurityException
+ FullyQualifiedErrorId : RuntimeException
从提⽰中可以看到抛出错误是因为当前脚本在使⽤的默认安全设置为不允许执⾏,Shell包含多种执⾏脚本的安全级别和⽤户权限策略,默认运⾏在Restricted(受限)策略级别下,既不允许执⾏脚本。可以通过执⾏Get-ExecutionPolicy命令查看当前脚本的执⾏权限:
PS C:\> Get-ExecutionPolicy
Restricted
为了能够执⾏正常的脚本操作,需要改变当前脚本的执⾏策略。预定义的脚本执⾏策略如下。
(1)Restricted:不允许执⾏任何脚本,⽽忽略⽤户是否具有超级⽤户权限,仍可执⾏控制台互交式命令。
(2)Allsigned:仅执⾏已经过受信第三⽅数字签名的脚本,关于签名脚本⽂件将在第12章中详细介绍。
shell创建文件并写入内容(3)RemoteSigned:在执⾏任何来⾃Internet的脚本⽂件时要求其具有受信第三⽅数字签名;否则拒绝执⾏,执⾏本地创建的脚本⽂件不需要数字签名。
(4)Unrestricted:所有脚本均可执⾏,⽆论是否具有签名,或者签名是否合法。
RemoteSigned执⾏策略兼顾了好的⽹络安全级别和本地易⽤性。为了执⾏前⾯创建的脚本⽂件,使⽤Set-ExecutionPolicy命令调整安全策略级别,是这个cmdlet只能由超级管理员(administrator)来执⾏。如果是普通⽤户,则可右击PowerShell的快捷⽅式,然后选择快捷菜单中的“以管理员⽅式运⾏”(Run as administrator)选项,这样就有了以超级⽤户⾝份创建的shell线程,从⽽可以执⾏命令改变执⾏策略,如下:
PS C:\> Set-ExecutionPolicy RemoteSigned
PS C:\> Get-ExecutionPolicy
RemoteSigned
下例调⽤本地脚本⽂件:
PS C:\> .\hello-world.ps1
Hello World!!
另外脚本块执⾏操作符(&)也可执⾏脚本⽂件:
PS C:\> & .\hello-world.ps1
Hello World!!
PS C:\> & .\hello-world
Hello World!!
1.2 传递参数
脚本能够接收参数,这样可以针对不同对象执⾏所需操作。脚本⽂件⽀持$args变量,⽤于在执⾏时接收传递的参数。下例在指定⽬录中查⽂件名符合指定通配符的⽂件:
$where = $args[0]
$what = $args[1]
if(!$what -match "\.wma$")
{
$what = $what + ".wma"
}
Get-ChildItem $where $what –Recurse
$what参数⽤来在通配符不包含.wma扩展名时添加扩展名,从⽽限制搜索⽂件的范围只限于.wma⽂件。代码递归遍历所有⽂件,并返回符合$what通配符的⽂件。将以上代码保存为Get-Music.ps1脚本⽂件,其中查名为“ A Place Near by.wma”的⾳乐⽂件:
PS C:\> .\Get-Music.ps1 'D:\Music' *Near*
Directory: D:\Music
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2009/3/1 20:38 3006849 A Place Near by.wma
脚本⽂件允许使⽤param语句定义⾃⼰的参数名,以避免$args参数对象要求输⼊参数的顺序必须与预定义顺序⼀致。下例⽤命名参数⽅式重写前⾯的脚本⽂件,并命名为“Get-Music-Param.ps1”:
param ($where,$what)
if(!$what -match "\.wma$")
{
$what = $what + ".wma"
}
Get-ChildItem $where $what –Recurse
其中的参数允许在不限制输⼊参数顺序的情况下调⽤脚本⽂件,下⾯调⽤该脚本⽂件查名为“Dont You Forget.wma”的⾳乐⽂件:
PS C:\> .\Get-Music-Param.ps1 -where 'D:\Music' -what *You*
Directory: D:\Music
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2009/3/1 21:19 3556057 Dont You Forget.wma
通过定义begin、process和end块来实现在管道命令中输⼊,下例获取⽂件序列并过滤掉所有⼤于指定⼤⼩的⽂件,将代码保存为FileSize-FilterLarge.ps1脚本:
param ($sizeLimit)
begin
{
}
process
{
if($_.Length -ge $sizeLimit)
{
$_
}
}
end
{
}
脚本⽂件接收⽂件⼤⼩变量⽤于后⾯检测所有管道命令传递项的⼤⼩,最后返回⽂件长度等于⼤⼩限制的⽂件。实例中的所有操作在process块中执⾏,当调⽤代码块时会作⽤于管道输⼊的所有项。begin和end块未执⾏任何操作,但是为了代码的完整性和可读性,总是习惯性地包含它们,即使其为空。下例使⽤脚本⽂件查⼩于13个字节的⽂件:
PS C:\> dir *.txt | .\FileSize-FilterLarge.ps1 13
Directory: C:\
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 2009/1/3 14:31
-ar-- 2009/1/3 6:27
-a--- 2009/1/3 6:28
与函数相似,脚本⽂件会创建独⽴的变量作⽤域。即可读⼊由当前shell环境及其⽗作⽤域中的变量,对同名变量的写⼊会在当前作⽤域下覆盖⽗作⽤域中的值。下⾯创建⼀个访问⽗作⽤域的脚本⽂件:
PS C:\> $name = "LiMing"
PS C:\> Set-Content Variable-Scope.ps1 "Write-Host $name"
PS C:\> .\Variable-Scope.ps1
LiMing
接下来演⽰如果在脚本⽂件中修改外部已经初始化的变量,是否会更改该变量的值,将以下代码保存为“Variable-
ScopeInnerVariable.ps1”⽂件:
Write-Host "Variable Within script original: $name"
$name = "XiaoGang"
Write-Host "Variable Within script modified: $name"
调⽤这个脚本⽂件验证原有变量的内容:
PS C:\> $name = "LiMing"
PS C:\> .\Variable-ScopeInnerVariable.ps1
Variable Within script original: LiMing
Variable Within script modified: XiaoGang
PS C:\> Write-Host "Variable Outside of script $name"
Variable Outside of script LiMing
可以看出修改变量的操作只在脚本⽂件内部起作⽤,如果需要在脚本⽂件内部修改⽗作⽤域中的变量,则在脚本⽂件中使⽤$global作⽤域前缀或者Get-Variable/Set-Variable这类cmdlet显式要求修改⽗作⽤域变量。下例在脚本⽂件内使⽤$global关键字修改⽗作⽤域变量:
Write-Host "Variable Within script original: $global:name"
$global:name = "XiaoGang"
Write-Host "Variable Within script modified: $global:name"
将上述代码保存为“Variable-ModifyGloablVariable.ps1”⽂件,调⽤该⽂件时的提⽰如下:
PS C:\> $name = "LiMing"
PS C:\> .\Variable-ModifyGloablVariable.ps1
Variable Within script original: LiMing
Variable Within script modified: XiaoGang
PS C:\> Write-Host "Variable outside script :$global:name"
Variable outside script :XiaoGang
有时需要隔离特定的变量或函数,对于特定的脚本⽂件来说它是全局的。需要让其中定义的所有函数和脚本块能够访问该变量或函数,但⽤户需要显式强调不能在脚本⽂件外访问对象。这时不能使⽤$global作⽤域前缀,因为这将使该变量或函数暴露在所有代码⾯,⽆论它们是否已在脚本⽂件中定义。解决这个问题的⽅法是使⽤$script作⽤域前缀,为了说明这个问题,建⽴如下脚本⽂件代码:
$script:name = "LiuTao"
function Modify-Name()
{
$script:name = "XiaoGang"
Write-Host "Variable within script function scope:$script:name"
}
Write-Host "Variable Original script global scope: $script:name"
Modify-Name
Write-Host "Variable Modified script global scope: $script:name"
上述代码中存在脚本全局作⽤域和Modify-Name函数作⽤域,在shell可以通过引⽤$script:name调⽤脚本全局变量。将以上脚本另存
为“Variable-ScriptScope.ps1”⽂件并调⽤:
PS C:\> $name = "LiMing"
PS C:\> .\Variable-ScriptScope.ps1
Variable Original script global scope: LiuTao
Variable within script function scope:XiaoGang
Variable Modified script global scope: XiaoGang
PS C:\> Write-Host "Variable outeside of script: $name"
Variable outeside of script: LiMing
能够看到对于脚本⽂件以外的$name变量并没有受到影响。
与此相对应,通过在脚本⽂件名前增加点(.)前缀来实现脚本⽂件访问所有的当前变量和函数,即将脚本⽂件点源引⽤(dot-sourcing)到当前shell进程中。点源引⽤使⽤点加空格再加上要引⽤的脚本⽂件名,将该脚本⽂件的所有变量、函数、脚本块释放在当前shell中供使⽤。下例以点源引⽤的⽅式来调⽤Variable-ScriptScope.ps1脚本⽂件:
PS C:\> $name = "LiMing"
PS C:\> . .\Variable-ScriptScope.ps1
Variable Original script global scope: LiuTao
Variable within script function scope:XiaoGang
Variable Modified script global scope: XiaoGang
PS C:\> Write-Host "Variable outeside of script: $name"
Variable outeside of script: XiaoGang
由于使⽤了点源引⽤⽅式来调⽤同⼀个脚本⽂件,因此结果与之前不同,即修改了全局变量$name的值。
1.3 返回值
在⼤多数情况下,参数是外部世界传递数据到脚本⽂件的唯⼀通信机制。在脚本⽂件处理数据之后需要将结果返回给外部,在PowerShell 中提供了很好的实现⽅法。
如果没有显式销毁、赋值给变量、管道输出或重定向到其他命令对象,则将会为下个命令输出到管道中,可以通过脚本⽣成⼀系列的对象并输出。下例输出3个缓存⽂件名:
PS C:\> $code = @"
>> "p"
>> "p"
>> "p"
>> "@
>>
PS C:\> Set-Content Generate-TempFiles.ps1 $code
PS C:\> $files = .\Generate-TempFiles.ps1
PS C:\> $files
PS C:\> .\Generate-TempFiles.ps1 | foreach{"File:" + $_ }
p
p
p
既可将对象赋给变量,也可传递到管道中的下⼀个命令。
下例中的return语句返回现有对象,并终⽌后续代码:
PS C:\> $code = @"
>> return "p"
>> return "p"
>> "@
>>
PS C:\> Set-Content Generate-TempFilesReturn.ps1 $code
PS C:\> .\Generate-TempFilesReturn.ps1
代码在第1个return语句之后终⽌,因⽽只输出⼀个⽂件对象。
如果是运⾏在顶层作⽤域中,return语句会退出脚本⽂件,但是上述代码中该语句只是退出当前作⽤域。即如果return语句是在脚本⽂件中的脚本块或函数中,只是退出该脚本块或函数,⽽脚本⽂件本⾝还将继续执⾏。为了显式退出脚本⽂件,需要使⽤exit语句。下例在脚本⽂件中的函数中退出整个脚本⽂件的执⾏:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论