文章

自定义供日常使用的PowerShell脚本模块

在使用Windows操作系统时,经常会以PowerShell作为Shell工具,本文描述如何封装自己经常使用的脚本供日常使用

编写脚本

在这里我们编写一个脚本,用来找出桌面上一个星期以上(大于或者等于8天)没有访问过的文件,因为是用于示例,这里使用相对容易理解的语法,并且添加了注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Get-OldFiles {
    # 获取桌面上所有的文件信息
    $all_files = Get-ChildItem $env:USERPROFILE\Desktop -Recurse -File

    # 循环文件信息,返回其文件名,路径,以及没有访问的天数
    foreach ($file in $all_files) {
        $not_access_day = ((Get-Date) - $file.LastAccessTime).Days
        if ($not_access_day -ge 8) {
            $value = [PSCustomObject] @{
                Name          = ""
                NotAccessDays = 0
                Path          = ""
            }
            $value.Name = $file.Name
            $value.Path = $file.FullName
            $value.NotAccessDays = $not_access_day
            Write-Output $value
        }
    }
}

可以看到,脚本里只有一个函数Get-OldFiles,这个函数可以用来查询出所有一个星期以上没有访问的文件信息

存储脚本

在将这个脚本保存为文件之前,我们先要了解一些关于PSModulePath的内容,这些内容可以查看about PSModulePath - PowerShell

我们可以了解到,PSModulePath也就是PowerShell脚本模块的保存路径,PowerShell会在里面递归搜索.psd1.psm1文件,使其中定义的脚本模块在当前PowerShell会话中可用

因此,我们现在要做的,就是把我们写好的脚本保存到PSMoudulePath,那么电脑上的PsModulePath指的是哪个文件夹呢,其实他不是一个文件夹,而是一些文件夹,PowerShell会在这些文件夹中寻找可以导入的模块。上面的链接中也有提及,这里进行简要说明(只对于没有自定义过PSModulePath,而使用默认路径的情况,关于如何自定义PSModulePath,也可以参考上面链接中的内容):

对于Windows系统来说(提及的路径可以在PowerShell中输出出来,例如Write-Output $PSHOME\Modules

  • 用于Windows系统管理的模块放置在:$PSHOME\Modules

对于PowerShell 7.x版本:

  • 用户自行安装(当前用户可用,安装时指定的ScopeCurrentUser)的模块放置在:$HOME\Documents\PowerShell\Modules
  • 用户自行安装(所有用户可用,安装时指定的ScopeAllUsers)的模块放置在:$env:ProgramFiles\PowerShell\Modules

对于PowerShell 5.1版本(Win10自带PowerShell):

  • 用户自行安装(当前用户可用,安装时指定的ScopeCurrentUser)的模块放置在:$HOME\Documents\WindowsPowerShell\Modules
  • 用户自行安装(所有用户可用,安装时指定的ScopeAllUsers)的模块放置在:$env:ProgramFiles\WindowsPowerShell\Modules

综上,现在我们想要新增的脚本模块对于这台电脑上的所有用户可用,我们选择将上面的脚本内容存储到$env:ProgramFiles\WindowsPowerShell\Modules文件夹

切换到上面说的文件夹:

1
cd $env:ProgramFiles\PowerShell\Modules

在这里新建一个文件夹,值得说明的是,这个文件夹将会作为稍后的模块名称,我们这里编写的函数是和文件有关的,我们起名叫FileTool,于是接着上面的命令:

1
mkdir FileTool; start FileTool

现在我们就创建并打开了我们的脚本模块文件夹,接着,我们将之前写好的脚本粘贴进任意的文本编辑器并保存到这个文件夹,文件后缀为.psm1即可,这里需要注意的是,一个模块里面至少要有一个文件和模块名称本身相同,关于这一点,可以查看Installing a PowerShell Module - PowerShell页面的Use the Correct Module Directory Name部分,所以这里我们将其保存为FileTool.psm1

使用脚本

保存完成后,我们在PowerShell中使用Get-Command -Module FileTool命令,发现FileTool模块的命令列表被列出来了,由于我们这里只定义了一个,可以看到:

1
2
3
CommandType Name         Version Source
----------- ----         ------- ------
Function    Get-OldFiles 0.0     FileTool

输入Get-OldFiles命令,命令被正确执行,至此,自定义PowerShell脚本模块的过程完成

扩展内容

.psd1文件的使用

在前面编写脚本部分,提到PowerShell也会检索.psd1文件,.psd1文件中记录的内容也被称为模块描述清单(module manifest),里面的内容是使用PowerShell语法创建的一个hash table,里面的键值对记录一个模块被导入时的一些配置,这个文件不需要我们手写,我们可以定位到上面保存.psm1文件的FileTool文件夹,使用命令创建一个.psd1文件

1
New-ModuleManifest -Path .\FileTool.psd1 -ModuleVersion "1.0" -Author "HANABI" -RootModule FileTool

可以看到,我们在这个命令中指定了.psd1文件的名称,脚本模块版本号,作者,以及与其关联的脚本模块名称。执行完这条命令后,我们启动一个新的PowerShell,再次输入命令Get-Command -Module FileTool,可以看到:

1
2
3
CommandType Name         Version Source
----------- ----         ------- ------
Function    Get-OldFiles 1.0     FileTool

版本信息已经成功改变了,.psd1文件的作用不限于此,还能通过它来指定有哪些函数是需要从脚本模块文件中导出的,从而只对外暴露那些想要被使用的函数,关于模块描述清单的更多信息,可以查看How to Write a PowerShell Module Manifest - PowerShell

关于模块导入

其实,查看当前PowerShell会话的可用脚本模块,应该使用的命令是Get-Module,但当我们在上述的编写脚本存储脚本步骤之后使用这个命令,是不能看到我们这次新增的FileTool模块的,只有当我们使用了属于这个模块的命令Get-OldFiles,或者使用Get-Command -Module FileTool命令之后,再次使用Get-Module命令,才能看到FileTool模块

之所以会出现这种情况,是因为实际上一个脚本模块,是需要先导入,才能进行使用的,但是从PowerShell 3.0开始,模块可以在使用其命令,或者使用上面提到的Get-Command命令(指定相应的模块或者命令)时被自动导入,而不需要再进行手动导入,具体可以查看Importing a PowerShell Module - PowerShell

如果需要显式导入的话,可以使用Import-Module FileTool命令来为当前PowerShell会话导入这个模块,若需要在每个PowerShell会话时都默认导入这个模块,可以在PowerShell的profile.ps1文件中添加每次启动PowerShell会话时都默认运行的脚本,profile.ps1文件的存储位置可以在PowerShell中通过命令查看:

1
$PROFILE | Select-Object CurrentUserAllHosts,AllUsersAllHosts | Format-List

输出的结果中,CurrentUserAllHosts是对当前用户的所有会话生效的profile.ps1文件路径,AllUsersAllHosts则是对应所有用户的所有会话的,我们可以使用命令向这个文件添加内容:

1
Add-Content $PROFILE.AllUsersAllHosts 'Import-Module FileTool'

之后再打开新的PowerShell会话,使用Get-Module,可以看到FileTool脚本模块已经被正确列出

不难发现,profile能做到的事情还有很多,不局限于自动导入脚本模块,如果想要了解更多关于PowerShell profile的内容,可以查看about Profiles - PowerShell

一些小事

  1. 保存.psm1文件,并且需要输出非ASCII字符时,文件的编码格式需要为UTF-8 with BOM,否则在使用PowerShell 5.1版本时会出现乱码或者脚本无法正常执行的问题(对于这里的.psm1文件来说,这会导致脚本模块无法被正常检测),这一点对于平时编写的.ps1文件(普通的PowerShell脚本文件)也同样适用

  2. 从上文对于.psd1文件的描述中不难看出,一个脚本模块文件可以定义不止一个函数,并且可以通过.psd1文件进行导出

  3. 对于PowerShell命令名的命名规范,请尽量使用<Verb>-<宾语>的格式,可以观察到PowerShell的几乎所有内置命令都是以这样的格式命名的,可用的Verb列表可以使用Get-Verb命令查看,这个命令会列出动词及其描述

  4. 关于脚本模块文件的存储路径,这里选择使用PowerShell 5.1的路径,是因为只要一台Win10电脑上具有默认的5.1版本的PowerShell并且脚本模块路径没有被自定义过,这个路径就总是可用的,各个版本的PowerShell都会查找每一个可用的脚本模块路径,这些路径可以通过命令$Env:PSModulePath -split ';'列出

  5. 结合自己的编程环境,通过在PowerShell脚本中结合Python脚本或者DLL,或许可以更快速的包装出一些更复杂更高效的命令

参考内容

Script modules - PowerShell

本文由作者按照 CC BY 4.0 进行授权