Skip to content

Conversation

@azurecloudkevin
Copy link

  • Add Invoke-* orchestration functions to all scripts
  • Apply guard pattern: $MyInvocation.InvocationName -ne '.'
  • Document pattern in scripts/README.md
  • Simplify test dot-sourcing (removes AST workaround)
  • Scripts: 12 files updated across security, linting, extension

Pull Request

Description

This PR standardizes the "direct invocation vs dot-sourced" guard pattern across all PowerShell scripts in the repository. This improves testability by allowing scripts to be dot-sourced without executing their main logic, enabling Pester tests to directly call individual functions.

Related Issue(s)

Closes issue 325

Type of Change

Select all that apply:

Code & Documentation:

  • Bug fix (non-breaking change fixing an issue)
  • New feature (non-breaking change adding functionality)
  • Breaking change (fix or feature causing existing functionality to change)
  • Documentation update

Infrastructure & Configuration:

  • GitHub Actions workflow
  • Linting configuration (markdown, PowerShell, etc.)
  • Security configuration
  • DevContainer configuration
  • Dependency update

AI Artifacts:

  • Reviewed contribution with prompt-builder agent and addressed all feedback
  • Copilot instructions (.github/instructions/*.instructions.md)
  • Copilot prompt (.github/prompts/*.prompt.md)
  • Copilot agent (.github/agents/*.agent.md)

Other:

  • Script/automation (.ps1, .sh, .py)
  • Other (please describe):

Testing

  • PSScriptAnalyzer: 32/33 files passed (1 warning about unused parameter - pre-existing issue)
  • Pester Tests: tests invoked successfully
  • Dot-sourcing verification: Functions load without executing main logic
  • Direct invocation verification: Scripts execute correctly

Checklist

Required Checks

  • Documentation is updated (if applicable)
  • Files follow existing naming conventions
  • Changes are backwards compatible (if applicable)
  • Tests added for new functionality (if applicable)

AI Artifact Contributions

  • Used /prompt-analyze to review contribution
  • Addressed all feedback from prompt-builder review
  • Verified contribution follows common standards and type-specific requirements

Required Automated Checks

The following validation commands must pass before merging:

  • Markdown linting: npm run lint:md
  • Spell checking: npm run spell-check
  • Frontmatter validation: npm run lint:frontmatter
  • Link validation: npm run lint:md-links
  • PowerShell analysis: npm run lint:ps

Security Considerations

  • This PR does not contain any sensitive or NDA information
  • Any new dependencies have been reviewed for security issues
  • Security-related scripts follow the principle of least privilege

Additional Notes

- Add Invoke-* orchestration functions to all scripts
- Apply guard pattern: $MyInvocation.InvocationName -ne '.'
- Document pattern in scripts/README.md
- Simplify test dot-sourcing (removes AST workaround)
- Scripts: 12 files updated across security, linting, extension
@azurecloudkevin azurecloudkevin requested a review from a team as a code owner January 29, 2026 15:56
Copilot AI review requested due to automatic review settings January 29, 2026 15:56
@github-actions
Copy link

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@codecov-commenter
Copy link

Codecov Report

❌ Patch coverage is 12.85141% with 217 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.87%. Comparing base (f1d3ac6) to head (7f3a5b5).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
scripts/extension/Package-Extension.ps1 0.76% 129 Missing ⚠️
scripts/security/Test-DependencyPinning.ps1 2.56% 38 Missing ⚠️
scripts/extension/Prepare-Extension.ps1 5.88% 16 Missing ⚠️
scripts/linting/Invoke-LinkLanguageCheck.ps1 0.00% 12 Missing ⚠️
scripts/linting/Markdown-Link-Check.ps1 0.00% 8 Missing ⚠️
scripts/security/Test-SHAStaleness.ps1 16.66% 5 Missing ⚠️
scripts/security/Update-ActionSHAPinning.ps1 0.00% 5 Missing ⚠️
scripts/linting/Link-Lang-Check.ps1 0.00% 4 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #346      +/-   ##
==========================================
+ Coverage   41.08%   42.87%   +1.79%     
==========================================
  Files          15       15              
  Lines        2870     2904      +34     
==========================================
+ Hits         1179     1245      +66     
+ Misses       1691     1659      -32     
Flag Coverage Δ
pester 42.87% <12.85%> (+1.79%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
scripts/linting/Invoke-PSScriptAnalyzer.ps1 93.33% <100.00%> (+0.27%) ⬆️
scripts/linting/Invoke-YamlLint.ps1 94.59% <100.00%> (+0.22%) ⬆️
scripts/linting/Link-Lang-Check.ps1 0.00% <0.00%> (ø)
scripts/security/Test-SHAStaleness.ps1 16.04% <16.66%> (+16.04%) ⬆️
scripts/security/Update-ActionSHAPinning.ps1 0.00% <0.00%> (ø)
scripts/linting/Markdown-Link-Check.ps1 0.00% <0.00%> (ø)
scripts/linting/Invoke-LinkLanguageCheck.ps1 0.00% <0.00%> (ø)
scripts/extension/Prepare-Extension.ps1 39.68% <5.88%> (-0.32%) ⬇️
scripts/security/Test-DependencyPinning.ps1 54.06% <2.56%> (-1.46%) ⬇️
scripts/extension/Package-Extension.ps1 26.19% <0.76%> (-0.26%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR standardizes a PowerShell script entry-point pattern across the repo, adding Invoke-* orchestration functions and a guard that distinguishes direct invocation from dot-sourcing, plus documentation for the pattern. The changes improve testability, make exit-code behavior more consistent, and clean up the SHA staleness tests by removing the AST-based workaround.

Changes:

  • Added Invoke-* orchestration functions and $MyInvocation.InvocationName -ne '.' guards to security, linting, and extension scripts, converting in-script exit calls into integer return codes propagated by the guard.
  • Simplified Test-SHAStaleness Pester tests by switching from AST-based function extraction to simple dot-sourcing now that scripts can be safely imported without executing main logic.
  • Documented the standard entry-point pattern and naming conventions for scripts in scripts/README.md.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
scripts/tests/security/Test-SHAStaleness.Tests.ps1 Updates tests to dot-source Test-SHAStaleness.ps1 directly, relying on the new guard pattern to prevent main execution during testing.
scripts/security/Update-ActionSHAPinning.ps1 Introduces Invoke-ActionSHAUpdate as the main orchestration function, returns integer exit codes, and guards main execution behind the $MyInvocation.InvocationName -ne '.' check.
scripts/security/Test-SHAStaleness.ps1 Adds Invoke-SHAStalenessTest orchestration, converts inline exits to integer returns, and uses a guarded main section that calls the orchestrator and exits with its code.
scripts/security/Test-DependencyPinning.ps1 Extracts the main dependency-pinning logic into Invoke-DependencyPinningTest, replaces direct exits with returns, and adds a guard so dot-sourcing no longer fails the script.
scripts/linting/Markdown-Link-Check.ps1 Wraps existing markdown link checking logic in Invoke-MarkdownLinkCheck with integer return codes and a guarded main execution path.
scripts/linting/Link-Lang-Check.ps1 Adds Invoke-LinkLanguageCheck as the orchestration function, returning 0 consistently while the top-level guard handles exit behavior.
scripts/linting/Invoke-YamlLint.ps1 Refactors main YAML linting logic into Invoke-YamlLintValidation with structured return codes and creates a guarded main section that exits with the function’s result.
scripts/linting/Invoke-PSScriptAnalyzer.ps1 Moves PSScriptAnalyzer execution into Invoke-PSScriptAnalysis, returning an integer summary result and wiring it through a guarded main block.
scripts/linting/Invoke-LinkLanguageCheck.ps1 Wraps Link-Lang-Check.ps1 with Invoke-LinkLanguageCheckWrapper, returning explicit exit codes and guarding the main entry for dot-sourcing scenarios.
scripts/extension/Prepare-Extension.ps1 Extracts the extension preparation workflow into Invoke-ExtensionPreparation, uses $PSScriptRoot for path resolution, and returns integer codes that the guarded main section converts into process exits.
scripts/extension/Package-Extension.ps1 Refactors packaging logic into Invoke-ExtensionPackaging, ensures all paths return explicit exit codes, and adds a guard-based main entry that calls the orchestrator and exits accordingly.
scripts/README.md Documents the new script entry-point pattern, including structure, guard usage, and naming conventions, with a minor inconsistency in the naming table noted separately.

| `Package-*.ps1` | `Invoke-*Packaging` |
| `Prepare-*.ps1` | `Invoke-*Preparation` |
| `Update-*.ps1` | `Invoke-*Update` |
| `Validate-*.ps1` | `Test-*Validation` |
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the naming convention table, the last row lists Test-*Validation as the "Invoke Function" for Validate-*.ps1 scripts, which is inconsistent with the preceding description that orchestration entrypoints follow the Invoke-* pattern and with new functions like Invoke-YamlLintValidation. To avoid confusion for future contributors, this row should be updated to use an Invoke-*Validation name to match the documented pattern and actual script implementations.

Suggested change
| `Validate-*.ps1` | `Test-*Validation` |
| `Validate-*.ps1` | `Invoke-*Validation` |

Copilot uses AI. Check for mistakes.
@azurecloudkevin
Copy link
Author

@azurecloudkevin please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.

@microsoft-github-policy-service agree [company="Microsoft"]

I am making Submissions in the course of work for my employer (or my employer has intellectual property rights in my Submissions by contract or applicable law). I have permission from my employer to make Submissions and enter into this Agreement on behalf of my employer. By signing below, the defined term “You” includes me and my employer.

@microsoft-github-policy-service agree company="Microsoft"

Contributor License Agreement

Add comprehensive test coverage improvements:

P1 - Entry point tests:
- Update-ActionSHAPinning: Invoke-ActionSHAUpdate tests
- Markdown-Link-Check: Invoke-MarkdownLinkCheck tests

P2 - Error path and boundary tests:
- Package-Extension: null manifest, empty hashtable, edge cases
- Prepare-Extension: malformed YAML, missing frontmatter handling
- Test-DependencyPinning: empty inputs, SHA format validation
- Test-SHAStaleness: version comparison edge cases, token validation
- Link-Lang-Check: fix flaky test

Coverage: 36.17% -> 53.71% (+17.54pp)
Tests: 742 -> 766 (+24 tests, 760 passing)
Copilot AI review requested due to automatic review settings February 2, 2026 17:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

.DESCRIPTION
Coordinates staleness checking for GitHub Actions and tools, then outputs results.
.PARAMETER OutputFormat
Output format: 'json', 'azdo', 'github', or 'console'.
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.PARAMETER OutputFormat documentation here only lists json, azdo, github, and console, but the parameter's ValidateSet also accepts BuildWarning and Summary, so the comment is out of date with the actual supported values. Please update the parameter description to reflect the full set of allowed formats so that callers using this function directly do not miss the additional options.

Suggested change
Output format: 'json', 'azdo', 'github', or 'console'.
Output format: 'json', 'azdo', 'github', 'console', 'BuildWarning', or 'Summary'.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +69
$repoRoot = git rev-parse --show-toplevel 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Error "Not in a git repository"
return 1
}

# Create logs directory if it doesn't exist
$logsDir = Join-Path $repoRoot "logs"
if (-not (Test-Path $logsDir)) {
New-Item -ItemType Directory -Path $logsDir -Force | Out-Null
}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic to resolve the git repository root and ensure a logs directory exists is now duplicated both in the top-level script (lines 17–28) and again inside Invoke-LinkLanguageCheckWrapper (lines 59–69). This duplication makes future changes to log location or git detection easy to forget in one place; consider centralizing this into a helper function or keeping it only in the wrapper so that behavior stays consistent and easier to maintain.

Copilot uses AI. Check for mistakes.
Add comprehensive tests for:
- Package-Extension.ps1: version validation, parameter handling
- Prepare-Extension.ps1: frontmatter parsing, discovery functions
- Invoke-LinkLanguageCheck.ps1: wrapper function, output handling
- Markdown-Link-Check.ps1: extended path handling, CLI scenarios

Fix zero-coverage issue in 4 test files by replacing AST parsing
with direct dot-sourcing for proper coverage tracking.

Coverage improvement: 53.71% -> 63.98% (+10.27pp)
@WilliamBerryiii WilliamBerryiii modified the milestones: v2.2.0, v2.3.0 Feb 5, 2026
- add phase 4 tests to Package-Extension.Tests.ps1 and Prepare-Extension.Tests.ps1
- add orchestration path tests to Invoke-LinkLanguageCheck.Tests.ps1
- add function tests to Invoke-PSScriptAnalyzer.Tests.ps1 and Invoke-YamlLint.Tests.ps1
- add error handling and fix mode tests to Link-Lang-Check.Tests.ps1
- add XML parsing and integration tests to Markdown-Link-Check.Tests.ps1

🧪 - Generated by Copilot
Copilot AI review requested due to automatic review settings February 11, 2026 21:13
@WilliamBerryiii
Copy link
Member

@azurecloudkevin - Since this PR fell pretty far behind main, I took your branch and re-fired it up as PR #477. I'm going to close this one out given the merge conflicts are pretty hairy. Can you give me eyes on the new PR?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.

Comment on lines +834 to 866
function Invoke-DependencyPinningTest {
<#
.SYNOPSIS
Main orchestration function for dependency pinning compliance analysis.
.DESCRIPTION
Coordinates the scanning and reporting of dependency pinning compliance.
.PARAMETER Path
Root path to scan for dependency files.
.PARAMETER Recursive
Scan recursively through subdirectories.
.PARAMETER Format
Output format for compliance report.
.PARAMETER OutputPath
Path where compliance results should be saved.
.PARAMETER FailOnUnpinned
Exit with error code if pinning violations are found.
.PARAMETER ExcludePaths
Comma-separated list of paths to exclude from scanning.
.PARAMETER IncludeTypes
Comma-separated list of dependency types to check.
.PARAMETER Threshold
Minimum compliance score percentage required for passing grade.
.PARAMETER Remediate
Generate remediation suggestions with specific SHA pins.
.OUTPUTS
System.Int32 - Exit code (0 for success, 1 for failure)
#>
[CmdletBinding()]
[OutputType([int])]
param(
[Parameter(Mandatory = $false)]
[string]$Path = ".",

Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new dot-sourcing approach, script-scope preference changes like $ErrorActionPreference = 'Stop' (currently set at top-level) will leak into the caller scope and can affect unrelated tests. Consider setting $ErrorActionPreference inside Invoke-DependencyPinningTest (or saving/restoring it) so importing functions does not mutate the caller environment.

Copilot uses AI. Check for mistakes.
Comment on lines +105 to +106
Invoke-MyOperation -InputPath $InputPath
exit 0
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README example exits with 0 unconditionally after calling Invoke-MyOperation, which hides failures and does not match the pattern used elsewhere in this PR (capture function return code, then exit $exitCode). Update the example to assign the orchestration function’s return value to $exitCode and exit with that value.

Suggested change
Invoke-MyOperation -InputPath $InputPath
exit 0
$exitCode = Invoke-MyOperation -InputPath $InputPath
exit $exitCode

Copilot uses AI. Check for mistakes.
Comment on lines +284 to +288
It 'Returns 1 when no markdown files found' {
$result = Invoke-MarkdownLinkCheck -Path $script:EmptyDir -ConfigPath $script:ConfigFile -ErrorAction SilentlyContinue 2>&1
# Function returns 1 when no files found
$true | Should -BeTrue
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests don’t assert the function’s behavior: they call Invoke-MarkdownLinkCheck and then only check $true | Should -BeTrue. This will pass even if the function returns the wrong exit code or throws. Assert on the returned exit code (and/or expected error message) so failures are caught.

Copilot uses AI. Check for mistakes.
Comment on lines 503 to 516
It 'Returns null on API error without throwing' {
Mock Invoke-RestMethod {
throw [System.Exception]::new('Network error')
# Create a WebException-like exception with Response property
$webException = [System.Net.WebException]::new('Network error')
Mock Invoke-GitHubAPIWithRetry {
throw $webException
}
Mock Test-GitHubToken {
return @{ Valid = $false; Message = 'No token' }
}
Mock Write-SecurityLog { }

# Function should handle error gracefully and return null
$result = Get-LatestCommitSHA -Owner 'actions' -Repo 'checkout' -Branch 'main'
$result | Should -BeNullOrEmpty
{ $result = Get-LatestCommitSHA -Owner 'actions' -Repo 'checkout' -Branch 'main' } | Should -Not -Throw
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test no longer verifies the documented behavior (“returns null on API error”): it only asserts the call does not throw, but never checks $result is $null/empty. Add an assertion on $result after invocation so regressions in the return value are caught.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +66
function Invoke-YamlLintValidation {
<#
.SYNOPSIS
Main orchestration function for YAML lint validation.
.DESCRIPTION
Coordinates the validation of GitHub Actions workflow files using actionlint.
.PARAMETER ChangedFilesOnly
Validate only changed YAML files.
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new guard pattern only prevents the bottom main block from running when dot-sourced, but this script still executes top-level logic (Write-Host + Get-Command actionlint + exit 1) before Invoke-YamlLintValidation is defined. Dot-sourcing in tests or other scripts will still terminate the session if actionlint is missing. Move the actionlint availability check (and any other side-effecting top-level code) inside Invoke-YamlLintValidation or inside the direct-invocation guard so dot-sourcing is safe.

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +66
function Invoke-PSScriptAnalysis {
<#
.SYNOPSIS
Main orchestration function for PSScriptAnalyzer validation.
.DESCRIPTION
Coordinates the analysis of PowerShell files using PSScriptAnalyzer.
.PARAMETER FilesToAnalyze
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script still runs significant side effects at top-level (module installation, file discovery, and exit 0 when no files are found) before the new Invoke-PSScriptAnalysis guard. If the file is dot-sourced for testing, it can unexpectedly install modules or terminate the calling session. Move the top-level execution logic into Invoke-PSScriptAnalysis, and ensure any exit calls occur only inside the direct-invocation guard.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +41
function Invoke-LinkLanguageCheckWrapper {
<#
.SYNOPSIS
Main orchestration function for link language check wrapper.
.DESCRIPTION
Coordinates the link language check with GitHub Actions integration.
.PARAMETER ExcludePaths
Paths to exclude from checking.
.OUTPUTS
System.Int32 - Exit code (0 for success, 1 for issues found)
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Invoke-LinkLanguageCheckWrapper duplicates repo-root detection and logs directory creation that already happens at the script top-level. More importantly, the script currently does git rev-parse + exit 1 at top-level, which still runs when dot-sourced, defeating the guard pattern and making tests brittle. Move repo-root/logs initialization into Invoke-LinkLanguageCheckWrapper (or the direct-invocation guard) and avoid any top-level exit so dot-sourcing only loads functions.

Copilot uses AI. Check for mistakes.
Comment on lines +870 to +894
function Invoke-SHAStalenessTest {
<#
.SYNOPSIS
Main orchestration function for SHA staleness monitoring.
.DESCRIPTION
Coordinates staleness checking for GitHub Actions and tools, then outputs results.
.PARAMETER OutputFormat
Output format: 'json', 'azdo', 'github', or 'console'.
.PARAMETER MaxAge
Maximum age in days before considering a dependency stale.
.PARAMETER LogPath
Path for security logging.
.PARAMETER OutputPath
Path to write structured output file.
.PARAMETER FailOnStale
Exit with code 1 if stale dependencies are found.
.PARAMETER GraphQLBatchSize
Batch size for GraphQL queries.
.OUTPUTS
System.Int32 - Exit code (0 for success, 1 for failure/stale dependencies with FailOnStale)
#>
[CmdletBinding()]
[OutputType([int])]
param(
[Parameter(Mandatory = $false)]
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because tests now dot-source this script, the top-level side effects (for example, creating the log directory before any function calls) will run during import and can write to the working directory unexpectedly. Consider moving the log directory creation and any other setup work into Invoke-SHAStalenessTest so dot-sourcing only defines functions and state initialization is explicit.

Copilot uses AI. Check for mistakes.
Comment on lines +817 to +836
function Invoke-ActionSHAUpdate {
<#
.SYNOPSIS
Main orchestration function for GitHub Actions SHA pinning updates.
.DESCRIPTION
Coordinates the scanning and updating of GitHub Actions workflows with SHA pins.
.PARAMETER WorkflowPath
Path to the .github/workflows directory.
.PARAMETER OutputReport
Generate detailed report of changes and pinning status.
.PARAMETER OutputFormat
Output format for results.
.PARAMETER UpdateStale
Update already-pinned-but-stale GitHub Actions to their latest commit SHAs.
.OUTPUTS
System.Int32 - Exit code (0 for success, 1 for failure)
#>
[CmdletBinding(SupportsShouldProcess)]
[OutputType([int])]
param(
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that tests dot-source this script, Set-StrictMode -Version Latest (near the top of the file) will apply to the caller scope and can break Pester or any script that imports these functions. To keep dot-sourcing safe, move Set-StrictMode (and any other global preference changes) inside Invoke-ActionSHAUpdate (or a child scope) so importing functions does not modify the caller environment.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants