Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion .github/workflows/powershell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ on:
- 'Public/**'
- 'Private/**'
- 'Tests/**'
- 'scripts/**'
- 'docs/**'
- 'README.md'
- 'CHANGELOG.md'
- 'LICENSE'
- 'IT-ToolBox.psd1'
- 'IT-ToolBox.psm1'
- '.github/workflows/powershell.yml'
Expand All @@ -15,6 +20,11 @@ on:
- 'Public/**'
- 'Private/**'
- 'Tests/**'
- 'scripts/**'
- 'docs/**'
- 'README.md'
- 'CHANGELOG.md'
- 'LICENSE'
- 'IT-ToolBox.psd1'
- 'IT-ToolBox.psm1'
- '.github/workflows/powershell.yml'
Expand Down Expand Up @@ -47,7 +57,7 @@ jobs:
if ($PSVersionTable.PSVersion -lt [version]'7.4') {
throw 'PowerShell 7.4 or later is required.'
}
foreach ($file in Get-ChildItem ./Public, ./Private, ./Tests -Filter *.ps1 -Recurse) {
foreach ($file in Get-ChildItem ./Public, ./Private, ./Tests, ./scripts -Filter *.ps1 -Recurse) {
$tokens = $null
$parseErrors = $null
$null = [System.Management.Automation.Language.Parser]::ParseFile(
Expand Down Expand Up @@ -84,3 +94,6 @@ jobs:
$configuration.Run.Exit = $true
$configuration.Output.Verbosity = 'Detailed'
Invoke-Pester -Configuration $configuration

- name: Build supported module archive
run: ./scripts/Build-Module.ps1
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@
*.Package.ps1
IT-ToolBox.psproj
CustomMenu.inf*tempPoint*

# Local build output and macOS metadata
/artifacts/
.DS_Store
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## Supported module packaging (unreleased)

- Add a local ZIP builder with conventional IT-ToolBox/version module layout.
- Package supported code and documentation only; exclude legacy and development files.
- Validate staged syntax and manifest; refuse to overwrite existing archives.
- Add real archive-content and isolated import/logger tests to the three-platform CI.
- Document preview installation; retain alpha1 metadata without publishing or signing.

## Integration boundary review (unreleased)

- Remove the obsolete Close-AzureSession helper and document service-specific authentication ownership.
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Redaction is opt-in and does not guarantee detection of every secret.

- SCP and GnuPG wrappers and bundled WinSCP binaries are removed. Separate modules
will own file transfer and OpenPGP; no replacement is bundled here.
- `Legacy/` retains historical string encryption and AzureAD helpers for reference.
- `Legacy/` retains historical string encryption helpers for migration reference.
- All commands formerly retained in `Staging/v3/` now have supported implementations.
Its README records the migration; historical service integrations remain excluded.
- Only the twenty-nine listed commands are exported. Private helpers, variables and aliases
Expand All @@ -78,6 +78,12 @@ Redaction is opt-in and does not guarantee detection of every secret.

## Tests and CI

For a clean installable preview archive, run `./scripts/Build-Module.ps1` in
PowerShell after running the tests. The archive includes supported module code
and documentation, excluding Legacy, Staging and development files. See
[build and installation instructions](./docs/Packaging.md). Nothing is published
or installed automatically.

```powershell
Install-Module Pester -RequiredVersion 5.7.1 -Scope CurrentUser
Invoke-Pester ./Tests
Expand Down
5 changes: 2 additions & 3 deletions Staging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
There are no executable candidates in this directory. All former v3 utility
candidates have supported implementations in Public/.

Close-AzureSession is archived in Legacy/ because it uses AzureAD and calls the
absent Test-AzureSession helper. The global certificate-validation bypass snippet
has been removed; its historical source remains available in Git history.
Close-AzureSession and the global certificate-validation bypass snippet have been
removed. Their historical source remains available in Git history.

Future candidates belong on a feature branch with tests before entering Public/.
Staging and Legacy are never loaded by the module. See
Expand Down
65 changes: 65 additions & 0 deletions Tests/Packaging.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
BeforeAll {
$sourceRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$buildScript = Join-Path $sourceRoot 'scripts/Build-Module.ps1'
$package = & $buildScript -OutputDirectory (Join-Path $TestDrive 'output [literal]')
$extractRoot = Join-Path $TestDrive 'extracted'
[IO.Compression.ZipFile]::ExtractToDirectory($package.ArchivePath, $extractRoot)
$moduleRoot = Join-Path $extractRoot 'IT-ToolBox/3.0.0'
}

Describe 'Installable module packaging' {
It 'uses the manifest preview version and conventional module layout' {
$package.Version | Should -Be '3.0.0-alpha1'
[IO.Path]::GetFileName($package.ArchivePath) | Should -Be 'IT-ToolBox-3.0.0-alpha1.zip'
Test-ModuleManifest (Join-Path $moduleRoot 'IT-ToolBox.psd1') -ErrorAction Stop | Should -Not -BeNullOrEmpty
Test-Path -LiteralPath (Join-Path $moduleRoot 'LICENSE') | Should -BeTrue
}

It 'ships only supported code and documentation, excluding development and legacy files' {
foreach ($directory in @('Legacy', 'Staging', 'Tests', 'scripts', '.git', '.github', 'artifacts')) {
Test-Path -LiteralPath (Join-Path $moduleRoot $directory) | Should -BeFalse
}
$files = @(Get-ChildItem -LiteralPath $moduleRoot -File -Recurse -Force)
$files.Count | Should -Be $package.FileCount
foreach ($file in $files) {
$relative = [IO.Path]::GetRelativePath($moduleRoot, $file.FullName).Replace('\', '/')
$relative | Should -Match '^(IT-ToolBox\.psd1|IT-ToolBox\.psm1|LICENSE|README\.md|CHANGELOG\.md|(Public|Private)/[^/]+\.ps1|docs/[^/]+\.md)$'
$original = Join-Path $sourceRoot $relative
(Get-FileHash -LiteralPath $file.FullName).Hash | Should -Be (Get-FileHash -LiteralPath $original).Hash
}
}

It 'imports the extracted module by name and exercises logging in a fresh process' {
$escapedRoot = $extractRoot.Replace("'", "''")
$code = @"
`$ErrorActionPreference = 'Stop'
`$env:PSModulePath = '$escapedRoot' + [IO.Path]::PathSeparator + `$env:PSModulePath
`$module = Import-Module IT-ToolBox -RequiredVersion 3.0.0 -Force -PassThru
New-LogEntry -LogMessage 'Packaged logger' -BufferOnly
[pscustomobject]@{
ExportCount = `$module.ExportedFunctions.Count
ModuleBase = `$module.ModuleBase
Buffer = @(New-LogEntry -GetBuffer)
} | ConvertTo-Json -Compress
"@
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($code))
$executable = Join-Path $PSHOME $(if ($IsWindows) { 'pwsh.exe' } else { 'pwsh' })
$output = & $executable -NoProfile -NonInteractive -EncodedCommand $encoded
$LASTEXITCODE | Should -Be 0
$result = $output | ConvertFrom-Json
$result.ModuleBase | Should -Be $moduleRoot
$result.ExportCount | Should -Be 29
$result.Buffer.Count | Should -Be 1
$result.Buffer[0] | Should -Match '\[INFO\]: Packaged logger$'
}

It 'refuses to overwrite an existing archive without changing its contents' {
$hash = (Get-FileHash -LiteralPath $package.ArchivePath).Hash
{ & $buildScript -OutputDirectory ([IO.Path]::GetDirectoryName($package.ArchivePath)) } | Should -Throw '*already exists*'
(Get-FileHash -LiteralPath $package.ArchivePath).Hash | Should -Be $hash
}

It 'rejects non-filesystem output providers' {
{ & $buildScript -OutputDirectory 'Env:ITToolBoxPackage' } | Should -Throw '*filesystem*'
}
}
37 changes: 37 additions & 0 deletions docs/Packaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Build and install the modernization preview

Run from the repository root using PowerShell 7.4 or later:

```powershell
Invoke-Pester ./Tests
./scripts/Build-Module.ps1
```

The script creates artifacts/IT-ToolBox-3.0.0-alpha1.zip and returns its path,
version and file count. It takes the version from the manifest without modifying
it. Use -OutputDirectory to choose another local destination. An existing archive
causes a terminating error; use a different destination for another build.

The ZIP contains IT-ToolBox/3.0.0/ with the manifest, loader, Public and Private
implementation files, license, README, changelog and Markdown documents in docs/.
Tests, build scripts, Legacy, Staging, Git metadata and external binaries are
excluded. Historical encryption migration requires the original repository source
in a separate session; the old decoder is not distributed in this archive.

Extract into a new directory, then verify or import the extracted manifest:

```powershell
Expand-Archive -LiteralPath './artifacts/IT-ToolBox-3.0.0-alpha1.zip' -DestinationPath './preview-install'
Import-Module './preview-install/IT-ToolBox/3.0.0/IT-ToolBox.psd1' -Force
Get-Command -Module IT-ToolBox
```

For discovery by name, put the extracted IT-ToolBox directory under a directory
listed in $env:PSModulePath. Review existing installations before copying there;
this script does not install or replace a module for you.

CI runs archive-content and fresh-process import tests on Windows, Linux and macOS,
then builds the archive locally. No publishing, release creation or signing is
performed. ZIP timestamps are not normalized, so identical source builds need not
have identical archive hashes. This remains an alpha preview; the package process
does not establish live AD, remote CIM or service authentication compatibility.
76 changes: 76 additions & 0 deletions scripts/Build-Module.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#Requires -Version 7.4
<#
.SYNOPSIS
Build a local installable ZIP from supported module files only.
.DESCRIPTION
Does not publish, install, sign or change the module version. Existing archives
are never overwritten. Build staging is removed even when packaging fails.
#>
[CmdletBinding()]
param (
[ValidateNotNullOrEmpty()]
[string]$OutputDirectory = (Join-Path $PSScriptRoot '../artifacts')
)

$ErrorActionPreference = 'Stop'
$sourceRoot = Split-Path $PSScriptRoot -Parent
$manifest = Import-PowerShellDataFile (Join-Path $sourceRoot 'IT-ToolBox.psd1')
$version = [string]$manifest.ModuleVersion
$prerelease = [string]$manifest.PrivateData.PSData.Prerelease
if ($prerelease) { $version += "-$prerelease" }
if ($version -notmatch '^\d+\.\d+\.\d+(\.\d+)?(-[A-Za-z0-9]+)?$') {
throw 'Manifest version cannot be used as an archive filename.'
}
$provider = $null
$drive = $null
$outputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
$OutputDirectory, [ref]$provider, [ref]$drive
)
if ($provider.Name -ne 'FileSystem') { throw 'OutputDirectory must be a filesystem path.' }
$archivePath = Join-Path $outputPath "IT-ToolBox-$version.zip"
if (Test-Path -LiteralPath $archivePath) { throw "Archive already exists: $archivePath" }

$stageRoot = Join-Path ([IO.Path]::GetTempPath()) ('ITToolBox-build-' + [guid]::NewGuid())
$packageRoot = Join-Path $stageRoot 'package'
$moduleRoot = Join-Path $packageRoot (Join-Path 'IT-ToolBox' ([string]$manifest.ModuleVersion))
try {
$null = [IO.Directory]::CreateDirectory($moduleRoot)
$files = @(foreach ($name in @('IT-ToolBox.psd1', 'IT-ToolBox.psm1', 'LICENSE', 'README.md', 'CHANGELOG.md')) {
Get-Item -LiteralPath (Join-Path $sourceRoot $name)
})
foreach ($directory in @('Public', 'Private', 'docs')) {
$filter = if ($directory -eq 'docs') { '*.md' } else { '*.ps1' }
$files += @(Get-ChildItem -LiteralPath (Join-Path $sourceRoot $directory) -Filter $filter -File | Sort-Object Name)
}
foreach ($file in $files) {
if ($file.Attributes -band [IO.FileAttributes]::ReparsePoint) {
throw "Packaging symbolic links is not supported: $($file.FullName)"
}
$relative = [IO.Path]::GetRelativePath($sourceRoot, $file.FullName)
$destination = Join-Path $moduleRoot $relative
$null = [IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination))
[IO.File]::Copy($file.FullName, $destination, $false)
if ($file.Extension -in @('.ps1', '.psm1', '.psd1')) {
$tokens = $null
$parseErrors = $null
$null = [Management.Automation.Language.Parser]::ParseFile($destination, [ref]$tokens, [ref]$parseErrors)
if ($parseErrors.Count) { throw ($parseErrors | Out-String) }
}
}
$null = Test-ModuleManifest -Path (Join-Path $moduleRoot 'IT-ToolBox.psd1') -ErrorAction Stop
$temporaryArchive = Join-Path $stageRoot 'module.zip'
[IO.Compression.ZipFile]::CreateFromDirectory($packageRoot, $temporaryArchive)
$null = [IO.Directory]::CreateDirectory($outputPath)
[IO.File]::Move($temporaryArchive, $archivePath, $false)
[pscustomobject]@{
ArchivePath = $archivePath
Version = $version
ModuleVersion = [string]$manifest.ModuleVersion
FileCount = $files.Count
}
}
finally {
if ([IO.Directory]::Exists($stageRoot)) {
[IO.Directory]::Delete($stageRoot, $true)
}
}
Loading