经典背景介绍:这一切的背景还是因为最近实习离职了之后用回了windows开发,windows上面的tree命令感觉实在是不好用,因为不能控制
-L深度,几番求索之后没找到一个喜欢的命令,最后决定是自己写一个工具命令,经过调试之后觉得还不错挺好用的,然后我就在想能不能跟npm包一样发布到大家都可用的那种社区平台上,然后我找到了powershell gallery.
创建Powershell Gallery账户
点此创建账户👉:PowerShellGallery.com
看到这里就说明创建完成:
然后点击用户名选择API Keys,创建一个新的api-key:
然后get到key:
使用PSScriptAnalyzer
这是微软官方文档建议完成的优先级第一的步骤: 使用PSScriptAnalyzer识别PowerShell代码中最常见的问题,并且通常会提供有关如何解决问题的建议。
1. 安装 PSScriptAnalyzer
Install-Module PSScriptAnalyzer -Scope CurrentUser
查看版本:
Get-Module PSScriptAnalyzer -ListAvail
2. 对脚本进行扫描
比如这是我的脚本路径:
D:\TreeView-PS\TreeView.ps1
Invoke-ScriptAnalyzer -Path "D:\TreeView-PS\TreeView.ps1"
会输出类似:
RuleName Severity Line Message
-------- -------- ---- -------
PSAvoidUsingWriteHost Warning 33 FileTree.cmd uses Write-Host; use Write-Output instead.
PSUseDeclaredVarsMoreThanAssignments Warning ...
...
参考这里的问题把问题都解决。直到使用扫描命令无输出。
3. 对整个模块扫描
如果你组织成模块结构:
TreeView/
TreeView.psd1
TreeView.psm1
运行:
Invoke-ScriptAnalyzer -Path "TreeView" -Recurse
4. 自动修复(如果可能)
Invoke-ScriptAnalyzer -Path .\TreeView.ps1 -Fix
不是所有规则都能自动修,但能自动 fix 的就会帮你处理。
5. 发布前自动检查
可以在发布前先跑:
Invoke-ScriptAnalyzer -Path . -Recurse -Severity Warning,Error
如果未来用 GitHub Actions 发布模块,可以加:
.github/workflows/lint.yml:
name: Lint PowerShell
on: [push, pull_request]
jobs:
analyze:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Install PSScriptAnalyzer
run: Install-Module PSScriptAnalyzer -Force -Scope CurrentUser
- name: Run Analyzer
run: Invoke-ScriptAnalyzer -Path . -Recurse -Severity Warning,Error
使用Pester进行单元测试
安装现代版本的Pester
Windows 10 / Windows Server 2016 及更高版本预装了 Pester 3.4.0 版本。此内置版本无法使用简单的Update-Modulecmdlet 进行更新。您需要执行并行安装才能开始使用最新版本的 Pester。
内置版本由微软签名,而较新的版本由社区维护并使用不同的证书签名,这Install-Module有时会导致出现错误,要求我们接受新的发布者证书。
首先安装Pester 5.x 现代版本,由于系统里预置了不再更新的3.x, 所以安装新版应当使用参数跳过系统检查:
Install-Module -Name Pester -Force -Scope CurrentUser -SkipPublisherCheck
安装完成之后可以用此命令验证版本:
Get-Module -ListAvailable Pester
看到有了5.7.1就说明就绪了,然后最好是卸载已加载的Pester3,否则不会用5.x版本,运行下面命令卸载靠前的版本并手动加载新版(5.0.0以后的版本):
Remove-Module Pester -Force -ErrorAction SilentlyContinue
Import-Module Pester -MinimumVersion 5.0.0 -Force
现在再看一下当前模块版本:
(Get-Module Pester).Version
PS D:\TreeView-PS> (Get-Module Pester).Version
Major Minor Build Revision
----- ----- ----- --------
5 7 1 -1
执行测试
然后准备好测试脚本,项目目录如:
D:\TreeView-PS
├── 📁 Private # 私有方法,不导出
│ ├── 📄 AnsiColors.ps1
│ ├── 📄 Get-Color.ps1
│ ├── 📄 Get-Icon.ps1
│ ├── 📄 Show-Tree.ps1
│ └── 📄 Write-OutOrHost.ps1
├── 📁 Public
│ └── 📄 TreeView.ps1 # 主函数
├── 📁 Tests
│ └── 📄 TreeView.Tests.ps1
├── 📄 TreeView.psd1
└── 📄 TreeView.psm1
在仓库根目录执行:
# 运行目录下所有测试
Invoke-Pester -Path .\Tests -Output Detailed
效果如下:
一个测试内部模块未导出的function的方法
在真实模块设计中,不推荐导出内部函数(Private Functions)。 例如 Get-Icon / Get-Color 这类内部辅助函数不应该成为模块的对外 API。
如果你的测试脚本里试图访问模块内的函数但却没有导出,可能会出现如下报错:
CommandNotFoundException: 无法将“Get-Color”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写,如果包括路径,请确保路径正确,然后再试一次。
可以采用这个办法--Dot Source:
BeforeAll {
# 导入模块
Import-Module "$PSScriptRoot\..\TreeView.psm1" -Force
# dot-source 私有函数,仅测试用
. "$PSScriptRoot\..\Private\Get-Icon.ps1"
. "$PSScriptRoot\..\Private\Get-Color.ps1"
}
测试覆盖率报告
如需同时生成覆盖率,可以参考这个文档:Generating Code Coverage Metrics | Pester
贴一个例子:
# Create a Pester configuration object using `New-PesterConfiguration`
$config = New-PesterConfiguration
# Set the test path to specify where your tests are located. In this example, we set the path to the current directory. Pester will look into all subdirectories.
$config.Run.Path = ".\Tests"
# Enable Code Coverage
$config.CodeCoverage.Enabled = $true
$config.CodeCoverage.Path = "."
# Run Pester tests using the configuration you've created
Invoke-Pester -Configuration $config
我查看了一下为自己的覆盖率:
Starting discovery in 1 files.
Discovery found 10 tests in 88ms.
Starting code coverage.
Running tests.
[+] TreeView.Tests.ps1 995ms (543ms|369ms)
Tests completed in 1.01s
Tests Passed: 10, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
Processing code coverage result.
Covered 37.18% / 75%. 234 analyzed Commands in 10 Files.
可以看到我这个分支覆盖率还很低,不过这并不是本博客的重点,至于提高覆盖率可以通过其他的手段来实现。
推送到Powershell Gallery
注册 PowerShell Gallery API Key
还记得我们之前是有得到了一个api-key的,你应当已经将这个api-key给记在某处。
生成psd1(打包)
使用下列命令:
New-ModuleManifest -Path ./TreeView.psd1 `
-RootModule TreeView.psm1 `
-ModuleVersion "1.0.0" `
-Author "你的名字" `
-Description "A tree command for Windows with icons and depth support."
生成的psd1内容像这样:
@{
RootModule = 'TreeView.psm1'
ModuleVersion = '1.0.0'
GUID = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
Author = '你'
CompanyName = '你'
Description = 'Linux tree-like directory viewer with icon mode.'
PowerShellVersion = '5.1'
FunctionsToExport = @('TreeView')
}
给他改吧改吧,把信息填一填.
你可以选择手动发布(简单好操作)
Publish-Module -Path .\ -Repository PSGallery -NuGetApiKey '<your-api-key>'
Publish-Module命令详解
1. Publish-Module
PowerShell 内置的发布命令,用来把一个模块上传到 PowerShell Gallery。
一般你的模块目录结构如下:
TreeView-PS/
TreeView.psd1
TreeView.psm1
Public/
Private/
只要有 .psd1 就可以发布。
2. -Path .
代表“当前路径就是我要发布的模块目录”。
你的执行目录一般就是模块根目录,所以写 . 就够了。
如果放模块在子目录,可能要写成:-Path ./src/TreeView
3. -Repository PSGallery
指定发布目标仓库,就是官方 PowerShell Gallery。
你也可以发布到私有 NuGet 服务器,但你现在用的是默认的公共仓库。
4. -NuGetApiKey <your-api-key>
这是发布到 Gallery 的 登录凭证。
PowerShell Gallery 使用 NuGet API Key 鉴权,也就是我们在Powershell Gallery申请的key。
所以整句命令的意思是:
使用 GitHub Actions 自动将当前目录的 PowerShell 模块上传到 PowerShell Gallery, 使用存放在环境变量中的 NuGet API Key 认证,并发布。
使用GitHub Actions实现CI/CD(推荐)
CI/CD 代表持续集成 (Continuous Integration)和持续交付/部署 (Continuous Delivery/Deployment),是一种软件开发方法,通过自动化构建、测试和部署流程,实现更快、更频繁地向客户交付高质量的软件。持续集成 (CI) 侧重于开发人员频繁将代码集成到共享存储库并进行自动化测试;而持续交付/部署 (CD) 则在此基础上,自动化将变更交付到生产环境的过程。
若要使 GitHub 在存储库中发现任何 GitHub Actions 工作流,必须将工作流文件保存在名为 .github/workflows 的目录中。
你可以为工作流文件指定所需的任何名称,但必须使用 .yml 或 .yaml 作为文件扩展名。 YAML 通常是用于配置文件的标记语言。 -- Github官方文档
Github建仓
- 在Github上创建仓库
建一个仓库记住url,如:https://github.com/你的账号/<仓库名字>.git
- 本地初始化git并推送
git init # 如果还没初始化过
git add .
git commit -m "Initial commit"
git branch -M main # 保证主分支是 main
git remote add origin https://github.com/你的账号/<仓库名字>.git
git push -u origin main
使用Github Action
你需要先来到网页端,给你的仓库配置一个专用的 API Key。创建方法如图:
这个时候就把刚刚存的API Key拿出来就行,然后给他命名,以后用得上,我这里命名为:
PSGalleryApiKey
在项目根目录下创建./github/workflows/main.yml,github的教程提供了一个模板,下面以我的方法为例解释ci/cd 工作流文件:
name: PowerShell Module CI/CD
on:
push:
branches:
- main
pull_request:
jobs:
test-and-publish:
runs-on: windows-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup PowerShell
uses: actions/setup-powershell@v3
- name: Install Pester
run: Install-Module -Name Pester -Force -Scope CurrentUser
- name: Run Pester tests
run: |
Invoke-Pester -Path .\Tests -Output Detailed
- name: Update ModuleVersion
shell: pwsh
run: |
$psd1Path = ".\TreeView.psd1"
$psd1 = Get-Content $psd1Path
$newVersion = "1.0.$env:GITHUB_RUN_NUMBER"
$psd1 = $psd1 -replace "ModuleVersion\s*=\s*'.*?'", "ModuleVersion = '$newVersion'"
Set-Content $psd1Path $psd1
env:
GITHUB_RUN_NUMBER: ${{ github.run_number }}
- name: Publish to PSGallery
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
env:
<YourKey>: ${{ secrets.<YourKey> }}
run: |
Publish-Module -Path . -NuGetApiKey $env:<YourKey> -Repository PSGallery -Force
- checkout:拉取仓库代码
- setup PowerShell:确保 Windows runner 安装 PowerShell
- 安装 Pester:最新版本
- 运行单元测试:确保模块没有问题
- 发布模块:
- 只在 main 分支 push 时触发
- 使用仓库 Secrets 中的 PSGalleryApiKey
-Force会覆盖同版本模块,如果你想严格控制版本可以改成自动生成新版本
push后自动执行流程
如下图,找到正在执行的workflow:
等待执行成功或失败。
然后我们可以看到这里有一个失败是啥呢,是这个-f支路没有成功:
No valid module was found
Line |
2 | Publish-Module -Path . -NuGetApiKey $env:PSGalleryApiKey -Repository …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| The specified module with path '.' was not published because no valid module was found with that path.
这个报错是因为仓库无合法模块,注意合法的ps模块必须是这样的结构,你只能用子文件夹作为目录,不能用根:
<ModuleName>/
<ModuleName>.psd1
<ModuleName>.psm1
只要把Publish-Module -Path . -NuGetApiKey $env:PSGalleryApiKey -Repository PSGallery -Force改成Publish-Module -Path ./<结构正确的子文件夹> -NuGetApiKey $env:PSGalleryApiKey -Repository PSGallery -Force
经过坚持不懈的调试终于发布成功。
参考文档
ps!推荐一个可以用来快速获得md link的浏览器插件:Copy as Markdown
PowerShell 库发布指南和最佳做法 - PowerShell | Microsoft Learn
PowerShell - 如何在 Pester v5 中处理点源? - Stack Overflow