Merge branch 'master' into copilot/fix-code-duplication-packaging

This commit is contained in:
Travis Plunk
2026-03-18 14:32:51 -04:00
committed by GitHub
209 changed files with 9651 additions and 1311 deletions
+3
View File
@@ -3,6 +3,9 @@
# Areas are not limited to the filters defined in this file
# First, let's start with areas with no filters or paths
# Default
* @PowerShell/powershell-maintainers
# Area: Performance
# @adityapatwardhan
+1 -3
View File
@@ -12,9 +12,7 @@ If you believe you have found a security vulnerability in any Microsoft-owned re
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
You should receive a response within 24 hours. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
+48
View File
@@ -14,6 +14,9 @@ inputs:
required: false
default: ctrf
type: string
GITHUB_TOKEN:
description: 'GitHub token for API authentication'
required: true
runs:
using: composite
@@ -43,6 +46,51 @@ runs:
with:
global-json-file: ./global.json
- name: Set Package Name by Platform
id: set_package_name
shell: pwsh
run: |-
Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1
$platform = $env:RUNNER_OS
Write-Host "Runner platform: $platform"
if ($platform -eq 'Linux') {
$packageName = 'DSC-*-x86_64-linux.tar.gz'
} elseif ($platform -eq 'macOS') {
$packageName = 'DSC-*-x86_64-apple-darwin.tar.gz'
} else {
throw "Unsupported platform: $platform"
}
Set-GWVariable -Name "DSC_PACKAGE_NAME" -Value $packageName
- name: Get Latest DSC Package Version
shell: pwsh
run: |-
Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1
$headers = @{
Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}"
}
$releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers
$latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1
$latestVersion = $latestRelease.tag_name.TrimStart("v")
Write-Host "Latest DSC Version: $latestVersion"
$packageName = "$env:DSC_PACKAGE_NAME"
Write-Host "Package Name: $packageName"
$downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "*$packageName*" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url
Write-Host "Download URL: $downloadUrl"
$tempPath = Get-GWTempPath
Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath/DSC.tar.gz" -Verbose -Headers $headers
New-Item -ItemType Directory -Path "$tempPath/DSC" -Force -Verbose
tar xvf "$tempPath/DSC.tar.gz" -C "$tempPath/DSC"
$dscRoot = "$tempPath/DSC"
Write-Host "DSC Root: $dscRoot"
Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot
- name: Bootstrap
shell: pwsh
run: |-
@@ -24,6 +24,7 @@ $testIgnoredCount = 0
$testSkippedCount = 0
$testInvalidCount = 0
# Process test results and generate annotations for failures
Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object {
$results = [xml] (get-content $_.FullName)
@@ -35,6 +36,61 @@ Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object {
$testIgnoredCount += [int]$results.'test-results'.ignored
$testSkippedCount += [int]$results.'test-results'.skipped
$testInvalidCount += [int]$results.'test-results'.invalid
# Generate GitHub Actions annotations for test failures
# Select failed test cases
if ("System.Xml.XmlDocumentXPathExtensions" -as [Type]) {
$failures = [System.Xml.XmlDocumentXPathExtensions]::SelectNodes($results.'test-results', './/test-case[@result = "Failure"]')
}
else {
$failures = $results.SelectNodes('.//test-case[@result = "Failure"]')
}
foreach ($testfail in $failures) {
$description = $testfail.description
$testName = $testfail.name
$message = $testfail.failure.message
$stack_trace = $testfail.failure.'stack-trace'
# Parse stack trace to get file and line info
$fileInfo = Get-PesterFailureFileInfo -StackTraceString $stack_trace
if ($fileInfo.File) {
# Convert absolute path to relative path for GitHub Actions
$filePath = $fileInfo.File
# GitHub Actions expects paths relative to the workspace root
if ($env:GITHUB_WORKSPACE) {
$workspacePath = $env:GITHUB_WORKSPACE
if ($filePath.StartsWith($workspacePath)) {
$filePath = $filePath.Substring($workspacePath.Length).TrimStart('/', '\')
# Normalize to forward slashes for consistency
$filePath = $filePath -replace '\\', '/'
}
}
# Create annotation title
$annotationTitle = "Test Failure: $description / $testName"
# Build the annotation message
$annotationMessage = $message -replace "`n", "%0A" -replace "`r"
# Build and output the workflow command
$workflowCommand = "::error file=$filePath"
if ($fileInfo.Line) {
$workflowCommand += ",line=$($fileInfo.Line)"
}
$workflowCommand += ",title=$annotationTitle::$annotationMessage"
Write-Host $workflowCommand
# Output a link to the test run
if ($env:GITHUB_SERVER_URL -and $env:GITHUB_REPOSITORY -and $env:GITHUB_RUN_ID) {
$logUrl = "$($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/actions/runs/$($env:GITHUB_RUN_ID)"
Write-Host "Test logs: $logUrl"
}
}
}
}
@"
+26
View File
@@ -14,6 +14,9 @@ inputs:
required: false
default: ctrf
type: string
GITHUB_TOKEN:
description: 'GitHub token for API authentication'
required: true
runs:
using: composite
@@ -43,6 +46,29 @@ runs:
with:
global-json-file: .\global.json
- name: Get Latest DSC Package Version
shell: pwsh
run: |-
Import-Module .\.github\workflows\GHWorkflowHelper\GHWorkflowHelper.psm1
$headers = @{
Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}"
}
$releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers
$latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1
$latestVersion = $latestRelease.tag_name.TrimStart("v")
Write-Host "Latest DSC Version: $latestVersion"
$downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url
Write-Host "Download URL: $downloadUrl"
$tempPath = Get-GWTempPath
Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath\DSC.zip" -Headers $headers
$null = New-Item -ItemType Directory -Path "$tempPath\DSC" -Force
Expand-Archive -Path "$tempPath\DSC.zip" -DestinationPath "$tempPath\DSC" -Force
$dscRoot = "$tempPath\DSC"
Write-Host "DSC Root: $dscRoot"
Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot
- name: Bootstrap
shell: powershell
run: |-
@@ -0,0 +1,198 @@
---
applyTo:
- "**/*.Tests.ps1"
---
# Pester Set-ItResult Pattern for Pending and Skipped Tests
## Purpose
This instruction explains when and how to use `Set-ItResult` in Pester tests to mark tests as Pending or Skipped dynamically within test execution.
## When to Use Set-ItResult
Use `Set-ItResult` when you need to conditionally mark a test as Pending or Skipped based on runtime conditions that can't be determined at test definition time.
### Pending vs Skipped
**Pending**: Use for tests that should be enabled but temporarily can't run due to:
- Intermittent external service failures (network, APIs)
- Known bugs being fixed
- Missing features being implemented
- Environmental issues that are being resolved
**Skipped**: Use for tests that aren't applicable to the current environment:
- Platform-specific tests running on wrong platform
- Tests requiring specific hardware/configuration not present
- Tests requiring elevated permissions when not available
- Feature-specific tests when feature is disabled
## Pattern
### Basic Usage
```powershell
It "Test description" {
if ($shouldBePending) {
Set-ItResult -Pending -Because "Explanation of why test is pending"
return
}
if ($shouldBeSkipped) {
Set-ItResult -Skipped -Because "Explanation of why test is skipped"
return
}
# Test code here
}
```
### Important: Always Return After Set-ItResult
After calling `Set-ItResult`, you **must** return from the test to prevent further execution:
```powershell
It "Test that checks environment" {
if ($env:SKIP_TESTS -eq 'true') {
Set-ItResult -Skipped -Because "SKIP_TESTS environment variable is set"
return # This is required!
}
# Test assertions
$result | Should -Be $expected
}
```
**Why?** Without `return`, the test continues executing and may fail with errors unrelated to the pending/skipped condition.
## Examples from the Codebase
### Example 1: Pending for Intermittent Network Issues
```powershell
It "Validate Update-Help for module" {
if ($markAsPending) {
Set-ItResult -Pending -Because "Update-Help from the web has intermittent connectivity issues. See issues #2807 and #6541."
return
}
Update-Help -Module $moduleName -Force
# validation code...
}
```
### Example 2: Skipped for Missing Environment
```powershell
It "Test requires CI environment" {
if (-not $env:CI) {
Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester"
return
}
Install-CIPester -ErrorAction Stop
}
```
### Example 3: Pending for Platform-Specific Issue
```powershell
It "Clear-Host works correctly" {
if ($IsARM64) {
Set-ItResult -Pending -Because "ARM64 runs in non-interactively mode and Clear-Host does not work."
return
}
& { Clear-Host; 'hi' } | Should -BeExactly 'hi'
}
```
### Example 4: Skipped for Missing Feature
```powershell
It "Test ACR authentication" {
if ($env:ACRTESTS -ne 'true') {
Set-ItResult -Skipped -Because "The tests require the ACRTESTS environment variable to be set to 'true' for ACR authentication."
return
}
$psgetModuleInfo = Find-PSResource -Name $ACRTestModule -Repository $ACRRepositoryName
# test assertions...
}
```
## Alternative: Static -Skip and -Pending Parameters
For conditions that can be determined at test definition time, use the static parameters instead:
```powershell
# Static skip - condition known at definition time
It "Windows-only test" -Skip:(-not $IsWindows) {
# test code
}
# Static pending - always pending
It "Test for feature being implemented" -Pending {
# test code that will fail until feature is done
}
```
**Use Set-ItResult when**:
- Condition depends on runtime state
- Condition is determined inside a helper function
- Need to check multiple conditions sequentially
**Use static parameters when**:
- Condition is known at test definition
- Condition doesn't change during test run
- Want Pester to show the condition in test discovery
## Best Practices
1. **Always include -Because parameter** with a clear explanation
2. **Always return after Set-ItResult** to prevent further execution
3. **Reference issues or documentation** when relevant (e.g., "See issue #1234")
4. **Be specific in the reason** - explain what's wrong and what's needed
5. **Use Pending sparingly** - it indicates a problem that should be fixed
6. **Prefer Skipped over Pending** when test truly isn't applicable
## Common Mistakes
### ❌ Mistake 1: Forgetting to Return
```powershell
It "Test" {
if ($condition) {
Set-ItResult -Pending -Because "Reason"
# Missing return - test code will still execute!
}
$value | Should -Be $expected # This runs and fails
}
```
### ❌ Mistake 2: Vague Reason
```powershell
Set-ItResult -Pending -Because "Doesn't work" # Too vague
```
### ✅ Correct:
```powershell
It "Test" {
if ($condition) {
Set-ItResult -Pending -Because "Update-Help has intermittent network timeouts. See issue #2807."
return
}
$value | Should -Be $expected
}
```
## See Also
- [Pester Documentation: Set-ItResult](https://pester.dev/docs/commands/Set-ItResult)
- [Pester Documentation: It](https://pester.dev/docs/commands/It)
- Examples in the codebase:
- `test/powershell/Host/ConsoleHost.Tests.ps1`
- `test/infrastructure/ciModule.Tests.ps1`
- `tools/packaging/releaseTests/sbom.tests.ps1`
@@ -0,0 +1,299 @@
---
applyTo: "**/*.Tests.ps1"
---
# Pester Test Status Meanings and Working Tests
## Purpose
This guide clarifies Pester test outcomes and what it means for a test to be "working" - which requires both **passing** AND **actually validating functionality**.
## Test Statuses in Pester
### Passed ✓
**Status Code**: `Passed`
**Exit Result**: Test ran successfully, all assertions passed
**What it means**:
- Test executed without errors
- All `Should` statements evaluated to true
- Test setup and teardown completed without issues
- Test is **validating** the intended functionality
**What it does NOT mean**:
- The feature is working (assertions could be wrong)
- The test is meaningful (could be testing wrong thing)
- The test exercises all code paths
### Failed ✗
**Status Code**: `Failed`
**Exit Result**: Test ran but assertions failed
**What it means**:
- Test executed but an assertion returned false
- Expected value did not match actual value
- Test detected a problem with the functionality
**Examples**:
```
Expected $true but got $false
Expected 5 items but got 3
Expected no error but got: Cannot find parameter
```
### Error ⚠
**Status Code**: `Error`
**Exit Result**: Test crashed with an exception
**What it means**:
- Test failed to complete
- An exception was thrown during test execution
- Could be in test setup, test body, or test cleanup
- Often indicates environmental issue, not code functional issue
**Examples**:
```
Cannot bind argument to parameter 'Path' because it is null
File not found: C:\expected\config.json
Access denied writing to registry
```
### Pending ⏳
**Status Code**: `Pending`
**Exit Result**: Test ran but never completed assertions
**What it means**:
- Test was explicitly marked as not ready to run
- `Set-ItResult -Pending` was called
- Used to indicate: known bugs, missing features, environmental issues
**When to use Pending**:
- Test for feature in development
- Test disabled due to known bug (issue #1234)
- Test disabled due to intermittent failures being fixed
- Platform-specific issues being resolved
**⚠️ WARNING**: Pending tests are NOT validating functionality. They hide problems.
### Skipped ⊘
**Status Code**: `Skipped`
**Exit Result**: Test did not run (detected at start)
**What it means**:
- Test was intentionally not executed
- `-Skip` parameter or `It -Skip:$condition` was used
- Environment doesn't support this test
**When to use Skip**:
- Test not applicable to current platform (Windows-only test on Linux)
- Test requires feature that's not available (admin privileges)
- Test requires specific configuration not present
**Difference from Pending**:
- **Skip**: "This test shouldn't run here" (known upfront)
- **Pending**: "This test should eventually run but can't now"
### Ignored ✛
**Status Code**: `Ignored`
**Exit Result**: Test marked as not applicable
**What it means**:
- Test has `[Ignore("reason")]` attribute
- Test is permanently disabled in this location
- Not the same as Skipped (which is conditional)
**When to use Ignore**:
- Test for deprecated feature
- Test for bug that won't be fixed
- Test moved to different test file
---
## What Does "Working" Actually Mean?
A test is **working** when it meets BOTH criteria:
### 1. **Test Status is PASSED** ✓
```powershell
It "Test name" {
# Test executes
# All assertions pass
# Returns Passed status
}
```
### 2. **Test Actually Validates Functionality**
```powershell
# ✓ GOOD: Tests actual functionality
It "Get-Item returns files from directory" -Tags @('Unit') {
$testDir = New-Item -ItemType Directory -Force
New-Item -Path $testDir -Name "file.txt" -ItemType File | Out-Null
$result = Get-Item -Path "$testDir\file.txt"
$result.Name | Should -Be "file.txt"
$result | Should -Exist
Remove-Item $testDir -Recurse -Force
}
# ✗ BAD: Returns Passed but doesn't validate functionality
It "Get-Item returns files from directory" -Tags @('Unit') {
$result = Get-Item -Path somepath # May not exist, may not actually test
$result | Should -Not -BeNullOrEmpty # Too vague
}
# ✗ BAD: Test marked Pending - validation is hidden
It "Get-Item returns files from directory" -Tags @('Unit') {
Set-ItResult -Pending -Because "File system not working"
return
# No validation happens at all
}
```
---
## The Problem with Pending Tests
### Why Pending Tests Hide Problems
```powershell
# BAD: Test marked Pending - looks like "working" status but validation is skipped
It "Download help from web" {
Set-ItResult -Pending -Because "Web connectivity issues"
return
# This code never runs:
Update-Help -Module PackageManagement -Force -ErrorAction Stop
Get-Help Get-Package | Should -Not -BeNullOrEmpty
}
```
**Result**:
- ✗ Feature is broken (Update-Help fails)
- ✓ Test shows "Pending" (looks acceptable)
- ✗ Problem is hidden and never fixed
### The Right Approach
**Option A: Fix the root cause**
```powershell
It "Download help from web" {
# Use local assets that are guaranteed to work
Update-Help -Module PackageManagement -SourcePath ./assets -Force -ErrorAction Stop
Get-Help Get-Package | Should -Not -BeNullOrEmpty
}
```
**Option B: Gracefully skip when unavailable**
```powershell
It "Download help from web" -Skip:$(-not $hasInternet) {
Update-Help -Module PackageManagement -Force -ErrorAction Stop
Get-Help Get-Package | Should -Not -BeNullOrEmpty
}
```
**Option C: Add retry logic for intermittent issues**
```powershell
It "Download help from web" {
$maxRetries = 3
$attempt = 0
while ($attempt -lt $maxRetries) {
try {
Update-Help -Module PackageManagement -Force -ErrorAction Stop
break
}
catch {
$attempt++
if ($attempt -ge $maxRetries) { throw }
Start-Sleep -Seconds 2
}
}
Get-Help Get-Package | Should -Not -BeNullOrEmpty
}
```
---
## Test Status Summary Table
| Status | Passed? | Validates? | Counts as "Working"? | Use When |
|--------|---------|------------|----------------------|----------|
| **Passed** | ✓ | ✓ | **YES** | Feature is working and test proves it |
| **Failed** | ✗ | ✓ | NO | Feature is broken or test has wrong expectation |
| **Error** | ✗ | ✗ | NO | Test infrastructure broken, can't validate |
| **Pending** | - | ✗ | **NO** ⚠️ | Temporary - test should eventually pass |
| **Skipped** | - | ✗ | NO | Test not applicable to this environment |
| **Ignored** | - | ✗ | NO | Test permanently disabled |
---
## Recommended Patterns
### Pattern 1: Resilient Test with Fallback
```powershell
It "Feature works with web or local source" {
$useLocal = $false
try {
Update-Help -Module Package -Force -ErrorAction Stop
}
catch {
$useLocal = $true
Update-Help -Module Package -SourcePath ./assets -Force -ErrorAction Stop
}
# Validate functionality regardless of source
Get-Help Get-Package | Should -Not -BeNullOrEmpty
}
```
### Pattern 2: Conditional Skip with Clear Reason
```powershell
Describe "Update-Help from Web" -Skip $(-not (Test-InternetConnectivity)) {
It "Downloads help successfully" {
Update-Help -Module PackageManagement -Force -ErrorAction Stop
Get-Help Get-Package | Should -Not -BeNullOrEmpty
}
}
```
### Pattern 3: Separate Suites by Dependency
```powershell
Describe "Help Content Tests - Web" {
# Tests that require internet - can be skipped if unavailable
It "Downloads from web" { ... }
}
Describe "Help Content Tests - Local" {
# Tests with local assets - should always pass
It "Loads from local assets" {
Update-Help -Module Package -SourcePath ./assets -Force
Get-Help Get-Package | Should -Not -BeNullOrEmpty
}
}
```
---
## Checklist: Is Your Test "Working"?
- [ ] Test status is **Passed** (not Pending, not Skipped, not Failed)
- [ ] Test actually **executes** the feature being tested
- [ ] Test has **specific assertions** (not just `Should -Not -BeNullOrEmpty`)
- [ ] Test includes **cleanup** (removes temp files, restores state)
- [ ] Test can run **multiple times** without side effects
- [ ] Test failure **indicates a real problem** (not flaky assertions)
- [ ] Test success **proves the feature works** (not just "didn't crash")
If any of these is false, your test may be passing but not "working" properly.
---
## See Also
- [Pester Documentation](https://pester.dev/)
- [Set-ItResult Documentation](https://pester.dev/docs/commands/Set-ItResult)
@@ -0,0 +1,48 @@
id: labelAdded.approvedLowRisk
name: GitOps.PullRequestIssueManagement
description: Remove Approved-LowRisk if applied by an unauthorized user
owner:
resource: repository
disabled: false
where:
configuration:
resourceManagementConfiguration:
eventResponderTasks:
- description: Remove Approved-LowRisk if label was added by someone not authorized
if:
- payloadType: Pull_Request
- isOpen
- labelAdded:
label: Approved-LowRisk
# Unauthorized = NOT admin AND NOT in explicit allowlist
- not:
or:
- activitySenderHasPermission:
permission: Admin
# Allowlist (enabled)
- isActivitySender:
user: iSazonov
issueAuthor: False
- isActivitySender:
user: daxian-dbw
issueAuthor: False
# Allowlist (commented out for now)
# - isActivitySender:
# user: TravisEz13
# issueAuthor: False
# - isActivitySender:
# user: adityapatwardhan
# issueAuthor: False
# - isActivitySender:
# user: jshigetomi
# issueAuthor: False
then:
- removeLabel:
label: Approved-LowRisk
- addReply:
reply: >-
The `Approved-LowRisk` label is restricted to authorized maintainers and was removed.
onFailure:
onSuccess:
@@ -0,0 +1,56 @@
id: labelAdded.clBuildPackaging.addBackportConsider
name: GitOps.PullRequestIssueManagement
description: Add backport consideration labels when CL-BuildPackaging is added to an open PR targeting master
owner:
resource: repository
disabled: false
where:
configuration:
resourceManagementConfiguration:
eventResponderTasks:
- description: Add BackPort-7.4.x-Consider when CL-BuildPackaging is added to open PR targeting master
if:
- payloadType: Pull_Request
- isOpen
- labelAdded:
label: CL-BuildPackaging
- targetsBranch:
branch: master
- not:
hasLabel:
label: BackPort-7.4.x-Consider
then:
- addLabel:
label: BackPort-7.4.x-Consider
- description: Add BackPort-7.5.x-Consider when CL-BuildPackaging is added to open PR targeting master
if:
- payloadType: Pull_Request
- isOpen
- labelAdded:
label: CL-BuildPackaging
- targetsBranch:
branch: master
- not:
hasLabel:
label: BackPort-7.5.x-Consider
then:
- addLabel:
label: BackPort-7.5.x-Consider
- description: Add BackPort-7.6.x-Consider when CL-BuildPackaging is added to open PR targeting master
if:
- payloadType: Pull_Request
- isOpen
- labelAdded:
label: CL-BuildPackaging
- targetsBranch:
branch: master
- not:
hasLabel:
label: BackPort-7.6.x-Consider
then:
- addLabel:
label: BackPort-7.6.x-Consider
onFailure:
onSuccess:
@@ -0,0 +1,524 @@
---
name: analyze-pester-failures
description: Troubleshooting guide for analyzing and investigating Pester test failures in PowerShell CI jobs. Help agents understand why tests are failing, interpret test output, navigate test result artifacts, and provide actionable recommendations for fixing test issues.
---
# Analyze Pester Test Failures
Investigate and troubleshoot Pester test failures in GitHub Actions workflows. Understand what tests are failing, why they're failing, and provide recommendations for test fixes.
| Skill | When to Use |
|-------|-----------|
| analyze-pester-failures | When investigating why Pester tests are failing in a CI job. Use when a test job shows failures and you need to understand what test failed, why it failed, what the error message means, and what might need to be fixed. Also use when asked: "why did this test fail?", "what's the test error?", "test is broken", "test failure analysis", "debug test failure", or given test failure logs and stack traces. |
## When to Use This Skill
Use this skill when you need to:
- Understand why a specific Pester test is failing
- Interpret test failure messages and error output
- Analyze test result data from CI workflow runs (XML, logs, stack traces)
- Identify the root cause of test failures (test logic, assertion failure, exception, timeout, skip/ignore reason)
- Provide recommendations for fixing failing tests
- Compare expected vs. actual test behavior
- Debug test environment issues (missing dependencies, configuration problems)
- Understand test skip/ignored/inconclusive status reasons
**Do not use this skill for:**
- General PowerShell debugging unrelated to tests
- Test infrastructure/CI setup issues (except as they affect test failure interpretation)
- Performance analysis or benchmarking (that's a different investigation)
## Quick Start
### ⚠️ CRITICAL: The Workflow Must Be Followed IN ORDER
This skill describes a **sequential 6-step analysis workflow**. Skipping steps or jumping around leads to **incomplete analysis and incorrect conclusions**.
**The Problem**: It's easy to skip to Step 4 or 5 without doing Steps 1-2, resulting in missing data and bad conclusions.
**The Solution**: Use the automated analysis script to enforce the workflow:
```powershell
# Automatically runs Steps 1-6 in order, preventing skipping
./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR <PR_NUMBER>
# Example:
./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR 26800
```
This script:
1. ✓ Fetches PR status automatically
2. ✓ Downloads artifacts (can't skip, depends on Step 1)
3. ✓ Extracts failures (can't skip, depends on Step 2)
4. ✓ Analyzes error messages
5. ✓ Documents context
6. ✓ Generates recommendations
**Only use the manual commands below if you fully understand the workflow.**
### Manual Workflow (for reference)
```powershell
# Step 1: Identify the failing job
gh pr view <PR_NUMBER> --json 'statusCheckRollup' | ConvertFrom-Json | Where-Object { $_.conclusion -eq 'FAILURE' }
# Step 2: Download artifacts (extract RUN_ID from Step 1)
gh run download <RUN_ID> --dir ./artifacts
gh run view <RUN_ID> --log > test-logs.txt
# Step 3-6: Extract, analyze, and interpret
# (See Analysis Workflow section below)
```
## Common Test Failure Analysis Approaches
### 1. **Interpreting Assertion Failures**
The most common test failure is when an assertion doesn't match expectations.
**Example:**
```
Expected $true but got $false at /path/to/test.ps1:42
Assertion failed: Should -Be "expected" but was "actual"
```
**How to analyze:**
- Read the assertion message: what was expected vs. what was actual?
- Check the test logic: is the expectation correct?
- Look for mock/stub issues: are dependencies configured correctly?
- Check parameter values: what inputs were passed to the function under test?
### 2. **Exception Failures**
Tests fail when PowerShell throws an exception instead of successful completion.
**Example:**
```
Command: Write-Host $null
Error: Cannot bind argument to parameter 'Object' because it is null.
```
**How to analyze:**
- Read the exception message: what operation failed?
- Check the stack trace: where in the test or tested code did it throw?
- Verify preconditions: does the test setup provide required values/mocks?
- Look for environmental issues: missing modules, permissions, file system state?
### 3. **Timeout Failures**
A test takes longer than the allowed timeout to complete.
**Example:**
```
Test 'Should complete in reasonable time' timed out after 30 seconds
```
**How to analyze:**
- Is the timeout appropriate for this test type? (network tests need more time)
- Is there an infinite loop in the test or tested code?
- Are there resource contention issues on the CI runner?
- Does the test hang waiting for something (file lock, network, process)?
### 4. **Skip/Ignored Reason Analysis**
Tests marked as skipped or ignored provide clues about test environment.
**Example:**
```
Test marked [Skip("Only runs on Windows")] - running on Linux
Test marked [Ignore("Known issue #12345")]
```
**How to analyze:**
- Read the skip/ignore reason: is it still valid?
- Check if environment has changed: platform, module versions, etc.
- Verify issue status: is the known issue still open? Has it been fixed?
- Determine if skip should be removed or if test needs environment changes
### 5. **Flaky/Intermittent Failures**
Tests that sometimes pass, sometimes fail indicate race conditions or environment sensitivity.
**Example:**
- Test passes locally but fails on CI
- Test passes first run of suite, fails on second run
- Test passes on Windows but fails on Linux
**How to analyze:**
- Look for timeout races: is timing involved in the test?
- Check for test isolation issues: does one test affect another?
- Verify environment differences: CI vs. local paths, permissions, versions
- Look for external dependencies: network calls, file I/O, process interactions
## Key Artifacts and Locations
| Item | Purpose | Location |
|------|---------|----------|
| Test result XML | Pester output with test cases, failures, errors | Workflow artifacts: `junit-pester-*.xml` |
| Job logs | Full job output including test execution and errors | GitHub Actions run logs or `gh run download` |
| Stack traces | Error location information from failed assertions | Within job logs and XML failure messages |
| Test files | The actual Pester test code (`.ps1` files) | `test/` directory in repository |
## Analysis Workflow
### ⚠️ Important: These Steps MUST Be Followed In Order
Each step depends on the previous one. Skipping or re-ordering steps causes incomplete analysis:
- **Step 1** (identify jobs) → You get the RUN_ID needed for Step 2
- **Step 2** (download) → You get the artifacts needed for Step 3
- **Step 3** (extract) → You discover what failures exist for Step 4
- **Step 4** (read messages) → You understand the errors to analyze in Step 5
- **Step 5** (context) → You gather information to make recommendations in Step 6
- **Step 6** (interpret) → You use all above to recommend fixes
**Real Problem We Had**:
- ❌ Jumped to Step 3 without Step 1-2
- ❌ Used random test data from context instead of downloading PR artifacts
- ❌ Skipped Steps 5-6 entirely
- ❌ Made recommendations without full context
**Result**: Wrong analysis and recommendations that didn't actually fix the problem.
### Recommended: Use the Automated Script
```powershell
./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR <PR_NUMBER>
```
This enforces the workflow and prevents skipping.
### Step 2: Get Test Results
Fetch the test result artifacts and job logs:
```powershell
# Download artifacts including test XML results
gh run download <RUN_ID> --dir ./artifacts
# Get job logs
gh run view <RUN_ID> --log > test-logs.txt
# Inspect test XML
$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml)
$xml.'test-results' | Select-Object total, failures, errors, ignored, inconclusive
```
### Step 3: Extract Specific Failures
Find the failing test cases in the XML:
```powershell
# Get all failed test cases
$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml)
$failures = $xml.SelectNodes('.//test-case[@result = "Failure"]')
# For each failure, display key info
$failures | ForEach-Object {
[PSCustomObject]@{
Name = $_.name
Description = $_.description
Message = $_.failure.message
StackTrace = $_.failure.'stack-trace'
}
}
```
### Step 4: Read the Error Message
The error message tells you what went wrong:
**Assertion failures:**
```
Expected $true but got $false
Expected "value1" but got "value2"
Expression should have failed with exception, but didn't
```
**Exceptions:**
```
Cannot find a parameter with name 'Name'
Property 'Property' does not exist on 'Object'
Cannot bind argument to parameter because it is null
```
**Timeouts:**
```
Test timed out after 30 seconds
Test is taking too long to complete
```
### Step 5: Understand the Context
Look at the test file to understand what was being tested:
```powershell
# Find the test file mentioned in the stack trace
# Example: /path/to/test/Feature.Tests.ps1:42
# Read the test code around that line
code <test-file-path>:<line-number>
# Understand:
# - What assertion is on that line?
# - What is the test trying to verify?
# - What are the setup/mock/before conditions?
# - Are there recent changes to the function being tested?
```
### Step 6: Interpret the Failure
Determine the root cause category:
**Test issue (needs code fix):**
- Assertion logic is wrong
- Test expectations don't match actual behavior
- Test setup is incomplete
- Mock/stub configuration missing
**Environmental issue (needs environment change):**
- Test assumes a specific file or registry entry exists
- Test requires Windows/Linux specifically
- Test requires specific PowerShell version
- Test requires specific module version
- Timing-sensitive test affected by CI load
**Data issue (needs input data change):**
- Test data no longer valid
- External API changed format
- Configuration file has changed structure
**Flakiness (needs test hardening):**
- Race condition in test
- Timing assumptions too tight
- Resource contention on CI runner
- Non-deterministic behavior in tested code
## Common Test Failure Patterns
| Pattern | What It Means | Example | Next Step |
|---------|---------------|---------|-----------|
| `Expected $true but got $false` | Assertion on boolean result failed | Test expects function returns true, but it returns false | Check function logic for bug or test logic for wrong expectation |
| `Cannot find path` | File or directory doesn't exist | Test tries to read config file that's not present | Verify file path, check test setup, ensure CI environment has file |
| `Cannot bind argument to parameter 'X'` | Required parameter value is null or wrong type | Function called with $null where object expected | Check test mock setup, verify parameter types |
| `Test timed out after X seconds` | Test exceeded time limit | Network call or loop takes too long | Increase timeout for slow test, find infinite loop, mock network calls |
| `Expression should have failed but didn't` | Exception wasn't thrown when expected | Test expects error but function succeeds | Check if function behavior changed, update test expectation |
| `Could not find parameter 'X'` | Function doesn't have parameter | Test calls function with parameter that doesn't exist | Check PowerShell version, verify function signature, update test |
| `This platform is not supported` | Test skipped on current OS | Windows-only test running on Linux | Add platform check, update test environment, or mark as platform-specific |
| `Test marked [Ignore]` | Test explicitly disabled | Test has `[Ignore("reason")]` attribute | Check if reason still valid, remove if issue fixed |
## Interpreting Test Results
### Test Result Counts
Pester test outcomes are categorized as:
| Count | Meaning | Notes |
|-------|---------|-------|
| `total` | Total number of test cases executed | Should match: passed + failed + errors + skipped + ignored |
| `failures` | Test assertions that failed | `Expected X but got Y` type failures |
| `errors` | Tests that threw exceptions | Unhandled PowerShell exceptions during test |
| `skipped` | Tests explicitly skipped (marked with `-Skip`) | Test code recognizes condition and skips |
| `ignored` | Tests marked as ignored (marked with `-Ignore`) | Test disabled intentionally, usually notes reason |
| `inconclusive` | Tests with unclear result | Rare; usually means test framework issue |
| `passed` | Tests with passing assertions | `total - failures - errors - skipped - ignored` |
### Stack Trace Interpretation
A stack trace shows where the failure occurred:
```
at /home/runner/work/PowerShell/test/Feature.Tests.ps1:42
Means:
- File: /home/runner/work/PowerShell/test/Feature.Tests.ps1
- Line: 42
- Look at that line to see which assertion failed
```
### Understanding Skipped Tests
When XML shows `result="Ignored"` or `result="Skipped"`:
```xml
<test-case name="Test Name" result="Ignored">
<reason>Only runs on Windows</reason>
</test-case>
```
The reason explains why test didn't run. Not a failure, but important for understanding test coverage.
## Providing Test Failure Analysis
### Investigation Questions
After gathering test output, ask yourself:
1. **Is the test code correct?**
- Does the test assertion match the expected behavior?
- Are test expectations still valid?
- Has the function being tested changed?
2. **Is the test setup correct?**
- Are mocks/stubs configured properly?
- Does the test environment have required files/configuration?
- Are preconditions (database, files, services) met?
3. **Is this a code bug or test issue?**
- Does the tested function have a logic error?
- Or does the test have incorrect expectations?
4. **Is this environment-specific?**
- Only fails on Windows/Linux?
- Only fails on CI but passes locally?
- Timing-dependent or resource-dependent?
5. **Is this a known/expected failure?**
- Is there already an issue tracking this failure?
- Is the test marked as flaky or expected to fail?
- Does the skip/ignore reason still apply?
### Recommendation Framework
Based on your analysis:
| Finding | Recommendation |
|---------|-----------------|
| Test logic is wrong | "Test assertion on line X is incorrect. Test expects Y but function correctly returns Z. Update test expectation." |
| Tested code has bug | "Function at file.ps1#L42 has logic error. When X happens, returns Y instead of Z. Fix the condition." |
| Missing test setup | "Test setup incomplete. Mock for dependency Y is not configured. Add `Mock Get-Y -MockWith { ... }`" |
| Environment issue | "Test is Windows-specific but running on Linux. Either add platform check or skip on non-Windows." |
| Flaky test | "Test is timing-sensitive (sleep 1 second). Increase timeout or use better synchronization." |
| Test should be skipped | "Test is marked Ignored for good reason. Keep it disabled until issue #12345 is fixed." |
### Tone and Structure
Provide analysis as:
1. **Summary** (1 sentence): What test is failing and general category
2. **Failure Details** (2-3 sentences): What the test output says
3. **Root Cause** (1-2 sentences): Why it's failing (test bug vs. code bug vs. environment)
4. **Recommendation** (actionable): What should be done to fix it
5. **Context** (optional): Link to related code, issues, or recent changes
## Examples
### Example 1: Assertion Failure Due to Code Bug
**Test Output:**
```
Expected 5 but got 3 at /path/to/Test.ps1:42
```
**Investigation:**
1. Look at line 42: `$result | Should -Be 5`
2. Check the test: It expects function to return 5 items
3. Check the function: It returns `$items | Where-Object {$_.Status -eq "Active"}` but the filter is wrong
4. Root cause: Function has logic error, not test error
**Recommendation:**
```
Test failure is due to a code bug:
The test Set-Configuration should return 5 items but returns 3.
Looking at the tested function at [module.ps1#L42](module.ps1#L42):
$activeItems = $items | Where-Object {$_.Status -eq "Active"}
The issue is the filter condition. It's currently filtering by "Active" status,
but should include "Pending" status as well.
Fix: Change line 42 to:
$activeItems = $items | Where-Object {$_.Status -ne "Disabled"}
Then re-run the test to verify it now returns 5 items as expected.
```
### Example 2: Test Setup Issue
**Test Output:**
```
Cannot find path '/expected/config.json' because it does not exist at /path/to/Test.ps1:15
```
**Investigation:**
1. Line 15 tries to read a config file
2. The test setup doesn't create this file
3. Works locally but fails on CI because CI doesn't have the same file
**Recommendation:**
```
Test setup is incomplete:
The test Initialize-Config fails because it expects /expected/config.json but the test doesn't create this file.
The test needs to ensure the config file exists. Currently line 12-14 doesn't set up the file:
# Before:
# (no setup of config file)
# After:
@{ setting1 = "value1"; setting2 = "value2" } | ConvertTo-Json |
Out-File $testConfigPath
Alternatively, the test function should accept a parameter for the config path and use a temporary file:
param([string]$ConfigPath = (New-TemporaryFile))
Re-run the test to verify the config file is properly available.
```
### Example 3: Platform-Specific Test Failure
**Test Output:**
```
Test 'should read Windows Registry' failed on Linux runner
Cannot find path 'HKEY_LOCAL_MACHINE:\...'
```
**Investigation:**
1. Test assumes Windows Registry exists (Windows-only)
2. Running on Linux runner doesn't have Registry
3. Test should skip on non-Windows platforms
**Recommendation:**
```
Test is platform-specific but running on wrong platform:
The test "should read Windows Registry" assumes Windows Registry exists but is running on Linux.
Add a platform check to skip this test on non-Windows systems:
It "should read Windows Registry" -Skip:$(-not $IsWindows) {
# test code here
}
Or group Windows-only tests in a separate Describe block with platform check:
Describe "Windows Registry Tests" -Skip:$(-not $IsWindows) {
# all Windows-specific tests here
}
This allows the test to be skipped on Linux/Mac while still running on Windows CI.
```
## References
- [Pester Testing Framework](https://pester.dev/) — Official documentation, best practices for test writing
- [Test Files](../../../test/) — PowerShell test suite in repository
- [GitHub Actions Documentation](https://docs.github.com/en/actions) — Understanding workflow runs and logs
- [PowerShell Documentation](https://learn.microsoft.com/en-us/powershell/) — Language reference for understanding test code
## Tips
1. **Read the error message first:** The error message is usually the most direct clue to the problem
2. **Check test vs. code blame:** Is the test wrong or is the code wrong? Look at both sides
3. **Verify test isolation:** Does one test failure affect others? Check for shared state or test ordering dependencies
4. **Test locally first:** Try running the failing test locally to reproduce and understand it better
5. **Check for environmental assumptions:** Windows-specific paths, module versions, file locations may differ on CI
6. **Look for skip/ignore patterns:** If a test is consistently ignored, check if the reason is still valid
7. **Compare passing vs. failing:** If test passes locally but fails on CI, the difference is usually environment-related
8. **Check recent changes:** Did a recent PR change the tested code or test itself?
9. **Understand Pester output format:** Different Pester versions, different `-ErrorAction`, `-WarningAction` produce different test results
10. **Don't assume CI is wrong:** Failures on CI often reveal real issues that local testing missed (network, file permissions, parallelization, etc.)
## Additional Links
- [PowerShell Repository](https://github.com/PowerShell/PowerShell)
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Pester Testing Framework](https://github.com/Pester/Pester)
@@ -0,0 +1,163 @@
# Understanding Pester Test Failures
This reference explains how to interpret Pester test output and understand failure messages.
## Supported Formats
### Pester 4 Format
```
at line: 123 in C:\path\to\file.ps1
```
**Regex Pattern:**
```powershell
if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') {
$result.Line = $matches[1]
$result.File = $matches[2].Trim()
return $result
}
```
### Pester 5 Format (Common)
```
at 1 | Should -Be 2, C:\path\to\file.ps1:123
at 1 | Should -Be 2, /home/runner/work/PowerShell/PowerShell/test/file.ps1:123
```
**Regex Pattern:**
```powershell
if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') {
$result.File = $matches[1].Trim()
$result.Line = $matches[2]
return $result
}
```
### Alternative Format
```
at C:\path\to\file.ps1:123
at /path/to/file.ps1:123
```
**Regex Pattern:**
```powershell
if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') {
$result.File = $matches[1].Trim()
$result.Line = $matches[2]
return $result
}
```
## Troubleshooting Parsing Failures
### Issue: Line Number Extracted But File Path Is Null
**Cause:** Stack trace matches line-with-path pattern but file extraction doesn't work
**Solution:**
1. Check if file path exists as expected in filesystem
2. Verify regex doesn't have too-greedy bounds (check use of `.+?` vs `.+`)
3. Test regex against actual stack trace string:
```powershell
$trace = "at line: 42 in C:\path\to\test.ps1"
if ($trace -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') {
Write-Host "File: $($matches[2])" # Should be "C:\path\to\test.ps1"
}
```
### Issue: Special Characters in File Path Break Regex
**Cause:** Characters like parens `()`, brackets `[]`, pipes `|` have special meaning in regex
**Solution:**
1. Escape special chars in regex: `[Regex]::Escape($path)`
2. Use character class `[\/\\]` instead of alternation for path separators
3. Test with files containing problematic names:
```powershell
$traces = @(
"at line: 1 in C:\path\(with)\parens\test.ps1",
"at /home/user/[brackets]/test.ps1:5",
"at C:\path\with spaces\test.ps1:10"
)
# Test each against all patterns
```
### Issue: Regex Matches But Extracts Wrong Values
**Symptom:** $matches[1] is file instead of line, or vice versa
**Debug Steps:**
1. Print all captured groups: `$matches.Values | Format-Table -AutoSize`
2. Verify group order in regex matches expectations
3. Test with sample Pester output:
```powershell
$sampleTrace = @"
at 1 | Should -Be 2, /home/runner/work/PowerShell/test/file.ps1:42
"@
if ($sampleTrace -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') {
Write-Host "Match 1: $($matches[1])" # Should be file path
Write-Host "Match 2: $($matches[2])" # Should be line number
}
```
## Testing the Parser
Use this PowerShell script to validate `Get-PesterFailureFileInfo`:
```powershell
# Import the function
. ./build.psm1
$testCases = @(
@{
Input = "at line: 42 in C:\path\to\test.ps1"
Expected = @{ File = "C:\path\to\test.ps1"; Line = "42" }
},
@{
Input = "at /home/runner/work/test.ps1:123"
Expected = @{ File = "/home/runner/work/test.ps1"; Line = "123" }
},
@{
Input = "at 1 | Should -Be 2, /path/to/file.ps1:99"
Expected = @{ File = "/path/to/file.ps1"; Line = "99" }
}
)
foreach ($test in $testCases) {
$result = Get-PesterFailureFileInfo -StackTraceString $test.Input
$fileMatch = $result.File -eq $test.Expected.File
$lineMatch = $result.Line -eq $test.Expected.Line
$status = if ($fileMatch -and $lineMatch) { "✓ PASS" } else { "✗ FAIL" }
Write-Host "$status : $($test.Input)"
if (-not $fileMatch) { Write-Host " Expected file: $($test.Expected.File), got: $($result.File)" }
if (-not $lineMatch) { Write-Host " Expected line: $($test.Expected.Line), got: $($result.Line)" }
}
```
## Adding Support for New Formats
When Pester changes its output format:
1. **Capture sample output** from failing tests
2. **Identify the pattern** (e.g., "file path always after comma followed by colon")
3. **Write regex** to match pattern without over-matching
4. **Add to `Get-PesterFailureFileInfo`** before existing patterns (order matters for fallback)
5. **Test with samples** containing special characters, long paths, and edge cases
Example: Adding a new format at the top of the function:
```powershell
# Try pattern: "at <description>, <path>:<line>" (Pester 5.1 hypothetical)
if ($StackTraceString -match 'at .+?, ((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') {
$result.File = $matches[1].Trim()
$result.Line = $matches[2]
return $result
}
# Try existing patterns...
```
Place new patterns **first** so they take precedence over fallback patterns.
@@ -0,0 +1,456 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
<#
.SYNOPSIS
Automated Pester test failure analysis workflow for GitHub PRs.
.DESCRIPTION
This script automates the complete analysis workflow defined in the analyze-pester-failures
skill. It performs all steps in order:
1. Identify failing test jobs in the PR
2. Download test artifacts and logs
3. Extract specific test failures
4. Parse error messages
5. Search logs for error markers and generate recommendations
By automating the workflow, this ensures analysis steps are followed in order
and nothing is skipped.
.PARAMETER PR
The GitHub PR number to analyze (e.g., 26800)
.PARAMETER Owner
Repository owner (default: PowerShell)
.PARAMETER Repo
Repository name (default: PowerShell)
.PARAMETER OutputDir
Directory to store analysis results (default: ./pester-analysis-PR<number>)
.PARAMETER Interactive
Prompt for recommendations after analysis (default: non-interactive)
.PARAMETER ForceDownload
Force re-download of artifacts and logs, even if they already exist
.EXAMPLE
.\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800
Analyzes PR #26800 and saves results to ./pester-analysis-PR26800
.EXAMPLE
.\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -Interactive
Interactive mode: shows failures and prompts for next steps
.EXAMPLE
.\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -ForceDownload
Re-download all logs and artifacts, skipping the cache
.NOTES
Requires: GitHub CLI (gh) configured and authenticated
This script enforces the workflow defined in .github/skills/analyze-pester-failures/SKILL.md
#>
param(
[Parameter(Mandatory)]
[int]$PR,
[string]$Owner = 'PowerShell',
[string]$Repo = 'PowerShell',
[string]$OutputDir,
[switch]$Interactive,
[switch]$ForceDownload
)
$ErrorActionPreference = 'Stop'
if (-not $OutputDir) {
$OutputDir = "./pester-analysis-PR$PR"
}
# Colors for output
$colors = @{
Step = [ConsoleColor]::Cyan
Success = [ConsoleColor]::Green
Warning = [ConsoleColor]::Yellow
Error = [ConsoleColor]::Red
Info = [ConsoleColor]::Gray
}
function Write-Step {
param([string]$text, [int]$number)
Write-Host "`n[$number/6] $text" -ForegroundColor $colors.Step -BackgroundColor Black
}
function Write-Result {
param([string]$text, [ValidateSet('Success','Warning','Error','Info')]$type = 'Info')
Write-Host $text -ForegroundColor $colors[$type]
}
Write-Host "`n=== Pester Test Failure Analysis ===" -ForegroundColor $colors.Step
Write-Host "PR: $Owner/$Repo#$PR" -ForegroundColor $colors.Info
Write-Host "Output Directory: $OutputDir" -ForegroundColor $colors.Info
# Ensure output directory exists
if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
# STEP 1: Identify the Failing Test Job
Write-Step "Identify failing test jobs" 1
Write-Result "Fetching PR status checks..." Info
$prResponse = gh pr view $PR --repo "$Owner/$Repo" --json 'statusCheckRollup' | ConvertFrom-Json
$allChecks = $prResponse.statusCheckRollup
$failedJobs = $allChecks | Where-Object { $_.conclusion -eq 'FAILURE' }
if (-not $failedJobs) {
Write-Result "✓ No failed jobs found" Success
Write-Host " Total checks: $($allChecks.Count)"
$allChecks | Where-Object { $_ } | ForEach-Object {
Write-Host " - $($_.name): $($_.conclusion)" -ForegroundColor $colors.Info
}
exit 0
}
Write-Result "✓ Found $($failedJobs.Count) failing job(s)" Warning
$failedJobs | Where-Object { $_.conclusion -eq 'FAILURE' } | ForEach-Object {
Write-Host " ✗ $($_.name) - $($_.conclusion)" -ForegroundColor $colors.Error
if ($_.detailsUrl) {
Write-Host " URL: $($_.detailsUrl)" -ForegroundColor $colors.Info
}
}
if ($Interactive) {
Write-Host "`nPress Enter to continue to Step 2..."
Read-Host | Out-Null
}
# STEP 2: Get Test Results
Write-Step "Download test artifacts and logs" 2
# Extract unique run IDs from failing jobs
$uniqueRuns = @()
foreach ($failedJob in $failedJobs) {
if ($failedJob.detailsUrl -match 'runs/(\d+)') {
$runId = $matches[1]
if ($runId -notin $uniqueRuns) {
$uniqueRuns += $runId
}
}
}
if ($uniqueRuns.Count -eq 0) {
Write-Result "✗ Could not extract run IDs from failing jobs" Error
exit 1
}
Write-Result "Found $($uniqueRuns.Count) run(s): $($uniqueRuns -join ', ')" Info
$artifactDir = Join-Path $OutputDir artifacts
# Check if artifacts already exist
$existingArtifacts = Get-ChildItem $artifactDir -Recurse -File -ErrorAction SilentlyContinue
if ($existingArtifacts -and -not $ForceDownload) {
Write-Result "✓ Artifacts already downloaded" Success
$existingArtifacts | ForEach-Object {
Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info
}
} else {
Write-Result "Downloading artifacts from run $($uniqueRuns[0])..." Info
gh run download $uniqueRuns[0] --dir $artifactDir --repo "$Owner/$Repo" 2>&1 | Out-Null
if (Test-Path $artifactDir) {
Write-Result "✓ Artifacts downloaded" Success
Get-ChildItem $artifactDir -Recurse -File | ForEach-Object {
Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info
}
} else {
Write-Result "✗ Failed to download artifacts" Error
exit 1
}
}
# Download individual job logs for failing jobs
Write-Result "Downloading individual job logs..." Info
$logsDir = Join-Path $OutputDir "logs"
if (-not (Test-Path $logsDir)) {
New-Item -ItemType Directory -Path $logsDir -Force | Out-Null
}
# Check if logs already exist
$existingLogs = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue
if ($existingLogs -and -not $ForceDownload) {
Write-Result "✓ Job logs already downloaded" Success
$existingLogs | ForEach-Object {
Write-Host " - $($_.Name)" -ForegroundColor $colors.Info
}
} else {
# Process each run and get its jobs
$failedJobIds = @()
foreach ($runId in $uniqueRuns) {
$runJobs = gh run view $runId --repo "$Owner/$Repo" --json jobs | ConvertFrom-Json
foreach ($failedJob in $failedJobs) {
# Check if this failed job belongs to this run
if ($failedJob.detailsUrl -match "runs/$runId/") {
$jobMatch = $runJobs.jobs | Where-Object { $_.name -eq $failedJob.name } | Select-Object -First 1
if ($jobMatch) {
$failedJobIds += @{
name = $failedJob.name
id = $jobMatch.databaseId
runId = $runId
}
}
}
}
}
# Download logs for all failed jobs
foreach ($jobInfo in $failedJobIds) {
$logFile = Join-Path $logsDir ("log-{0}.txt" -f ($jobInfo.name -replace '[^a-zA-Z0-9-]', '_'))
Write-Result " Downloading: $($jobInfo.name) (Run $($jobInfo.runId))" Info
gh run view $jobInfo.runId --log --job $jobInfo.id --repo "$Owner/$Repo" > $logFile 2>&1
}
Write-Result "✓ Job logs downloaded" Success
Get-ChildItem $logsDir -Filter "*.txt" | ForEach-Object {
Write-Host " - $($_.Name)" -ForegroundColor $colors.Info
}
}
if ($Interactive) {
Write-Host "`nPress Enter to continue to Step 3..."
Read-Host | Out-Null
}
# STEP 3: Extract Specific Failures
Write-Step "Extract test failures from XML" 3
$xmlFiles = Get-ChildItem $artifactDir -Filter "*.xml" -Recurse
if (-not $xmlFiles) {
Write-Result "✗ No test result XML files found" Error
exit 1
}
Write-Result "✓ Found $($xmlFiles.Count) test result file(s)" Success
$allFailures = @()
foreach ($xmlFile in $xmlFiles) {
Write-Result "`nParsing: $($xmlFile.Name)" Info
try {
[xml]$xml = Get-Content $xmlFile
$testResults = $xml.'test-results'
Write-Host " Total: $($testResults.total)" -ForegroundColor $colors.Info
Write-Host " Passed: $($testResults.passed)" -ForegroundColor $colors.Success
if ($testResults.failures -gt 0) {
Write-Host " Failed: $($testResults.failures)" -ForegroundColor $colors.Error
}
if ($testResults.errors -gt 0) {
Write-Host " Errors: $($testResults.errors)" -ForegroundColor $colors.Error
}
if ($testResults.skipped -gt 0) {
Write-Host " Skipped: $($testResults.skipped)" -ForegroundColor $colors.Warning
}
if ($testResults.ignored -gt 0) {
Write-Host " Ignored: $($testResults.ignored)" -ForegroundColor $colors.Warning
}
# Extract failures
$failures = $xml.SelectNodes('.//test-case[@result = "Failure"]')
foreach ($failure in $failures) {
$allFailures += @{
Name = $failure.name
File = $xmlFile.Name
Message = $failure.failure.message
StackTrace = $failure.failure.'stack-trace'
}
}
} catch {
Write-Result "✗ Error parsing XML: $_" Error
}
}
Write-Result "`n✓ Extracted $($allFailures.Count) failures total" Success
# Save failures to JSON for later analysis
$allFailures | ConvertTo-Json -Depth 10 | Out-File (Join-Path $OutputDir "failures.json")
if ($Interactive) {
Write-Host "`nPress Enter to continue to Step 4..."
Read-Host | Out-Null
}
# STEP 4: Read Error Messages
Write-Step "Analyze error messages" 4
$failuresByType = @{}
foreach ($failure in $allFailures) {
$message = $failure.Message -split "`n" | Select-Object -First 1
# Categorize failure
$type = 'Other'
if ($message -match 'Expected .* but got') { $type = 'Assertion' }
elseif ($message -match 'Cannot (find|bind)') { $type = 'Exception' }
elseif ($message -match 'timed out') { $type = 'Timeout' }
if (-not $failuresByType[$type]) {
$failuresByType[$type] = @()
}
$failuresByType[$type] += $failure
}
Write-Result "Failure breakdown:" Info
$failuresByType.GetEnumerator() | ForEach-Object {
Write-Host " $($_.Key): $($_.Value.Count)" -ForegroundColor $colors.Warning
}
Write-Result "`nTop failure messages:" Info
$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 3 | ForEach-Object {
Write-Host " [$($_.Count)x] $($_.Name -split "`n" | Select-Object -First 1)" -ForegroundColor $colors.Info
}
# Save analysis
$analysis = @{
FailuresByType = @{}
TopMessages = @()
}
$failuresByType.GetEnumerator() | ForEach-Object {
$analysis.FailuresByType[$_.Key] = $_.Value.Count
}
$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 5 | ForEach-Object {
$analysis.TopMessages += @{
Count = $_.Count
Message = ($_.Name -split "`n" | Select-Object -First 1)
}
}
$analysis | ConvertTo-Json | Out-File (Join-Path $OutputDir "analysis.json")
if ($Interactive) {
Write-Host "`nPress Enter to continue to Step 5..."
Read-Host | Out-Null
}
# STEP 5: Search Logs for Error Markers
Write-Step "Search logs for error markers" 5
$logsDir = Join-Path $OutputDir "logs"
if (-not (Test-Path $logsDir)) {
Write-Result "⚠ Logs directory not found" Warning
} else {
$logFiles = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue
if (-not $logFiles) {
Write-Result "⚠ No log files found in logs directory" Warning
} else {
Write-Result "Searching $($logFiles.Count) job log(s) for error markers ([-])" Info
Write-Result "Format: [JobName] [LineNumber] Content" Info
Write-Host ""
$allErrorLines = @()
foreach ($logFile in $logFiles) {
$jobName = $logFile.BaseName -replace '^log-', ''
$logLines = @(Get-Content $logFile)
for ($i = 0; $i -lt $logLines.Count; $i++) {
$line = $logLines[$i]
if ($line -match '\s\[-\]\s') {
$allErrorLines += @{
JobName = $jobName
LineNumber = $i + 1
Content = $line
}
}
}
}
if ($allErrorLines.Count -gt 0) {
Write-Result "✓ Found $($allErrorLines.Count) error marker line(s)" Warning
$allErrorLines | ForEach-Object {
Write-Host " [$($_.JobName)] [$($_.LineNumber)] $($_.Content)" -ForegroundColor $colors.Error
}
# Save to file
$allErrorLines | ConvertTo-Json | Out-File (Join-Path $OutputDir "error-markers.json")
Write-Result "✓ Error markers saved to error-markers.json" Success
} else {
Write-Result "✓ No error markers found in logs" Success
}
}
}
if ($Interactive) {
Write-Host "`nPress Enter to continue to Step 6..."
Read-Host | Out-Null
}
# STEP 6: Generate Recommendations
Write-Step "Generate recommendations" 6
$recommendations = @()
# Analyze patterns
if ($failuresByType['Assertion']) {
$recommendations += "Multiple assertion failures detected. These indicate test expectations don't match actual behavior."
}
if ($failuresByType['Exception']) {
$recommendations += "Exception errors found. Check test setup and prerequisites - may indicate missing files, modules, or permissions."
}
if ($failuresByType['Timeout']) {
$recommendations += "Timeout failures suggest slow or hanging operations. Consider network issues or resource constraints on CI."
}
# Check for patterns in failure messages
$failureMessages = $allFailures.Message -join "`n"
if ($failureMessages -match 'PackageManagement') {
$recommendations += "PackageManagement module issues detected. Verify module availability and help repository access."
}
if ($failureMessages -match 'Update-Help') {
$recommendations += "Update-Help failures detected. Check network connectivity to help repository and help installation paths."
}
Write-Result "`n📋 Recommendations:" Info
if ($recommendations) {
$recommendations | ForEach-Object { Write-Host " • $_" -ForegroundColor $colors.Info }
} else {
Write-Host " • Review failures in detail" -ForegroundColor $colors.Info
Write-Host " • Check if test changes are needed" -ForegroundColor $colors.Info
Write-Host " • Consider environment-specific issues" -ForegroundColor $colors.Info
}
$recommendations | Out-File (Join-Path $OutputDir "recommendations.txt")
# Summary
Write-Host "`n=== Analysis Complete ===" -ForegroundColor $colors.Step
Write-Host "Results saved to: $OutputDir" -ForegroundColor $colors.Info
Write-Host " - failures.json (detailed failure data)" -ForegroundColor $colors.Info
Write-Host " - analysis.json (summary analysis)" -ForegroundColor $colors.Info
Write-Host " - recommendations.txt (suggested fixes)" -ForegroundColor $colors.Info
Write-Host " - error-markers.json (error markers from logs)" -ForegroundColor $colors.Info
Write-Host " - logs/ (individual job log files)" -ForegroundColor $colors.Info
Write-Host " - artifacts/ (downloaded test artifacts)" -ForegroundColor $colors.Info
Write-Host "`nNext steps:" -ForegroundColor $colors.Step
Write-Host "1. Review recommendations.txt for analysis" -ForegroundColor $colors.Info
Write-Host "2. Examine failures.json for detailed error messages" -ForegroundColor $colors.Info
Write-Host "3. Check error-markers.json for specific test failures in logs" -ForegroundColor $colors.Info
Write-Host "4. Review individual job logs in logs/ directory for contextual details" -ForegroundColor $colors.Info
Write-Host "`n"
+3 -3
View File
@@ -37,7 +37,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
fetch-depth: '0'
@@ -47,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -74,4 +74,4 @@ jobs:
shell: pwsh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
# You can define any steps you want, and they will run before the agent starts.
# If you do not check out your code, Copilot will do this for you.
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
fetch-depth: 1000
+2 -2
View File
@@ -17,6 +17,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: 'Dependency Review'
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: Check out the repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Verify PR has label starting with 'cl-'
id: verify-labels
+13 -9
View File
@@ -57,7 +57,7 @@ jobs:
packagingChanged: ${{ steps.filter.outputs.packagingChanged }}
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false
@@ -76,7 +76,7 @@ jobs:
contents: read
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Check for merge conflict markers
uses: "./.github/actions/infrastructure/merge-conflict-checker"
@@ -88,7 +88,7 @@ jobs:
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
@@ -103,7 +103,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Linux Unelevated CI
@@ -111,6 +111,7 @@ jobs:
with:
purpose: UnelevatedPesterTests
tagSet: CI
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
linux_test_elevated_ci:
name: Linux Elevated CI
needs:
@@ -120,7 +121,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Linux Elevated CI
@@ -128,6 +129,7 @@ jobs:
with:
purpose: ElevatedPesterTests
tagSet: CI
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
linux_test_unelevated_others:
name: Linux Unelevated Others
needs:
@@ -137,7 +139,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Linux Unelevated Others
@@ -145,6 +147,7 @@ jobs:
with:
purpose: UnelevatedPesterTests
tagSet: Others
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
linux_test_elevated_others:
name: Linux Elevated Others
needs:
@@ -154,7 +157,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Linux Elevated Others
@@ -162,6 +165,7 @@ jobs:
with:
purpose: ElevatedPesterTests
tagSet: Others
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xunit_tests:
name: xUnit Tests
needs:
@@ -177,7 +181,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1
@@ -251,7 +255,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Linux Packaging
+14 -9
View File
@@ -54,9 +54,10 @@ jobs:
outputs:
source: ${{ steps.filter.outputs.source }}
buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }}
packagingChanged: ${{ steps.filter.outputs.packagingChanged }}
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Change Detection
id: filter
@@ -71,7 +72,7 @@ jobs:
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Build
@@ -85,7 +86,7 @@ jobs:
runs-on: macos-15-large
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: macOS Unelevated CI
@@ -93,6 +94,7 @@ jobs:
with:
purpose: UnelevatedPesterTests
tagSet: CI
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
macos_test_elevated_ci:
name: macOS Elevated CI
needs:
@@ -102,7 +104,7 @@ jobs:
runs-on: macos-15-large
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: macOS Elevated CI
@@ -110,6 +112,7 @@ jobs:
with:
purpose: ElevatedPesterTests
tagSet: CI
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
macos_test_unelevated_others:
name: macOS Unelevated Others
needs:
@@ -119,7 +122,7 @@ jobs:
runs-on: macos-15-large
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: macOS Unelevated Others
@@ -127,6 +130,7 @@ jobs:
with:
purpose: UnelevatedPesterTests
tagSet: Others
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
macos_test_elevated_others:
name: macOS Elevated Others
needs:
@@ -136,7 +140,7 @@ jobs:
runs-on: macos-15-large
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: macOS Elevated Others
@@ -144,6 +148,7 @@ jobs:
with:
purpose: ElevatedPesterTests
tagSet: Others
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xunit_tests:
name: xUnit Tests
needs:
@@ -157,12 +162,12 @@ jobs:
name: macOS packaging and testing
needs:
- changes
if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }}
if: ${{ needs.changes.outputs.packagingChanged == 'true' }}
runs-on:
- macos-15-large
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- uses: actions/setup-dotnet@v5
@@ -224,7 +229,7 @@ jobs:
testResultsFolder: "${{ runner.workspace }}/testResults"
- name: Upload package artifact
if: always()
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: macos-package
path: "*.pkg"
+3 -3
View File
@@ -32,7 +32,7 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
@@ -59,7 +59,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: SARIF file
path: results.sarif
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5
with:
sarif_file: results.sarif
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Verify markdown links
id: verify
+10 -6
View File
@@ -60,7 +60,7 @@ jobs:
packagingChanged: ${{ steps.filter.outputs.packagingChanged }}
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Change Detection
id: filter
@@ -75,7 +75,7 @@ jobs:
runs-on: windows-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Build
@@ -89,7 +89,7 @@ jobs:
runs-on: windows-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Windows Unelevated CI
@@ -97,6 +97,7 @@ jobs:
with:
purpose: UnelevatedPesterTests
tagSet: CI
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
windows_test_elevated_ci:
name: Windows Elevated CI
needs:
@@ -106,7 +107,7 @@ jobs:
runs-on: windows-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Windows Elevated CI
@@ -114,6 +115,7 @@ jobs:
with:
purpose: ElevatedPesterTests
tagSet: CI
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
windows_test_unelevated_others:
name: Windows Unelevated Others
needs:
@@ -123,7 +125,7 @@ jobs:
runs-on: windows-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Windows Unelevated Others
@@ -131,6 +133,7 @@ jobs:
with:
purpose: UnelevatedPesterTests
tagSet: Others
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
windows_test_elevated_others:
name: Windows Elevated Others
needs:
@@ -140,7 +143,7 @@ jobs:
runs-on: windows-latest
steps:
- name: checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
- name: Windows Elevated Others
@@ -148,6 +151,7 @@ jobs:
with:
purpose: ElevatedPesterTests
tagSet: Others
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
xunit_tests:
name: xUnit Tests
needs:
@@ -36,7 +36,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
@@ -81,7 +81,7 @@ jobs:
- name: Upload Build Artifacts
if: always()
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
with:
name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }}
path: |
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ inputs.runner_os }}
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 1000
@@ -46,7 +46,7 @@ jobs:
Write-Host "Completed xUnit test run."
- name: Upload xUnit results
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@v7
if: always()
with:
name: ${{ inputs.test_results_artifact_name }}
+3
View File
@@ -119,5 +119,8 @@ assets/manpage/*.gz
tmp/*
.env.local
# Pester test failure analysis results (generated by analyze-pr-test-failures.ps1)
**/pester-analysis-*/
# Ignore CTRF report files
crtf/*
+4
View File
@@ -510,6 +510,10 @@ dotnet_diagnostic.CA1846.severity = warning
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1847
dotnet_diagnostic.CA1847.severity = warning
# CA1852: Seal internal types
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852
dotnet_diagnostic.CA1852.severity = warning
# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)'
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1853
dotnet_diagnostic.CA1853.severity = warning
+4 -1
View File
@@ -22,7 +22,7 @@ variables:
BuildSolution: $(Build.SourcesDirectory)\dirs.proj
ReleaseTagVar: ${{ parameters.ReleaseTagVar }}
BuildConfiguration: Release
WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest'
WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest'
Codeql.Enabled: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack
DOTNET_CLI_TELEMETRY_OPTOUT: 1
POWERSHELL_TELEMETRY_OPTOUT: 1
@@ -46,6 +46,9 @@ resources:
extends:
template: v2/Microsoft.Official.yml@onebranchTemplates
parameters:
featureFlags:
WindowsHostVersion:
Version: 2022
platform:
name: 'windows_undocked' # windows undocked
@@ -29,11 +29,8 @@ parameters:
displayName: Debugging - Enable CodeQL and set cadence to 1 hour
type: boolean
default: false
- name: OfficialBuild
type: boolean
default: false
name: bins-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId)
name: bins-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId)
resources:
repositories:
@@ -71,7 +68,7 @@ variables:
- name: LinuxContainerImage
value: mcr.microsoft.com/onebranch/azurelinux/build:3.0
- name: WindowsContainerImage
value: onebranch.azurecr.io/windows/ltsc2019/vse2022:latest
value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest
- name: CDP_DEFINITION_BUILD_COUNT
value: $[counter('', 0)]
- name: ReleaseTagVar
@@ -91,8 +88,6 @@ variables:
value: true
${{ else }}:
value: false
- name: templateFile
value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }}
# Fix for BinSkim ICU package error in Linux containers
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
value: true
@@ -100,16 +95,17 @@ variables:
- name: ob_sdl_binskim_enabled
value: false
- name: ps_official_build
value: ${{ parameters.OfficialBuild }}
value: true
extends:
template: ${{ variables.templateFile }}
template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates
parameters:
customTags: 'ES365AIMigrationTooling'
featureFlags:
LinuxHostVersion:
Network: KS3
WindowsHostVersion:
Version: 2022
Network: KS3
incrementalSDLBinaryAnalysis: true
globalSdl:
+3 -8
View File
@@ -24,14 +24,11 @@ parameters: # parameters are shown up in ADO UI in a build queue time
displayName: Skip Signing
type: string
default: 'NO'
- name: OfficialBuild
type: boolean
default: false
- name: disableNetworkIsolation
type: boolean
default: false
name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId)
name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId)
variables:
- name: CDP_DEFINITION_BUILD_COUNT
@@ -67,8 +64,6 @@ variables:
- name: branchCounter
value: $[counter(variables['branchCounterKey'], 1)]
- group: MSIXSigningProfile
- name: templateFile
value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }}
- name: disableNetworkIsolation
value: ${{ parameters.disableNetworkIsolation }}
@@ -89,7 +84,7 @@ resources:
ref: refs/heads/main
extends:
template: ${{ variables.templateFile }}
template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates
parameters:
cloudvault:
enabled: false
@@ -294,7 +289,7 @@ extends:
jobs:
- template: /.pipelines/templates/package-create-msix.yml@self
parameters:
OfficialBuild: ${{ parameters.OfficialBuild }}
OfficialBuild: true
- stage: upload
displayName: 'Upload'
@@ -13,11 +13,8 @@ parameters: # parameters are shown up in ADO UI in a build queue time
displayName: Skip Signing
type: string
default: 'NO'
- name: OfficialBuild
type: boolean
default: false
name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId)
name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId)
variables:
- name: CDP_DEFINITION_BUILD_COUNT
@@ -49,8 +46,6 @@ variables:
- name: LinuxContainerImage
value: mcr.microsoft.com/onebranch/azurelinux/build:3.0
- group: PoolNames
- name: templateFile
value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }}
resources:
repositories:
@@ -72,7 +67,7 @@ resources:
- releases/*
extends:
template: ${{ variables.templateFile }}
template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates
parameters:
featureFlags:
WindowsHostVersion:
+3 -8
View File
@@ -29,11 +29,8 @@ parameters: # parameters are shown up in ADO UI in a build queue time
displayName: Skip MSIX Publish
type: boolean
default: false
- name: OfficialBuild
type: boolean
default: false
name: release-$(BUILD.SOURCEBRANCHNAME)-prod.${{ parameters.OfficialBuild }}-$(Build.BuildId)
name: release-$(BUILD.SOURCEBRANCHNAME)-prod.true-$(Build.BuildId)
variables:
- name: CDP_DEFINITION_BUILD_COUNT
@@ -65,10 +62,8 @@ variables:
- name: ReleaseTagVar
value: ${{ parameters.ReleaseTagVar }}
- group: PoolNames
- name: templateFile
value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }}
- name: releaseEnvironment
value: ${{ iif ( parameters.OfficialBuild, 'Production', 'Test' ) }}
value: 'Production'
# Fix for BinSkim ICU package error in Linux containers
- name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT
value: true
@@ -97,7 +92,7 @@ resources:
- releases/*
extends:
template: ${{ variables.templateFile }}
template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates
parameters:
release:
category: NonAzure
+3 -8
View File
@@ -1,9 +1,6 @@
trigger: none
parameters: # parameters are shown up in ADO UI in a build queue time
- name: OfficialBuild
type: boolean
default: true
- name: 'createVPack'
displayName: 'Create and Submit VPack'
type: boolean
@@ -33,7 +30,7 @@ parameters: # parameters are shown up in ADO UI in a build queue time
- Netlock
default: "R1"
name: vPack_$(Build.SourceBranchName)_Prod.${{ parameters.OfficialBuild }}_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr)
name: vPack_$(Build.SourceBranchName)_Prod.true_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr)
variables:
- name: CDP_DEFINITION_BUILD_COUNT
@@ -45,7 +42,7 @@ variables:
- name: BuildConfiguration
value: Release
- name: WindowsContainerImage
value: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest'
value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest'
- name: Codeql.Enabled
value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack
- name: DOTNET_CLI_TELEMETRY_OPTOUT
@@ -58,8 +55,6 @@ variables:
value: ${{ parameters.ReleaseTagVar }}
- group: Azure Blob variable group
- group: certificate_logical_to_actual # used within signing task
- name: templateFile
value: ${{ iif ( parameters.OfficialBuild, 'v2/Microsoft.Official.yml@onebranchTemplates', 'v2/Microsoft.NonOfficial.yml@onebranchTemplates' ) }}
- group: DotNetPrivateBuildAccess
- group: certificate_logical_to_actual
- name: netiso
@@ -75,7 +70,7 @@ resources:
ref: refs/heads/main
extends:
template: ${{ variables.templateFile }}
template: v2/Microsoft.Official.yml@onebranchTemplates
parameters:
platform:
name: 'windows_undocked' # windows undocked
+4 -4
View File
@@ -2,6 +2,10 @@ steps:
- pwsh: |
# Determine LTS, Preview, or Stable
$metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json
$LTS = $metadata.LTSRelease.PublishToChannels
$Stable = $metadata.StableRelease.PublishToChannels
$isPreview = '$(OutputReleaseTag.releaseTag)' -match '-'
$releaseTag = '$(OutputReleaseTag.releaseTag)'
# Rebuild branches should be treated as preview builds
@@ -11,10 +15,6 @@ steps:
# If you update this regex, also update it in rebuild-branch-check.yml to keep them in sync.
$isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.'
$LTS = $metadata.LTSRelease.Latest
$Stable = $metadata.StableRelease.Latest
$isPreview = $releaseTag -match '-'
# If this is a rebuild branch, force preview mode and ignore LTS metadata
if ($isRebuildBranch) {
$IsLTS = $false
@@ -4,8 +4,6 @@
jobs:
- job: APIScan
variables:
- name: runCodesignValidationInjection
value : false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: ReleaseTagVar
@@ -10,8 +10,6 @@ parameters:
jobs:
- job: generateNotice
variables:
- name: runCodesignValidationInjection
value : false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: ob_outputDirectory
@@ -57,7 +55,7 @@ jobs:
- task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0
displayName: 'Component Detection'
inputs:
sourceScanPath: '$(repoRoot)\tools'
sourceScanPath: '$(repoRoot)\tools\cgmanifest\tpn'
- pwsh: |
$(repoRoot)/tools/clearlyDefined/ClearlyDefined.ps1 -TestAndHarvest
+5 -6
View File
@@ -13,8 +13,6 @@ jobs:
type: linux
variables:
- name: runCodesignValidationInjection
value: false
- name: nugetMultiFeedWarnLevel
value: none
- name: NugetSecurityAnalysisWarningLevel
@@ -157,10 +155,6 @@ jobs:
Write-Verbose -Verbose "LTS: $LTS"
if ($LTS) {
Write-Verbose -Message "LTS Release: $LTS"
}
if (-not (Test-Path $(ob_outputDirectory))) {
New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force
}
@@ -170,6 +164,11 @@ jobs:
Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath
if ($LTS) {
Write-Verbose -Message "LTS Release: $LTS" -Verbose
Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS
}
$vstsCommandString = "vso[task.setvariable variable=PackageFilter]$pkgFilter"
Write-Host ("sending " + $vstsCommandString)
Write-Host "##$vstsCommandString"
-4
View File
@@ -10,8 +10,6 @@ jobs:
pool:
type: linux
variables:
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: DOTNET_NOLOGO
@@ -139,8 +137,6 @@ jobs:
pool:
type: windows
variables:
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: DOTNET_NOLOGO
+11 -4
View File
@@ -15,8 +15,6 @@ jobs:
variables:
- name: HOMEBREW_NO_ANALYTICS
value: 1
- name: runCodesignValidationInjection
value: false
- name: nugetMultiFeedWarnLevel
value: none
- name: NugetSecurityAnalysisWarningLevel
@@ -104,6 +102,10 @@ jobs:
Restore-PSOptions -PSOptionsPath "$psoptionsPath"
Get-PSOptions | Write-Verbose -Verbose
if (-not (Test-Path "$repoRoot/tools/metadata.json")) {
throw "metadata.json not found in $repoRoot/tools"
}
$metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json
Write-Verbose -Verbose "metadata:"
@@ -122,14 +124,19 @@ jobs:
Write-Verbose -Verbose "LTS: $LTS"
if ($LTS) {
Write-Verbose -Message "LTS Release: $LTS"
Write-Verbose -Message "LTS Release: $LTS" -Verbose
}
Start-PSBootstrap -Scenario Package
$macosRuntime = "osx-$buildArch"
Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS
Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath
if ($LTS) {
Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS
}
$pkgNameFilter = "powershell-*$macosRuntime.pkg"
Write-Verbose -Verbose "Looking for pkg packages with filter: $pkgNameFilter in '$(Pipeline.Workspace)' to upload..."
$pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File
-2
View File
@@ -13,8 +13,6 @@ jobs:
variables:
- name: HOMEBREW_NO_ANALYTICS
value: 1
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- group: DotNetPrivateBuildAccess
+6 -8
View File
@@ -6,8 +6,6 @@ jobs:
type: windows
variables:
- name: runCodesignValidationInjection
value: false
- name: nugetMultiFeedWarnLevel
value: none
- name: NugetSecurityAnalysisWarningLevel
@@ -119,13 +117,13 @@ jobs:
Start-PSBuild -Clean -Runtime linux-x64 -Configuration Release -ReleaseTag $(ReleaseTagVar)
$sharedModules | Foreach-Object {
$refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\$_.dll"
$refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net11.0\refint\$_.dll"
Write-Verbose -Verbose "RefAssembly: $refFile"
Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose
$refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml"
$refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml"
if (-not (Test-Path $refDoc)) {
Write-Warning "$refDoc not found"
Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0\" | Out-String | Write-Verbose -Verbose
Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.0\" | Out-String | Write-Verbose -Verbose
}
else {
Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose
@@ -135,13 +133,13 @@ jobs:
Start-PSBuild -Clean -Runtime win7-x64 -Configuration Release -ReleaseTag $(ReleaseTagVar)
$winOnlyModules | Foreach-Object {
$refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\*.dll"
$refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net11.0\refint\*.dll"
Write-Verbose -Verbose 'RefAssembly: $refFile'
Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose
$refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml"
$refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml"
if (-not (Test-Path $refDoc)) {
Write-Warning "$refDoc not found"
Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0" | Out-String | Write-Verbose -Verbose
Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.0" | Out-String | Write-Verbose -Verbose
}
else {
Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose
@@ -9,8 +9,6 @@ jobs:
type: windows
variables:
- name: runCodesignValidationInjection
value: false
- name: ob_sdl_codeSignValidation_enabled
value: false # Skip signing validation in build-only stage
- name: ob_signing_setup_enabled
+12 -3
View File
@@ -59,8 +59,15 @@ jobs:
$json.listings.'en-us'.baseListing.releaseNotes = $message
# Add PowerShell version to the top of the description
$description = $json.listings.'en-us'.baseListing.description
$version = "$(ReleaseTag)"
$updatedDescription = "Version: $version`n`n$description"
$json.listings.'en-us'.baseListing.description = $updatedDescription
Write-Verbose -Verbose "Updated description: $updatedDescription"
$json | ConvertTo-Json -Depth 100 | Set-Content $jsonPath -Encoding UTF8
displayName: 'Update Release Notes in JSON'
displayName: 'Add Changelog Link and Version Number to SBJSON'
- task: PowerShell@2
inputs:
@@ -100,7 +107,8 @@ jobs:
- task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3
displayName: 'Publish StoreBroker Package (Stable/LTS)'
condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), or(eq('$(STABLE)', 'true'), eq('$(LTS)', 'true')))
condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')))
continueOnError: true
inputs:
serviceEndpoint: 'StoreAppPublish-Stable'
appId: '$(AppID)'
@@ -113,7 +121,8 @@ jobs:
- task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3
displayName: 'Publish StoreBroker Package (Preview)'
condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq('$(PREVIEW)', 'true'))
condition: and(ne('${{ parameters.skipMSIXPublish }}', 'true'), eq(variables['PREVIEW'], 'true'))
continueOnError: true
inputs:
serviceEndpoint: 'StoreAppPublish-Preview'
appId: '$(AppID)'
@@ -35,6 +35,14 @@ jobs:
targetType: inline
script: |
$Path = "$(Pipeline.Workspace)/GitHubPackages"
# The .exe packages are for Windows Update only and should not be uploaded to GitHub release.
$exefiles = Get-ChildItem -Path $Path -Filter *.exe
if ($exefiles) {
Write-Verbose -Verbose "Remove .exe packages:"
$exefiles | Remove-Item -Force -Verbose
}
$OutputPath = Join-Path $Path 'hashes.sha256'
$packages = Get-ChildItem -Path $Path -Include * -Recurse -File
$checksums = $packages |
@@ -33,7 +33,7 @@ stages:
- template: release-SetReleaseTagandContainerName.yml
parameters:
restorePhase: true
- pwsh: |
$packageVersion = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant() -replace '^v',''
$vstsCommandString = "vso[task.setvariable variable=packageVersion]$packageVersion"
@@ -42,7 +42,7 @@ stages:
displayName: Set Package version
env:
ob_restore_phase: true
- pwsh: |
$branch = 'mirror-target'
$gitArgs = "clone",
@@ -151,7 +151,7 @@ stages:
$metadataHash = @{}
$skipPublishValue = '${{ parameters.skipPublish }}'
$metadataHash["ReleaseTag"] = '$(OutputReleaseTag.ReleaseTag)'
$metadataHash["LTS"] = $metadata.LTSRelease.Latest
$metadataHash["LTS"] = $metadata.LTSRelease.PublishToChannels
$metadataHash["ForProduction"] = $true
$metadataHash["SkipPublish"] = [System.Convert]::ToBoolean($skipPublishValue)
@@ -222,7 +222,7 @@ stages:
files_to_sign: '*.ps1'
search_root: '$(repoRoot)/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run'
displayName: Sign Run.ps1
- pwsh: |
# folder to tar must have: Run.ps1, settings.toml, python_dl
$srcPath = Join-Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'Shell'
-2
View File
@@ -10,8 +10,6 @@ jobs:
pool:
type: windows
variables:
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: DOTNET_NOLOGO
@@ -14,8 +14,6 @@ jobs:
demands:
- ImageOverride -equals PSMMS2019-Secure
variables:
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: DOTNET_NOLOGO
@@ -58,8 +56,11 @@ jobs:
$dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind)
$metadata = Get-Content -LiteralPath "$toolsDirectory/metadata.json" -ErrorAction Stop | ConvertFrom-Json
$stableRelease = $metadata.StableRelease.Latest
$ltsRelease = $metadata.LTSRelease.Latest
$stableReleaseTag = $metadata.StableReleaseTag -Replace 'v',''
$currentReleaseTag = $buildInfo.ReleaseTag -Replace 'v',''
$stableRelease = $metadata.StableRelease.PublishToChannels
$ltsRelease = $metadata.LTSRelease.PublishToChannels
Write-Verbose -Verbose "Writing $jsonFile contents:"
$buildInfoJsonContent = Get-Content $jsonFile -Encoding UTF8NoBom -Raw
@@ -67,40 +68,44 @@ jobs:
$buildInfo = $buildInfoJsonContent | ConvertFrom-Json
$buildInfo.ReleaseDate = $dateTime
$currentReleaseTag = $buildInfo.ReleaseTag -Replace 'v',''
$targetFile = "$ENV:PIPELINE_WORKSPACE/$fileName"
ConvertTo-Json -InputObject $buildInfo | Out-File $targetFile -Encoding ascii
if ($stableRelease -or $fileName -eq "preview.json") {
Set-BuildVariable -Name CopyMainBuildInfo -Value YES
if ($fileName -eq "preview.json") {
Set-BuildVariable -Name UploadPreview -Value YES
} else {
Set-BuildVariable -Name CopyMainBuildInfo -Value NO
Set-BuildVariable -Name UploadPreview -Value NO
}
Set-BuildVariable -Name BuildInfoJsonFile -Value $targetFile
## Create 'lts.json' if it's the latest stable and also a LTS release.
Set-BuildVariable -Name PreviewBuildInfoFile -Value $targetFile
## Create 'lts.json' if marked as a LTS release.
if ($fileName -eq "stable.json") {
[System.Management.Automation.SemanticVersion] $stableVersion = $stableReleaseTag
[System.Management.Automation.SemanticVersion] $currentVersion = $currentReleaseTag
if ($ltsRelease) {
$ltsFile = "$ENV:PIPELINE_WORKSPACE/lts.json"
Copy-Item -Path $targetFile -Destination $ltsFile -Force
Set-BuildVariable -Name LtsBuildInfoJsonFile -Value $ltsFile
Set-BuildVariable -Name CopyLTSBuildInfo -Value YES
Set-BuildVariable -Name LTSBuildInfoFile -Value $ltsFile
Set-BuildVariable -Name UploadLTS -Value YES
} else {
Set-BuildVariable -Name CopyLTSBuildInfo -Value NO
Set-BuildVariable -Name UploadLTS -Value NO
}
$releaseTag = $buildInfo.ReleaseTag
$version = $releaseTag -replace '^v'
$semVersion = [System.Management.Automation.SemanticVersion] $version
## Only update the stable.json if the current version is greater than the stable version.
if ($currentVersion -gt $stableVersion) {
$versionFile = "$ENV:PIPELINE_WORKSPACE/$($currentVersion.Major)-$($currentVersion.Minor).json"
Copy-Item -Path $targetFile -Destination $versionFile -Force
Set-BuildVariable -Name StableBuildInfoFile -Value $versionFile
Set-BuildVariable -Name UploadStable -Value YES
} else {
Set-BuildVariable -Name UploadStable -Value NO
}
$versionFile = "$ENV:PIPELINE_WORKSPACE/$($semVersion.Major)-$($semVersion.Minor).json"
Copy-Item -Path $targetFile -Destination $versionFile -Force
Set-BuildVariable -Name VersionBuildInfoJsonFile -Value $versionFile
Set-BuildVariable -Name CopyVersionBuildInfo -Value YES
} else {
Set-BuildVariable -Name CopyVersionBuildInfo -Value NO
Set-BuildVariable -Name UploadStable -Value NO
}
displayName: Create json files
@@ -118,24 +123,27 @@ jobs:
$storageContext = New-AzStorageContext -StorageAccountName $storageAccount -UseConnectedAccount
if ($env:CopyMainBuildInfo -eq 'YES') {
$jsonFile = "$env:BuildInfoJsonFile"
#preview
if ($env:UploadPreview -eq 'YES') {
$jsonFile = "$env:PreviewBuildInfoFile"
$blobName = Get-Item $jsonFile | Split-Path -Leaf
Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName"
Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force
}
if ($env:CopyLTSBuildInfo -eq 'YES') {
$jsonFile = "$env:LtsBuildInfoJsonFile"
#LTS
if ($env:UploadLTS -eq 'YES') {
$jsonFile = "$env:LTSBuildInfoFile"
$blobName = Get-Item $jsonFile | Split-Path -Leaf
Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName"
Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force
}
if ($env:CopyVersionBuildInfo -eq 'YES') {
$jsonFile = "$env:VersionBuildInfoJsonFile"
#stable
if ($env:UploadStable -eq 'YES') {
$jsonFile = "$env:StableBuildInfoFile"
$blobName = Get-Item $jsonFile | Split-Path -Leaf
Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName"
Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force
}
condition: and(succeeded(), eq(variables['CopyMainBuildInfo'], 'YES'))
condition: and(succeeded(), or(eq(variables['UploadPreview'], 'YES'), eq(variables['UploadLTS'], 'YES'), eq(variables['UploadStable'], 'YES')))
-4
View File
@@ -1,8 +1,6 @@
jobs:
- job: build_testartifacts_win
variables:
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- group: DotNetPrivateBuildAccess
@@ -73,8 +71,6 @@ jobs:
- job: build_testartifacts_nonwin
variables:
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- group: DotNetPrivateBuildAccess
-2
View File
@@ -7,8 +7,6 @@ jobs:
variables:
- name: ob_sdl_sbom_enabled
value: true
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: DOTNET_NOLOGO
@@ -17,8 +17,6 @@ variables:
value: false
- name: ob_sdl_sbom_enabled
value: ${{ parameters.SBOM }}
- name: runCodesignValidationInjection
value: false
- name: DOTNET_NOLOGO
value: 1
- group: 'mscodehub-code-read-akv'
@@ -10,8 +10,6 @@ jobs:
pool:
type: windows
variables:
- name: runCodesignValidationInjection
value: false
- name: NugetSecurityAnalysisWarningLevel
value: none
- name: DOTNET_NOLOGO
@@ -276,7 +274,7 @@ jobs:
)
$sourceModulePath = Join-Path '$(GlobalToolArtifactPath)' 'publish' 'PowerShell.Windows.x64' 'release' 'Modules'
$destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' 'modules'
$destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.0' 'any' 'modules'
$modulesToCopy | ForEach-Object {
$modulePath = Join-Path $sourceModulePath $_
@@ -284,7 +282,7 @@ jobs:
}
# Copy ref assemblies
Copy-Item '$(Pipeline.Workspace)/Symbols_$(Architecture)/ref' "$outputPath\temp\tools\net10.0\any\ref" -Recurse -Force
Copy-Item '$(Pipeline.Workspace)/Symbols_$(Architecture)/ref' "$outputPath\temp\tools\net11.0\any\ref" -Recurse -Force
$contentPath = Join-Path "$outputPath\temp" 'content'
$contentFilesPath = Join-Path "$outputPath\temp" 'contentFiles'
@@ -292,14 +290,14 @@ jobs:
Remove-Item -Path $contentPath,$contentFilesPath -Recurse -Force
# remove PDBs to reduce the size of the nupkg
Remove-Item -Path "$outputPath\temp\tools\net10.0\any\*.pdb" -Recurse -Force
Remove-Item -Path "$outputPath\temp\tools\net11.0\any\*.pdb" -Recurse -Force
# create powershell.config.json
$config = [ordered]@{}
$config.Add("Microsoft.PowerShell:ExecutionPolicy", "RemoteSigned")
$config.Add("WindowsPowerShellCompatibilityModuleDenyList", @("PSScheduledJob", "BestPractices", "UpdateServices"))
$configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' "powershell.config.json"
$configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.0' 'any' "powershell.config.json"
Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop
Compress-Archive -Path "$outputPath\temp\*" -DestinationPath "$outputPath\$nupkgName" -Force
+1 -1
View File
@@ -34,7 +34,7 @@ pr:
- .vsts-ci/misc-analysis.yml
- .vsts-ci/windows.yml
- .vsts-ci/windows/*
- tools/cgmanifest.json
- tools/cgmanifest/*
- LICENSE.txt
- test/common/markdown/*
- test/perf/*
+1 -1
View File
@@ -34,7 +34,7 @@ pr:
- .vsts-ci/misc-analysis.yml
- .vsts-ci/windows.yml
- .vsts-ci/windows/*
- tools/cgmanifest.json
- tools/cgmanifest/*
- LICENSE.txt
- test/common/markdown/*
- test/perf/*
+1 -1
View File
@@ -34,7 +34,7 @@ pr:
- .github/ISSUE_TEMPLATE/*
- .github/workflows/*
- .vsts-ci/misc-analysis.yml
- tools/cgmanifest.json
- tools/cgmanifest/*
- LICENSE.txt
- test/common/markdown/*
- test/perf/*
+1 -1
View File
@@ -28,7 +28,7 @@ pr:
- .dependabot/config.yml
- .github/ISSUE_TEMPLATE/*
- .vsts-ci/misc-analysis.yml
- tools/cgmanifest.json
- tools/cgmanifest/*
- LICENSE.txt
- test/common/markdown/*
- test/perf/*
+77
View File
@@ -1,5 +1,82 @@
# 7.4 Changelog
## [7.4.14]
### General Cmdlet Updates and Fixes
- Fix `PSMethodInvocationConstraints.GetHashCode` method (#26959)
### Tools
- Add merge conflict marker detection to `linux-ci` workflow and refactor existing actions to use reusable `get-changed-files` action (#26362)
- Add reusable `get-changed-files` action and refactor existing actions (#26361)
- Refactor analyze job to reusable workflow and enable on Windows CI (#26342)
### Tests
- Skip the flaky `Update-Help` test for the `PackageManagement` module (#26871)
- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26869)
- Add GitHub Actions annotations for Pester test failures (#26800)
- Mark flaky `Update-Help` web tests as pending to unblock CI (#26805)
- Update the `Update-Help` tests to use `-Force` to remove read-only files (#26786)
- Fix merge conflict checker for empty file lists and filter `*.cs` files (#26387)
- Add markdown link verification for PRs (#26340)
### Build and Packaging Improvements
<details>
<summary>
<p>Update .NET SDK to 8.0.419</p>
</summary>
<ul>
<li>Update MaxVisitCount and MaxHashtableKeyCount if visitor safe value context indicates SkipLimitCheck is true (Internal 38882)</li>
<li>Hardcode Official templates (#26962)</li>
<li>Split TPN manifest and Component Governance manifest (#26961)</li>
<li>Correct the package name for .deb and .rpm packages (#26960)</li>
<li>Bring over all changes for MSIX packaging template (#26933)</li>
<li>.NET Resolution and Store Publishing Updates (#26930)</li>
<li>Update Application Insights package version to 2.23.0 (#26883)</li>
<li>Update metadata.json to update the Latest attribute with a better name (#26872)</li>
<li>Update <code>Get-ChangeLog</code> to handle backport PRs correctly (#26870)</li>
<li>Remove unused runCodesignValidationInjection variable from pipeline templates (#26868)</li>
<li>Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26864)</li>
<li>Fix buildinfo.json uploading for preview, LTS, and stable releases (#26863)</li>
<li>Fix macOS preview package identifier detection to use version string (#26774)</li>
<li>Update the macOS package name for preview releases to match the previous pattern (#26435)</li>
<li>Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26434)</li>
<li>Fix template path for rebuild branch check in package.yml (#26433)</li>
<li>Add rebuild branch support with conditional MSIX signing (#26418)</li>
<li>Move package validation to package pipeline (#26417)</li>
<li>Backport Store publishing improvements (#26401)</li>
<li>Fix path to metadata.json in channel selection script (#26399)</li>
<li>Optimize/split Windows package signing (#26413)</li>
<li>Improve ADO package build and validation across platforms (#26405)</li>
<li>Separate Store Automation Service Endpoints, Resolve AppID (#26396)</li>
<li>Fix the task name to not use the pre-release task (#26395)</li>
<li>Remove usage of fpm for DEB package generation (#26382)</li>
<li>Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26344)</li>
<li>Replace fpm with native rpmbuild for RPM package generation (#26337)</li>
<li>Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26363)</li>
<li>Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26336)</li>
<li>Integrate Windows packaging into windows-ci workflow using reusable workflow (#26335)</li>
<li>Add network isolation policy parameter to vPack pipeline (#26339)</li>
<li>GitHub Workflow cleanup (#26334)</li>
<li>Add build to vPack Pipeline (#25980)</li>
<li>Update vPack name (#26222)</li>
</ul>
</details>
### Documentation and Help Content
- Update Third Party Notices (#26892)
[7.4.14]: https://github.com/PowerShell/PowerShell/compare/v7.4.13...v7.4.14
## [7.4.13]
### Build and Packaging Improvements
+85
View File
@@ -1,5 +1,90 @@
# 7.5 Changelog
## [7.5.5]
### Engine Updates and Fixes
- Fix up `SSHConnectionInfo` ssh PATH checks (#26165) (Thanks @jborean93!)
### General Cmdlet Updates and Fixes
- Close pipe client handles after creating the child ssh process (#26822)
- Fix the progress preference variable in script cmdlets (#26791) (Thanks @cmkb3!)
### Tools
- Add merge conflict marker detection to `linux-ci` workflow and refactor existing actions to use reusable `get-changed-files` action (#26812)
- Add reusable `get-changed-files` action and refactor existing actions (#26811)
- Create GitHub Copilot setup workflow (#26807)
- Refactor analyze job to reusable workflow and enable on Windows CI (#26799)
### Tests
- Mark flaky `Update-Help` web tests as pending to unblock CI (#26837)
- Add GitHub Actions annotations for Pester test failures (#26836)
- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26823)
- Fix merge conflict checker for empty file lists and filter `*.cs` files (#26813)
- Update the `Update-Help` tests to use `-Force` to remove read-only files (#26788)
- Add markdown link verification for PRs (#26407)
### Build and Packaging Improvements
<details>
<summary>
<p>Update to .NET SDK 9.0.312</p>
<p>We thank the following contributors!</p>
<p>@kasperk81, @RichardSlater</p>
</summary>
<ul>
<li>Revert change to module name ThreadJob (#26997)</li>
<li>Update branch for release (#26990)</li>
<li>Fix <code>ConvertFrom-ClearlyDefinedCoordinates</code> to handle API object coordinates (#26987)</li>
<li>Update CGManifests (#26981)</li>
<li>Hardcode Official templates (#26968)</li>
<li>Split TPN manifest and Component Governance manifest (#26967)</li>
<li>Fix a preview detection test for the packaging script (#26966)</li>
<li>Correct the package name for .deb and .rpm packages (#26964)</li>
<li>Bring Release Changes from v7.6.0-preview.6 (#26963)</li>
<li>Merge the v7.6.0-preview.5 release branch back to master (#26958)</li>
<li>Fix macOS preview package identifier detection to use version string (#26835)</li>
<li>Update metadata.json to update the Latest attribute with a better name (#26826)</li>
<li>Remove unused runCodesignValidationInjection variable from pipeline templates (#26825)</li>
<li>Update <code>Get-ChangeLog</code> to handle backport PRs correctly (#26824)</li>
<li>Mirror .NET/runtime ICU version range in PowerShell (#26821) (Thanks @kasperk81!)</li>
<li>Update the macos package name for preview releases to match the previous pattern (#26820)</li>
<li>Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26819)</li>
<li>Fix template path for rebuild branch check in package.yml (#26818)</li>
<li>Add rebuild branch support with conditional MSIX signing (#26817)</li>
<li>Move package validation to package pipeline (#26816)</li>
<li>Optimize/split windows package signing (#26815)</li>
<li>Improve ADO package build and validation across platforms (#26814)</li>
<li>Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26810)</li>
<li>Remove usage of fpm for DEB package generation (#26809)</li>
<li>Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26801)</li>
<li>Fix build to only enable ready-to-run for the Release configuration (#26798)</li>
<li>Fix R2R for fxdependent packaging (#26797)</li>
<li>Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26794)</li>
<li>Replace <code>fpm</code> with native <code>rpmbuild</code> for RPM package generation (#26793)</li>
<li>Add <code>libicu76</code> dependency to support Debian 13 (#26792) (Thanks @RichardSlater!)</li>
<li>Specify .NET search by build type (#26408)</li>
<li>Fix buildinfo.json uploading for preview, LTS, and stable releases (#26773)</li>
<li>Fix path to <code>metadata.json</code> in channel selection script (#26400)</li>
<li>Separate store automation service endpoints and resolve <code>AppID</code> (#26266)</li>
<li>Update a few packages to use the right version corresponding to .NET 9 (#26671)</li>
<li>Add network isolation policy parameter to vPack pipeline (#26393)</li>
<li>Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26391)</li>
<li>Integrate Windows packaging into windows-ci workflow using reusable workflow (#26390)</li>
<li>GitHub Workflow cleanup (#26389)</li>
<li>Update vPack name (#26221)</li>
</ul>
</details>
[7.5.5]: https://github.com/PowerShell/PowerShell/compare/v7.5.4...v7.5.5
## [7.5.4]
### Build and Packaging Improvements
+105
View File
@@ -1,5 +1,110 @@
# Preview Changelog
## [7.6.0-rc.1] - 2026-02-19
### Tests
- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26705)
### Build and Packaging Improvements
<details>
<summary>
<p>Expand to see details.</p>
</summary>
<ul>
<li>Update branch for release (#26779)</li>
<li>Update <code>Microsoft.PowerShell.PSResourceGet</code> version to 1.2.0-rc3 (#26767)</li>
<li>Update <code>Microsoft.PowerShell.Native</code> package version (#26748)</li>
<li>Move PowerShell build to depend on .NET SDK 10.0.102 (#26717)</li>
<li>Fix <code>buildinfo.json</code> uploading for preview, LTS, and stable releases (#26715)</li>
<li>Fix macOS preview package identifier detection to use version string (#26709)</li>
<li>Update <code>metadata.json</code> to update the Latest attribute with a better name (#26708)</li>
<li>Remove unused <code>runCodesignValidationInjection</code> variable from pipeline templates (#26707)</li>
<li>Update <code>Get-ChangeLog</code> to handle backport PRs correctly (#26706)</li>
<li>Bring release changes from the <code>v7.6.0-preview.6</code> release (#26626)</li>
<li>Fix the DSC test by skipping <code>AfterAll</code> cleanup if the initial setup in <code>BeforeAll</code> failed (#26622)</li>
</ul>
</details>
[7.6.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.6...v7.6.0-rc.1
## [7.6.0-preview.6] - 2025-12-11
### Engine Updates and Fixes
- Properly Expand Aliases to their actual ResolvedCommand (#26571) (Thanks @kilasuit!)
### General Cmdlet Updates and Fixes
- Update `Microsoft.PowerShell.PSResourceGet` to `v1.2.0-preview5` (#26590)
- Make the experimental feature `PSFeedbackProvider` stable (#26502)
- Fix a regression in the API `CompletionCompleters.CompleteFilename()` that causes null reference exception (#26487)
- Add Delimiter parameter to `Get-Clipboard` (#26572) (Thanks @MartinGC94!)
- Close pipe client handles after creating the child ssh process (#26564)
- Make some experimental features stable (#26490)
- DSC v3 resource for PowerShell Profile (#26447)
### Tools
- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26530)
- Add reusable get-changed-files action and refactor existing actions (#26529)
- Refactor analyze job to reusable workflow and enable on Windows CI (#26494)
### Tests
- Fix merge conflict checker for empty file lists and filter *.cs files (#26556)
- Add markdown link verification for PRs (#26445)
### Build and Packaging Improvements
<details>
<summary>
<p>Expand to see details.</p>
</summary>
<ul>
<li>Fix template path for rebuild branch check in package.yml (#26560)</li>
<li>Update the macos package name for preview releases to match the previous pattern (#26576)</li>
<li>Add rebuild branch support with conditional MSIX signing (#26573)</li>
<li>Update the WCF packages to the latest version that is compatible with v4.10.3 (#26503)</li>
<li>Improve ADO package build and validation across platforms (#26532)</li>
<li>Mirror .NET/runtime ICU version range in PowerShell (#26563) (Thanks @kasperk81!)</li>
<li>Update the macos package name for preview releases to match the previous pattern (#26562)</li>
<li>Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26561)</li>
<li>Move package validation to package pipeline (#26558)</li>
<li>Optimize/split windows package signing (#26557)</li>
<li>Remove usage of fpm for DEB package generation (#26504)</li>
<li>Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26524)</li>
<li>Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26501)</li>
<li>Replace fpm with native rpmbuild for RPM package generation (#26441)</li>
<li>Fix GitHub API rate limit errors in test actions (#26492)</li>
<li>Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26493)</li>
<li>Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26488)</li>
<li>Fix build to only enable ready-to-run for the Release configuration (#26481)</li>
<li>Integrate Windows packaging into windows-ci workflow using reusable workflow (#26468)</li>
<li>Update outdated package references (#26471)</li>
<li>GitHub Workflow cleanup (#26439)</li>
<li>Update PSResourceGet package version to preview4 (#26438)</li>
<li>Update PSReadLine to v2.4.5 (#26446)</li>
<li>Add network isolation policy parameter to vPack pipeline (#26444)</li>
<li>Fix a couple more lint errors</li>
<li>Fix lint errors in preview.md</li>
<li>Make MSIX publish stage dependent on SetReleaseTagandContainerName stage</li>
</ul>
</details>
[7.6.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.5...v7.6.0-preview.6
## [7.6.0-preview.5] - 2025-09-30
### Engine Updates and Fixes
+1 -1
View File
@@ -4,7 +4,7 @@
"quality": "daily",
"qualityFallback": "preview",
"packageVersionPattern": "9.0.0-preview.6",
"sdkImageVersion": "10.0.100-rc.1.25451.107",
"sdkImageVersion": "11.0.100-preview.1.26104.118",
"nextChannel": "9.0.0-preview.7",
"azureFeed": "",
"sdkImageOverride": ""
+2 -2
View File
@@ -144,8 +144,8 @@
<Copyright>(c) Microsoft Corporation.</Copyright>
<AssemblyTitle>PowerShell 7</AssemblyTitle>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>13.0</LangVersion>
<TargetFramework>net11.0</TargetFramework>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+78 -13
View File
@@ -1010,8 +1010,8 @@ function New-PSOptions {
[ValidateSet('Debug', 'Release', 'CodeCoverage', 'StaticAnalysis', '')]
[string]$Configuration,
[ValidateSet("net10.0")]
[string]$Framework = "net10.0",
[ValidateSet("net11.0")]
[string]$Framework = "net11.0",
# These are duplicated from Start-PSBuild
# We do not use ValidateScript since we want tab completion
@@ -1864,6 +1864,69 @@ $stack_trace
}
function Get-PesterFailureFileInfo
{
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[string]$StackTraceString
)
# Parse stack trace to extract file path and line number
# Common patterns:
# "at line: 123 in C:\path\to\file.ps1" (Pester 4)
# "at C:\path\to\file.ps1:123"
# "at <ScriptBlock>, C:\path\to\file.ps1: line 123"
# "at 1 | Should -Be 2, /path/to/file.ps1:123" (Pester 5)
# "at 1 | Should -Be 2, C:\path\to\file.ps1:123" (Pester 5 Windows)
$result = @{
File = $null
Line = $null
}
if ([string]::IsNullOrWhiteSpace($StackTraceString)) {
return $result
}
# Try pattern: "at line: 123 in <path>" (Pester 4)
if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') {
$result.Line = $matches[1]
$result.File = $matches[2].Trim()
return $result
}
# Try pattern: ", <path>:123" (Pester 5 format)
# This handles both Unix paths (/path/file.ps1:123) and Windows paths (C:\path\file.ps1:123)
if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') {
$result.File = $matches[1].Trim()
$result.Line = $matches[2]
return $result
}
# Try pattern: "at <path>:123" (without comma)
# Handle both absolute Unix and Windows paths
if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') {
$result.File = $matches[1].Trim()
$result.Line = $matches[2]
return $result
}
# Try pattern: "<path>: line 123"
if ($StackTraceString -match '((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):\s*line\s+(\d+)(?:\r|\n|$)') {
$result.File = $matches[1].Trim()
$result.Line = $matches[2]
return $result
}
# Try to extract just the file path if no line number found
if ($StackTraceString -match '(?:at\s+|in\s+)?((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1)') {
$result.File = $matches[1].Trim()
}
return $result
}
function Test-XUnitTestResults
{
param(
@@ -2485,19 +2548,21 @@ function Start-PSBootstrap {
Write-LogGroupEnd -Title "Install Windows Dependencies"
}
if ($Scenario -in 'All', 'Tools') {
Write-LogGroupStart -Title "Install .NET Global Tools"
Write-Log -message "Installing .NET global tools"
# Ensure dotnet is available
Find-Dotnet
# Ensure dotnet is available
Find-Dotnet
if (-not $env:TF_BUILD) {
if ($Scenario -in 'All', 'Tools') {
Write-LogGroupStart -Title "Install .NET Global Tools"
Write-Log -message "Installing .NET global tools"
# Install dotnet-format
Write-Verbose -Verbose "Installing dotnet-format global tool"
Start-NativeExecution {
dotnet tool install --global dotnet-format
# Install dotnet-format
Write-Verbose -Verbose "Installing dotnet-format global tool"
Start-NativeExecution {
dotnet tool install --global dotnet-format
}
Write-LogGroupEnd -Title "Install .NET Global Tools"
}
Write-LogGroupEnd -Title "Install .NET Global Tools"
}
if ($env:TF_BUILD) {
@@ -3956,7 +4021,7 @@ function Clear-NativeDependencies
$filesToDeleteWinDesktop = @()
$deps = Get-Content "$PublishFolder/pwsh.deps.json" -Raw | ConvertFrom-Json -Depth 20
$targetRuntime = ".NETCoreApp,Version=v10.0/$($script:Options.Runtime)"
$targetRuntime = ".NETCoreApp,Version=v11.0/$($script:Options.Runtime)"
$runtimePackNetCore = $deps.targets.${targetRuntime}.PSObject.Properties.Name -like 'runtimepack.Microsoft.NETCore.App.Runtime*'
$runtimePackWinDesktop = $deps.targets.${targetRuntime}.PSObject.Properties.Name -like 'runtimepack.Microsoft.WindowsDesktop.App.Runtime*'
+3 -1
View File
@@ -64,10 +64,12 @@ Import-Module ./build.psm1
Start-PSBuild -UseNuGetOrg
```
> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead.
Congratulations! If everything went right, PowerShell is now built.
The `Start-PSBuild` script will output the location of the executable:
`./src/powershell-unix/bin/Debug/net10.0/linux-x64/publish/pwsh`.
`./src/powershell-unix/bin/Debug/net11.0/linux-x64/publish/pwsh`.
You should now be running the PowerShell Core that you just built, if you run the above executable.
You can run our cross-platform Pester tests with `Start-PSPester -UseNuGetOrg`, and our xUnit tests with `Start-PSxUnit`.
+2
View File
@@ -37,3 +37,5 @@ We cannot do this for you in the build module due to #[847][].
Start a PowerShell session by running `pwsh`, and then use `Start-PSBuild -UseNuGetOrg` from the module.
After building, PowerShell will be at `./src/powershell-unix/bin/Debug/net6.0/osx-x64/publish/pwsh`.
> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead.
+2
View File
@@ -61,6 +61,8 @@ Import-Module ./build.psm1
Start-PSBuild -Clean -PSModuleRestore -UseNuGetOrg
```
> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead.
Congratulations! If everything went right, PowerShell is now built and executable as `./src/powershell-win-core/bin/Debug/net6.0/win7-x64/publish/pwsh.exe`.
This location is of the form `./[project]/bin/[configuration]/[framework]/[rid]/publish/[binary name]`,
+126
View File
@@ -0,0 +1,126 @@
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json",
"description": "Manage PowerShell profiles.",
"tags": [
"Linux",
"Windows",
"macOS",
"PowerShell"
],
"type": "Microsoft.PowerShell/Profile",
"version": "0.1.0",
"get": {
"executable": "pwsh",
"args": [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"./pwsh.profile.resource.ps1",
"-operation",
"get"
],
"input": "stdin"
},
"set": {
"executable": "pwsh",
"args": [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"./pwsh.profile.resource.ps1",
"-operation",
"set"
],
"input": "stdin"
},
"export": {
"executable": "pwsh",
"args": [
"-NoLogo",
"-NonInteractive",
"-NoProfile",
"-ExecutionPolicy",
"Bypass",
"-File",
"./pwsh.profile.resource.ps1",
"-operation",
"export"
],
"input": "stdin"
},
"exitCodes": {
"0": "Success",
"1": "Error",
"2": "Input not supported for export operation"
},
"schema": {
"embedded": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Profile",
"description": "Manage PowerShell profiles.",
"type": "object",
"unevaluatedProperties": false,
"required": [
"profileType"
],
"properties": {
"profileType": {
"type": "string",
"title": "Profile Type",
"description": "Defines which profile to manage. Valid values are: 'AllUsersCurrentHost', 'AllUsersAllHosts', 'CurrentUserAllHosts', and 'CurrentUserCurrentHost'.",
"enum": [
"AllUsersCurrentHost",
"AllUsersAllHosts",
"CurrentUserAllHosts",
"CurrentUserCurrentHost"
]
},
"profilePath": {
"title": "Profile Path",
"description": "The full path to the profile file.",
"type": "string",
"readOnly": true
},
"content": {
"title": "Content",
"description": "Defines the content of the profile. If you don't specify this property, the resource doesn't manage the file contents. If you specify this property as an empty string, the resource removes all content from the file. If you specify this property as a non-empty string, the resource sets the file contents to the specified string. The resources retains newlines from this property without any modification.",
"type": [ "string", "null" ]
},
"_exist": {
"$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json"
},
"_name": {
"$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json"
}
},
"$defs": {
"https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json",
"title": "Instance should exist",
"description": "Indicates whether the DSC resource instance should exist.",
"type": "boolean",
"default": true,
"enum": [
false,
true
]
},
"https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json",
"title": "Exported instance name",
"description": "Returns a generated name for the resource instance from an export operation.",
"readOnly": true,
"type": "string"
}
}
}
}
}
+179
View File
@@ -0,0 +1,179 @@
## Copyright (c) Microsoft Corporation. All rights reserved.
## Licensed under the MIT License.
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidateSet('get', 'set', 'export')]
[string]$Operation,
[Parameter(ValueFromPipeline)]
[string[]]$UserInput
)
Begin {
enum ProfileType {
AllUsersCurrentHost
AllUsersAllHosts
CurrentUserAllHosts
CurrentUserCurrentHost
}
function New-PwshResource {
param(
[Parameter(Mandatory = $true)]
[ProfileType] $ProfileType,
[Parameter(ParameterSetName = 'WithContent')]
[string] $Content,
[Parameter(ParameterSetName = 'WithContent')]
[bool] $Exist
)
# Create the PSCustomObject with properties
$resource = [PSCustomObject]@{
profileType = $ProfileType
content = $null
profilePath = GetProfilePath -profileType $ProfileType
_exist = $false
}
# Add ToJson method
$resource | Add-Member -MemberType ScriptMethod -Name 'ToJson' -Value {
return ([ordered] @{
profileType = $this.profileType
content = $this.content
profilePath = $this.profilePath
_exist = $this._exist
}) | ConvertTo-Json -Compress -EnumsAsStrings
}
# Constructor logic - if Content and Exist parameters are provided (WithContent parameter set)
if ($PSCmdlet.ParameterSetName -eq 'WithContent') {
$resource.content = $Content
$resource._exist = $Exist
} else {
# Default constructor logic - read from file system
$fileExists = Test-Path $resource.profilePath
if ($fileExists) {
$resource.content = Get-Content -Path $resource.profilePath
} else {
$resource.content = $null
}
$resource._exist = $fileExists
}
return $resource
}
function GetProfilePath {
param (
[ProfileType] $profileType
)
$path = switch ($profileType) {
'AllUsersCurrentHost' { $PROFILE.AllUsersCurrentHost }
'AllUsersAllHosts' { $PROFILE.AllUsersAllHosts }
'CurrentUserAllHosts' { $PROFILE.CurrentUserAllHosts }
'CurrentUserCurrentHost' { $PROFILE.CurrentUserCurrentHost }
}
return $path
}
function ExportOperation {
$allUserCurrentHost = New-PwshResource -ProfileType 'AllUsersCurrentHost'
$allUsersAllHost = New-PwshResource -ProfileType 'AllUsersAllHosts'
$currentUserAllHost = New-PwshResource -ProfileType 'CurrentUserAllHosts'
$currentUserCurrentHost = New-PwshResource -ProfileType 'CurrentUserCurrentHost'
# Cannot use the ToJson() method here as we are adding a note property
$allUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings
$allUsersAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings
$currentUserAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings
$currentUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings
}
function GetOperation {
param (
[Parameter(Mandatory = $true)]
$InputResource,
[Parameter()]
[switch] $AsJson
)
$profilePath = GetProfilePath -profileType $InputResource.profileType.ToString()
$actualState = New-PwshResource -ProfileType $InputResource.profileType
$actualState.profilePath = $profilePath
$exists = Test-Path $profilePath
if ($InputResource._exist -and $exists) {
$content = Get-Content -Path $profilePath
$actualState.Content = $content
} elseif ($InputResource._exist -and -not $exists) {
$actualState.Content = $null
$actualState._exist = $false
} elseif (-not $InputResource._exist -and $exists) {
$actualState.Content = Get-Content -Path $profilePath
$actualState._exist = $true
} else {
$actualState.Content = $null
$actualState._exist = $false
}
if ($AsJson) {
return $actualState.ToJson()
} else {
return $actualState
}
}
function SetOperation {
param (
$InputResource
)
$actualState = GetOperation -InputResource $InputResource
if ($InputResource._exist) {
if (-not $actualState._exist) {
$null = New-Item -Path $actualState.profilePath -ItemType File -Force
}
if ($null -ne $InputResource.content) {
Set-Content -Path $actualState.profilePath -Value $InputResource.content
}
} elseif ($actualState._exist) {
Remove-Item -Path $actualState.profilePath -Force
}
}
}
End {
$inputJson = $input | ConvertFrom-Json
if ($inputJson) {
$InputResource = New-PwshResource -ProfileType $inputJson.profileType -Content $inputJson.content -Exist $inputJson._exist
}
switch ($Operation) {
'get' {
GetOperation -InputResource $InputResource -AsJson
}
'set' {
SetOperation -InputResource $InputResource
}
'export' {
if ($inputJson) {
Write-Error "Input not supported for export operation"
exit 2
}
ExportOperation
}
}
exit 0
}
+12
View File
@@ -0,0 +1,12 @@
schemaVersion: 1.0.0
providers:
- provider: InventoryAsCode
version: 1.0.0
metadata:
isProduction: true
accountableOwners:
service: cef1de07-99d6-45df-b907-77d0066032ec
routing:
defaultAreaPath:
org: msazure
path: One\MGMT\Compute\Powershell\Powershell\Powershell Core\pwsh
+1
View File
@@ -2,6 +2,7 @@
"PSFeedbackProvider",
"PSLoadAssemblyFromNativeCode",
"PSNativeWindowsTildeExpansion",
"PSProfileDSCResource",
"PSSerializeJSONLongEnumAsNumber",
"PSRedirectToVariable",
"PSSubsystemPluginModel"
+1
View File
@@ -2,6 +2,7 @@
"PSFeedbackProvider",
"PSLoadAssemblyFromNativeCode",
"PSNativeWindowsTildeExpansion",
"PSProfileDSCResource",
"PSSerializeJSONLongEnumAsNumber",
"PSRedirectToVariable",
"PSSubsystemPluginModel"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "10.0.100-rc.2.25502.107"
"version": "11.0.100-preview.1.26104.118"
}
}
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net11.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackAsTool>true</PackAsTool>
@@ -679,7 +679,8 @@ Function PSGetSerializedShowCommandInfo
/// <returns>The host window, if it is present or null if it is not.</returns>
internal static Window GetHostWindow(PSCmdlet cmdlet)
{
PSPropertyInfo windowProperty = cmdlet.Host.PrivateData.Properties["Window"];
// The value of 'PrivateData' property may be null for the default host or a custom host.
PSPropertyInfo windowProperty = cmdlet.Host.PrivateData?.Properties["Window"];
if (windowProperty == null)
{
return null;
@@ -8,7 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="11.0.0-preview.1.26104.118" />
</ItemGroup>
<PropertyGroup>
@@ -47,7 +47,7 @@
<ItemGroup>
<!-- the following package(s) are from https://github.com/dotnet/corefx -->
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.1.26104.118" />
</ItemGroup>
</Project>
@@ -51,6 +51,21 @@ namespace Microsoft.PowerShell.Commands
[Parameter]
public SwitchParameter Resolve { get; set; }
/// <summary>
/// Gets or sets the extension to use for the resulting path.
/// If not specified, the original extension (if any) is preserved.
/// <para>
/// Behavior:
/// - If the path has an existing extension, it will be replaced with the specified extension.
/// - If the path does not have an extension, the specified extension will be added.
/// - If an empty string is provided, any existing extension will be removed.
/// - A leading dot in the extension is optional; if omitted, one will be added automatically.
/// </para>
/// </summary>
[Parameter(ValueFromPipelineByPropertyName = true)]
[ValidateNotNull]
public string Extension { get; set; }
#endregion Parameters
#region Command code
@@ -128,6 +143,12 @@ namespace Microsoft.PowerShell.Commands
continue;
}
// If Extension parameter is present it is not null due to [ValidateNotNull].
if (Extension is not null)
{
joinedPath = System.IO.Path.ChangeExtension(joinedPath, Extension.Length == 0 ? null : Extension);
}
if (Resolve)
{
// Resolve the paths. The default API (GetResolvedPSPathFromPSPath)
@@ -2019,35 +2019,24 @@ $result
/// <returns></returns>
internal static bool IsComputerNameValid(string computerName)
{
bool allDigits = true;
bool hasAsciiLetterOrHyphen = false;
if (computerName.Length >= 64)
return false;
foreach (char t in computerName)
{
if (t >= 'A' && t <= 'Z' ||
t >= 'a' && t <= 'z')
if (char.IsAsciiLetter(t) || t is '-')
{
allDigits = false;
continue;
hasAsciiLetterOrHyphen = true;
}
else if (t >= '0' && t <= '9')
{
continue;
}
else if (t == '-')
{
allDigits = false;
continue;
}
else
else if (!char.IsAsciiDigit(t))
{
return false;
}
}
return !allDigits;
return hasAsciiLetterOrHyphen;
}
/// <summary>
@@ -289,137 +289,125 @@ namespace Microsoft.PowerShell.Commands
{
string result = null;
switch (ParameterSetName)
// Check switch parameters in order of specificity
if (IsAbsolute)
{
case isAbsoluteSet:
string ignored;
bool isPathAbsolute =
SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored);
string ignored;
bool isPathAbsolute =
SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored);
WriteObject(isPathAbsolute);
WriteObject(isPathAbsolute);
continue;
}
else if (Qualifier)
{
int separatorIndex = pathsToParse[index].IndexOf(':');
if (separatorIndex < 0)
{
FormatException e =
new(
StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index]));
WriteError(
new ErrorRecord(
e,
"ParsePathFormatError", // RENAME
ErrorCategory.InvalidArgument,
pathsToParse[index]));
continue;
}
else
{
// Check to see if it is provider or drive qualified
case qualifierSet:
int separatorIndex = pathsToParse[index].IndexOf(':');
if (separatorIndex < 0)
if (SessionState.Path.IsProviderQualified(pathsToParse[index]))
{
FormatException e =
new(
StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index]));
WriteError(
new ErrorRecord(
e,
"ParsePathFormatError", // RENAME
ErrorCategory.InvalidArgument,
pathsToParse[index]));
continue;
// The plus 2 is for the length of the provider separator
// which is "::"
result =
pathsToParse[index].Substring(
0,
separatorIndex + 2);
}
else
{
// Check to see if it is provider or drive qualified
if (SessionState.Path.IsProviderQualified(pathsToParse[index]))
{
// The plus 2 is for the length of the provider separator
// which is "::"
result =
pathsToParse[index].Substring(
0,
separatorIndex + 2);
}
else
{
result =
pathsToParse[index].Substring(
0,
separatorIndex + 1);
}
}
break;
case parentSet:
case literalPathSet:
try
{
result =
SessionState.Path.ParseParent(
pathsToParse[index],
string.Empty,
CmdletProviderContext,
true);
pathsToParse[index].Substring(
0,
separatorIndex + 1);
}
catch (PSNotSupportedException)
}
}
else if (Leaf || LeafBase || Extension)
{
try
{
result =
SessionState.Path.ParseChildName(
pathsToParse[index],
CmdletProviderContext,
true);
if (LeafBase)
{
// Since getting the parent path is not supported,
// the provider must be a container, item, or drive
// provider. Since the paths for these types of
// providers can't be split, asking for the parent
// is asking for an empty string.
result = string.Empty;
result = System.IO.Path.GetFileNameWithoutExtension(result);
}
break;
case leafSet:
case leafBaseSet:
case extensionSet:
try
else if (Extension)
{
// default handles leafSet
result =
SessionState.Path.ParseChildName(
pathsToParse[index],
CmdletProviderContext,
true);
if (LeafBase)
{
result = System.IO.Path.GetFileNameWithoutExtension(result);
}
else if (Extension)
{
result = System.IO.Path.GetExtension(result);
}
result = System.IO.Path.GetExtension(result);
}
catch (PSNotSupportedException)
{
// Since getting the leaf part of a path is not supported,
// the provider must be a container, item, or drive
// provider. Since the paths for these types of
// providers can't be split, asking for the leaf
// is asking for the specified path back.
result = pathsToParse[index];
}
catch (DriveNotFoundException driveNotFound)
{
WriteError(
new ErrorRecord(
driveNotFound.ErrorRecord,
driveNotFound));
continue;
}
catch (ProviderNotFoundException providerNotFound)
{
WriteError(
new ErrorRecord(
providerNotFound.ErrorRecord,
providerNotFound));
continue;
}
break;
case noQualifierSet:
result = RemoveQualifier(pathsToParse[index]);
break;
default:
Dbg.Diagnostics.Assert(
false,
"Only a known parameter set should be called");
break;
}
catch (PSNotSupportedException)
{
// Since getting the leaf part of a path is not supported,
// the provider must be a container, item, or drive
// provider. Since the paths for these types of
// providers can't be split, asking for the leaf
// is asking for the specified path back.
result = pathsToParse[index];
}
catch (DriveNotFoundException driveNotFound)
{
WriteError(
new ErrorRecord(
driveNotFound.ErrorRecord,
driveNotFound));
continue;
}
catch (ProviderNotFoundException providerNotFound)
{
WriteError(
new ErrorRecord(
providerNotFound.ErrorRecord,
providerNotFound));
continue;
}
}
else if (NoQualifier)
{
result = RemoveQualifier(pathsToParse[index]);
}
else
{
// None of the switch parameters are true: default to -Parent behavior
try
{
result =
SessionState.Path.ParseParent(
pathsToParse[index],
string.Empty,
CmdletProviderContext,
true);
}
catch (PSNotSupportedException)
{
// Since getting the parent path is not supported,
// the provider must be a container, item, or drive
// provider. Since the paths for these types of
// providers can't be split, asking for the parent
// is asking for an empty string.
result = string.Empty;
}
}
if (result != null)
@@ -242,14 +242,13 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
protected override void BeginProcessing()
{
switch (ParameterSetName)
if (Repeat)
{
case RepeatPingParameterSet:
Count = int.MaxValue;
break;
case TcpPortParameterSet:
SetCountForTcpTest();
break;
Count = int.MaxValue;
}
else if (ParameterSetName == TcpPortParameterSet)
{
SetCountForTcpTest();
}
}
@@ -265,21 +264,22 @@ namespace Microsoft.PowerShell.Commands
foreach (var targetName in TargetName)
{
switch (ParameterSetName)
if (MtuSize)
{
case DefaultPingParameterSet:
case RepeatPingParameterSet:
ProcessPing(targetName);
break;
case MtuSizeDetectParameterSet:
ProcessMTUSize(targetName);
break;
case TraceRouteParameterSet:
ProcessTraceroute(targetName);
break;
case TcpPortParameterSet:
ProcessConnectionByTCPPort(targetName);
break;
ProcessMTUSize(targetName);
}
else if (Traceroute)
{
ProcessTraceroute(targetName);
}
else if (ParameterSetName == TcpPortParameterSet)
{
ProcessConnectionByTCPPort(targetName);
}
else
{
// None of the switch parameters are true: handle default ping or -Repeat
ProcessPing(targetName);
}
}
}
@@ -298,7 +298,7 @@ namespace Microsoft.PowerShell.Commands
private void SetCountForTcpTest()
{
if (Repeat.IsPresent)
if (Repeat)
{
Count = int.MaxValue;
}
@@ -54,7 +54,7 @@ namespace Microsoft.PowerShell.Commands
// make sure we've got the latest time zone settings
TimeZoneInfo.ClearCachedData();
if (this.ParameterSetName.Equals("ListAvailable", StringComparison.OrdinalIgnoreCase))
if (ListAvailable)
{
// output the list of all available time zones
WriteObject(TimeZoneInfo.GetSystemTimeZones(), true);
@@ -8,7 +8,7 @@
<ItemGroup>
<ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" />
<PackageReference Include="Markdig.Signed" Version="0.43.0" />
<PackageReference Include="Markdig.Signed" Version="1.0.0" />
<PackageReference Include="Microsoft.PowerShell.MarkdownRender" Version="7.2.1" />
</ItemGroup>
@@ -32,8 +32,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
<PackageReference Include="System.Drawing.Common" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageReference Include="System.Drawing.Common" Version="11.0.0-preview.1.26104.118" />
<PackageReference Include="JsonSchema.Net" Version="7.4.0" />
</ItemGroup>
@@ -45,17 +45,19 @@ namespace Microsoft.PowerShell.Commands
public abstract PSObject InputObject { get; set; }
/// <summary>
/// IncludeTypeInformation : The #TYPE line should be generated. Default is false. Cannot specify with NoTypeInformation.
/// IncludeTypeInformation : The #TYPE line should be generated. Default is false.
/// </summary>
[Parameter]
[Alias("ITI")]
public SwitchParameter IncludeTypeInformation { get; set; }
/// <summary>
/// NoTypeInformation : The #TYPE line should not be generated. Default is true. Cannot specify with IncludeTypeInformation.
/// Gets or sets a value indicating whether to suppress the #TYPE line.
/// This parameter is obsolete and has no effect. It is retained for backward compatibility only.
/// </summary>
[Parameter(DontShow = true)]
[Alias("NTI")]
[Obsolete("This parameter is obsolete and has no effect. The default behavior is to not include type information. Use -IncludeTypeInformation to include type information.")]
public SwitchParameter NoTypeInformation { get; set; } = true;
/// <summary>
@@ -120,18 +122,6 @@ namespace Microsoft.PowerShell.Commands
this.ThrowTerminatingError(errorRecord);
}
if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation)) && this.MyInvocation.BoundParameters.ContainsKey(nameof(NoTypeInformation)))
{
InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyIncludeTypeInformationAndNoTypeInformation);
ErrorRecord errorRecord = new(exception, "CannotSpecifyIncludeTypeInformationAndNoTypeInformation", ErrorCategory.InvalidData, null);
this.ThrowTerminatingError(errorRecord);
}
if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation)))
{
NoTypeInformation = !IncludeTypeInformation;
}
Delimiter = ImportExportCSVHelper.SetDelimiter(this, ParameterSetName, Delimiter, UseCulture);
}
}
@@ -270,6 +260,14 @@ namespace Microsoft.PowerShell.Commands
this.ThrowTerminatingError(errorRecord);
}
// Validate that Append and NoHeader are not specified together.
if (Append && NoHeader)
{
InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyAppendAndNoHeader);
ErrorRecord errorRecord = new(exception, "CannotSpecifyBothAppendAndNoHeader", ErrorCategory.InvalidData, null);
this.ThrowTerminatingError(errorRecord);
}
_shouldProcess = ShouldProcess(Path);
if (!_shouldProcess)
{
@@ -309,7 +307,7 @@ namespace Microsoft.PowerShell.Commands
// write headers (row1: typename + row2: column names)
if (!_isActuallyAppending && !NoHeader.IsPresent)
{
if (NoTypeInformation == false)
if (IncludeTypeInformation)
{
WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject));
}
@@ -734,7 +732,7 @@ namespace Microsoft.PowerShell.Commands
if (!NoHeader.IsPresent)
{
if (NoTypeInformation == false)
if (IncludeTypeInformation)
{
WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject));
}
@@ -32,6 +32,7 @@ namespace Microsoft.PowerShell.Commands
/// will be determined using property sets, etc.
/// </summary>
[Parameter(Position = 0)]
[ValidateNotNullOrEmpty]
public object[] Property
{
get { return _props; }
@@ -41,6 +42,12 @@ namespace Microsoft.PowerShell.Commands
private object[] _props;
/// <summary>
/// Gets or sets the properties to exclude from formatting.
/// </summary>
[Parameter]
public string[] ExcludeProperty { get; set; }
/// <summary>
/// </summary>
/// <value></value>
@@ -61,6 +68,18 @@ namespace Microsoft.PowerShell.Commands
{
FormattingCommandLineParameters parameters = new();
// Check View conflicts first (before any auto-expansion)
if (!string.IsNullOrEmpty(this.View))
{
// View cannot be used with Property or ExcludeProperty
if ((_props is not null && _props.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0))
{
ReportCannotSpecifyViewAndProperty();
}
parameters.viewName = this.View;
}
if (_props != null)
{
ParameterProcessor processor = new(new FormatObjectParameterDefinition());
@@ -68,15 +87,17 @@ namespace Microsoft.PowerShell.Commands
parameters.mshParameterList = processor.ProcessParameters(_props, invocationContext);
}
if (!string.IsNullOrEmpty(this.View))
if (ExcludeProperty is not null)
{
// we have a view command line switch
if (parameters.mshParameterList.Count != 0)
{
ReportCannotSpecifyViewAndProperty();
}
parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty);
parameters.viewName = this.View;
// ExcludeProperty implies -Property * for better UX
if (_props is null || _props.Length == 0)
{
ParameterProcessor processor = new(new FormatObjectParameterDefinition());
TerminatingErrorContext invocationContext = new(this);
parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext);
}
}
parameters.groupByParameter = this.ProcessGroupByParameter();
@@ -42,9 +42,14 @@ namespace Microsoft.PowerShell.Commands
private object _prop;
/// <summary>
/// Optional, non positional parameter.
/// Gets or sets the properties to exclude from formatting.
/// </summary>
[Parameter]
public string[] ExcludeProperty { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to autosize the output.
/// </summary>
/// <value></value>
[Parameter]
public SwitchParameter AutoSize
{
@@ -74,6 +79,18 @@ namespace Microsoft.PowerShell.Commands
{
FormattingCommandLineParameters parameters = new();
// Check View conflicts first (before any auto-expansion)
if (!string.IsNullOrEmpty(this.View))
{
// View cannot be used with Property or ExcludeProperty
if (_prop is not null || (ExcludeProperty is not null && ExcludeProperty.Length != 0))
{
ReportCannotSpecifyViewAndProperty();
}
parameters.viewName = this.View;
}
if (_prop != null)
{
ParameterProcessor processor = new(new FormatWideParameterDefinition());
@@ -81,15 +98,17 @@ namespace Microsoft.PowerShell.Commands
parameters.mshParameterList = processor.ProcessParameters(new object[] { _prop }, invocationContext);
}
if (!string.IsNullOrEmpty(this.View))
if (ExcludeProperty is not null)
{
// we have a view command line switch
if (parameters.mshParameterList.Count != 0)
{
ReportCannotSpecifyViewAndProperty();
}
parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty);
parameters.viewName = this.View;
// ExcludeProperty implies -Property * for better UX
if (_prop is null)
{
ParameterProcessor processor = new(new FormatWideParameterDefinition());
TerminatingErrorContext invocationContext = new(this);
parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext);
}
}
// we cannot specify -column and -autosize, they are mutually exclusive
@@ -488,7 +488,7 @@ namespace Microsoft.PowerShell.Commands
{
if (EffectiveParameterSet == MyParameterSet.RandomListItem)
{
if (ParameterSetName == ShuffleParameterSet)
if (Shuffle)
{
// this allows for $null to be in an array passed to InputObject
foreach (object item in InputObject ?? _nullInArray)
@@ -12,47 +12,6 @@ using Microsoft.PowerShell.Commands.Internal.Format;
namespace Microsoft.PowerShell.Commands
{
/// <summary>
/// Helper class to do wildcard matching on PSPropertyExpressions.
/// </summary>
internal sealed class PSPropertyExpressionFilter
{
/// <summary>
/// Initializes a new instance of the <see cref="PSPropertyExpressionFilter"/> class
/// with the specified array of patterns.
/// </summary>
/// <param name="wildcardPatternsStrings">Array of pattern strings to use.</param>
internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings)
{
ArgumentNullException.ThrowIfNull(wildcardPatternsStrings);
_wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length];
for (int k = 0; k < wildcardPatternsStrings.Length; k++)
{
_wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase);
}
}
/// <summary>
/// Try to match the expression against the array of wildcard patterns.
/// The first match shortcircuits the search.
/// </summary>
/// <param name="expression">PSPropertyExpression to test against.</param>
/// <returns>True if there is a match, else false.</returns>
internal bool IsMatch(PSPropertyExpression expression)
{
for (int k = 0; k < _wildcardPatterns.Length; k++)
{
if (_wildcardPatterns[k].IsMatch(expression.ToString()))
return true;
}
return false;
}
private readonly WildcardPattern[] _wildcardPatterns;
}
internal sealed class SelectObjectExpressionParameterDefinition : CommandParameterDefinition
{
protected override void SetEntries()
@@ -264,19 +264,11 @@ namespace Microsoft.PowerShell.Commands
if (_jschema != null)
{
EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.List });
EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical });
result = evaluationResults.IsValid;
if (!result)
{
HandleValidationErrors(evaluationResults);
if (evaluationResults.HasDetails)
{
foreach (var nestedResult in evaluationResults.Details)
{
HandleValidationErrors(nestedResult);
}
}
ReportValidationErrors(evaluationResults);
}
}
}
@@ -298,6 +290,33 @@ namespace Microsoft.PowerShell.Commands
WriteObject(result);
}
/// <summary>
/// Recursively reports validation errors from hierarchical evaluation results.
/// Skips nodes (and their children) where IsValid is true to avoid false positives
/// from constructs like OneOf or AnyOf.
/// </summary>
/// <param name="evaluationResult">The evaluation result to process.</param>
private void ReportValidationErrors(EvaluationResults evaluationResult)
{
// Skip this node and all children if validation passed
if (evaluationResult.IsValid)
{
return;
}
// Report errors at this level
HandleValidationErrors(evaluationResult);
// Recursively process child results
if (evaluationResult.HasDetails)
{
foreach (var nestedResult in evaluationResult.Details)
{
ReportValidationErrors(nestedResult);
}
}
}
private void HandleValidationErrors(EvaluationResults evaluationResult)
{
if (!evaluationResult.HasErrors)
@@ -81,9 +81,9 @@ namespace Microsoft.PowerShell.Commands
{
List<PSObject> parsedFields = new();
MatchCollection fieldMatch = HtmlParser.InputFieldRegex.Matches(Content);
foreach (Match field in fieldMatch)
foreach (Match match in fieldMatch)
{
parsedFields.Add(CreateHtmlObject(field.Value, "INPUT"));
parsedFields.Add(CreateHtmlObject(match.Value, "INPUT"));
}
_inputFields = new WebCmdletElementCollection(parsedFields);
@@ -1995,7 +1995,7 @@ namespace Microsoft.PowerShell.Commands
/// <param name="file">The file to use for the <see cref="StreamContent"/></param>
private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file)
{
StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open));
StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read));
result.Headers.ContentDisposition.FileName = file.Name;
result.Headers.ContentDisposition.FileNameStar = file.Name;
@@ -130,9 +130,6 @@
<data name="CannotSpecifyQuoteFieldsAndUseQuotes" xml:space="preserve">
<value>You must specify either the -UseQuotes or -QuoteFields parameters, but not both.</value>
</data>
<data name="CannotSpecifyIncludeTypeInformationAndNoTypeInformation" xml:space="preserve">
<value>You must specify either the -IncludeTypeInformation or -NoTypeInformation parameters, but not both.</value>
</data>
<data name="CannotSpecifyPathAndLiteralPath" xml:space="preserve">
<value>You must specify either the -Path or -LiteralPath parameters, but not both.</value>
</data>
@@ -157,4 +154,7 @@
<data name="EOFIsReached" xml:space="preserve">
<value>EOF is reached.</value>
</data>
<data name="CannotSpecifyAppendAndNoHeader" xml:space="preserve">
<value>You must specify either the -Append or -NoHeader parameters, but not both.</value>
</data>
</root>
@@ -2201,7 +2201,6 @@ namespace Microsoft.PowerShell
if (e1 != null)
{
// that didn't work. Write out the error ourselves as a last resort.
ReportExceptionFallback(e, null);
}
}
@@ -2222,7 +2221,7 @@ namespace Microsoft.PowerShell
Console.Error.WriteLine(header);
}
if (e == null)
if (e is null)
{
return;
}
@@ -2230,7 +2229,9 @@ namespace Microsoft.PowerShell
// See if the exception has an error record attached to it...
ErrorRecord er = null;
if (e is IContainsErrorRecord icer)
{
er = icer.ErrorRecord;
}
if (e is PSRemotingTransportException)
{
@@ -2247,8 +2248,22 @@ namespace Microsoft.PowerShell
}
// Add the position message for the error if it's available.
if (er != null && er.InvocationInfo != null)
if (er?.InvocationInfo is { })
{
Console.Error.WriteLine(er.InvocationInfo.PositionMessage);
}
// Print the stack trace.
Console.Error.WriteLine($"\n--- {e.GetType().FullName} ---");
Console.Error.WriteLine(e.StackTrace);
Exception inner = e.InnerException;
while (inner is { })
{
Console.Error.WriteLine($"--- inner {inner.GetType().FullName} ---");
Console.Error.WriteLine(inner.StackTrace);
inner = inner.InnerException;
}
}
/// <summary>
@@ -8,7 +8,7 @@
<ItemGroup>
<!-- the following package(s) are from https://github.com/dotnet/corefx -->
<PackageReference Include="System.Diagnostics.EventLog" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Diagnostics.EventLog" Version="11.0.0-preview.1.26104.118" />
</ItemGroup>
</Project>
@@ -16,19 +16,19 @@
<ItemGroup>
<!-- This section is to force the version of non-direct dependencies -->
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.1.26104.118" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="11.0.0-preview.1.26104.118" />
<!-- the following package(s) are from https://github.com/dotnet/fxdac -->
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.9.1" />
<!-- the following package(s) are from https://github.com/dotnet/corefx -->
<PackageReference Include="System.IO.Packaging" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.IO.Packaging" Version="11.0.0-preview.1.26104.118" />
<PackageReference Include="System.Net.Http.WinHttpHandler" Version="11.0.0-preview.1.26104.118" />
<!-- the following package(s) are from https://github.com/dotnet/wcf -->
<PackageReference Include="System.ServiceModel.Http" Version="10.0.0-rc.2.final" />
<PackageReference Include="System.ServiceModel.NetTcp" Version="10.0.0-rc.2.final" />
<PackageReference Include="System.ServiceModel.Primitives" Version="10.0.0-rc.2.final" />
<PackageReference Include="System.ServiceModel.Http" Version="10.0.652802" />
<PackageReference Include="System.ServiceModel.NetTcp" Version="10.0.652802" />
<PackageReference Include="System.ServiceModel.Primitives" Version="10.0.652802" />
<!-- the source could not be found for the following package(s) -->
<PackageReference Include="Microsoft.Windows.Compatibility" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.1.26104.118" />
</ItemGroup>
</Project>
@@ -3424,7 +3424,6 @@ namespace Microsoft.PowerShell.Commands
return IntPtr.Zero;
}
#else
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private static IntPtr hWnd = IntPtr.Zero;
private static bool firstRun = true;
+13 -10
View File
@@ -734,18 +734,19 @@ namespace Microsoft.WSMan.Management
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
string ResourceUri
{
// IDL: HRESULT resourceUri (BSTR value);
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")]
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")]
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
[DispId(1)]
set;
// IDL: HRESULT resourceUri ([out, retval] BSTR* ReturnValue);
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")]
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
[DispId(1)]
[return: MarshalAs(UnmanagedType.BStr)]
get;
// IDL: HRESULT resourceUri (BSTR value);
[SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")]
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")]
[DispId(1)]
set;
}
/// <summary><para><c>AddSelector</c> method of <c>IWSManResourceLocator</c> interface. </para><para>Add selector to resource locator</para></summary>
@@ -818,14 +819,16 @@ namespace Microsoft.WSMan.Management
int MustUnderstandOptions
{
// IDL: HRESULT MustUnderstandOptions (long value);
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")]
[DispId(7)]
set;
// IDL: HRESULT MustUnderstandOptions ([out, retval] long* ReturnValue);
[DispId(7)]
get;
// IDL: HRESULT MustUnderstandOptions (long value);
[DispId(7)]
set;
}
/// <summary><para><c>ClearOptions</c> method of <c>IWSManResourceLocator</c> interface. </para><para>Clear all options</para></summary>
@@ -10,7 +10,7 @@
<ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" />
<ProjectReference Include="..\Microsoft.WSMan.Runtime\Microsoft.WSMan.Runtime.csproj" />
<!-- the following package(s) are from https://github.com/dotnet/corefx -->
<PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.1.26104.118" />
</ItemGroup>
<PropertyGroup>
+3 -3
View File
@@ -5,7 +5,7 @@
<Company>Microsoft Corporation</Company>
<Copyright>(c) Microsoft Corporation.</Copyright>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net11.0</TargetFramework>
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
</PropertyGroup>
@@ -13,10 +13,10 @@
<ItemGroup>
<PackageReference Include="PowerShellGet" Version="2.2.5" />
<PackageReference Include="PackageManagement" Version="1.4.8.1" />
<PackageReference Include="Microsoft.PowerShell.PSResourceGet" Version="1.2.0-preview4" />
<PackageReference Include="Microsoft.PowerShell.PSResourceGet" Version="1.2.0" />
<PackageReference Include="Microsoft.PowerShell.Archive" Version="1.2.5" />
<PackageReference Include="PSReadLine" Version="2.4.5" />
<PackageReference Include="Microsoft.PowerShell.ThreadJob" Version="2.2.0" />
<PackageReference Include="Microsoft.PowerShell.ThreadJob" Version="2.2.0" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<Description>Generates C# typed bindings for .resx files</Description>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net11.0</TargetFramework>
<AssemblyName>resgen</AssemblyName>
<OutputType>Exe</OutputType>
<TieredCompilation>true</TieredCompilation>
@@ -680,10 +680,7 @@ if (($_.relatedLinks -ne $()) -and ($_.relatedLinks.navigationLink -ne $()) -and
.StartFrame(leftIndent: 4)
.AddPropertyExpressionBinding(@"title")
.AddNewline()
.AddNewline()
.StartFrame(leftIndent: 4)
.AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11])
.EndFrame()
.AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11])
.AddNewline()
.EndFrame()
.EndEntry()
@@ -1190,8 +1190,37 @@ namespace System.Management.Automation.Runspaces
$highlightLine = ''
if ($useTargetObject) {
$line = $_.TargetObject.LineText.Trim()
$offsetLength = 0
$offsetInLine = 0
$startColumn = 0
if (
([System.Collections.IDictionary]$_.TargetObject).Contains('StartColumn') -and
[System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.StartColumn, [ref]$startColumn) -and
$null -ne $startColumn -and
$startColumn -gt 0 -and
$startColumn -le $line.Length
) {
$endColumn = 0
if (-not (
([System.Collections.IDictionary]$_.TargetObject).Contains('EndColumn') -and
[System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.EndColumn, [ref]$endColumn) -and
$null -ne $endColumn -and
$endColumn -gt $startColumn -and
$endColumn -le ($line.Length + 1)
)) {
$endColumn = $line.Length + 1
}
# Input is expected to be 1-based index to match the extent positioning
# but we use 0-based indexing below.
$startColumn -= 1
$endColumn -= 1
$highlightLine = "$(" " * $startColumn)$("~" * ($endColumn - $startColumn))"
$offsetLength = $endColumn - $startColumn
$offsetInLine = $startColumn
}
}
else {
$positionMessage = $myinv.PositionMessage.Split($newline)
@@ -9,6 +9,7 @@ using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;
using Microsoft.PowerShell.Commands;
namespace Microsoft.PowerShell.Commands.Internal.Format
{
@@ -727,8 +728,15 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
/// will be determined using property sets, etc.
/// </summary>
[Parameter(Position = 0)]
[ValidateNotNullOrEmpty]
public object[] Property { get; set; }
/// <summary>
/// Optional parameter for excluding properties from formatting.
/// </summary>
[Parameter]
public string[] ExcludeProperty { get; set; }
#endregion
internal override FormattingCommandLineParameters GetCommandLineParameters()
@@ -751,6 +759,18 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
internal void GetCommandLineProperties(FormattingCommandLineParameters parameters, bool isTable)
{
// Check View conflicts first (before any auto-expansion)
if (!string.IsNullOrEmpty(this.View))
{
// View cannot be used with Property or ExcludeProperty
if ((Property is not null && Property.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0))
{
ReportCannotSpecifyViewAndProperty();
}
parameters.viewName = this.View;
}
if (Property != null)
{
CommandParameterDefinition def;
@@ -765,15 +785,21 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
parameters.mshParameterList = processor.ProcessParameters(Property, invocationContext);
}
if (!string.IsNullOrEmpty(this.View))
if (ExcludeProperty is not null)
{
// we have a view command line switch
if (parameters.mshParameterList.Count != 0)
{
ReportCannotSpecifyViewAndProperty();
}
parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty);
parameters.viewName = this.View;
// ExcludeProperty implies -Property * for better UX
if (Property is null || Property.Length == 0)
{
CommandParameterDefinition def = isTable
? new FormatTableParameterDefinition()
: new FormatListParameterDefinition();
ParameterProcessor processor = new ParameterProcessor(def);
TerminatingErrorContext invocationContext = new TerminatingErrorContext(this);
parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext);
}
}
}
}
@@ -70,6 +70,11 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
/// Extension mechanism for shape specific parameters.
/// </summary>
internal ShapeSpecificParameters shapeParameters = null;
/// <summary>
/// Filter for excluding properties from formatting.
/// </summary>
internal PSPropertyExpressionFilter excludePropertyFilter = null;
}
/// <summary>
@@ -9,7 +9,6 @@ using System.Globalization;
using System.Management.Automation;
using System.Management.Automation.Internal;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.PowerShell.Commands.Internal.Format
{
@@ -317,6 +316,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
{
internal string Word;
internal string Delim;
internal bool VtResetAdded;
}
/// <summary>
@@ -367,9 +367,19 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
{
var vtSpan = s.AsSpan(i, len);
sb.Append(vtSpan);
vtSeqs.Append(vtSpan);
wordHasVtSeqs = true;
if (vtSpan.SequenceEqual(PSStyle.Instance.Reset))
{
// The Reset sequence will void all previous VT sequences.
vtSeqs.Clear();
wordHasVtSeqs = false;
}
else
{
vtSeqs.Append(vtSpan);
wordHasVtSeqs = true;
}
i += len - 1;
continue;
}
@@ -390,15 +400,18 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
if (delimiter is not null)
{
bool vtResetAdded = false;
if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset))
{
vtResetAdded = true;
sb.Append(PSStyle.Instance.Reset);
}
var result = new GetWordsResult()
{
Word = sb.ToString(),
Delim = delimiter
Delim = delimiter,
VtResetAdded = vtResetAdded
};
sb.Clear().Append(vtSeqs);
@@ -611,7 +624,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
if (suffix is not null)
{
wordToAdd = wordToAdd.EndsWith(resetStr)
wordToAdd = word.VtResetAdded
? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix)
: wordToAdd + suffix;
}
@@ -760,9 +773,19 @@ namespace Microsoft.PowerShell.Commands.Internal.Format
{
var vtSpan = s.AsSpan(i, len);
sb.Append(vtSpan);
vtSeqs.Append(vtSpan);
hasVtSeqs = true;
if (vtSpan.SequenceEqual(PSStyle.Instance.Reset))
{
// The Reset sequence will void all previous VT sequences.
vtSeqs.Clear();
hasVtSeqs = false;
}
else
{
vtSeqs.Append(vtSpan);
hasVtSeqs = true;
}
i += len - 1;
continue;
}

Some files were not shown because too many files have changed in this diff Show More