mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Merge branch 'master' into copilot/fix-code-duplication-packaging
This commit is contained in:
@@ -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
@@ -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:
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@"
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -13,8 +13,6 @@ jobs:
|
||||
variables:
|
||||
- name: HOMEBREW_NO_ANALYTICS
|
||||
value: 1
|
||||
- name: runCodesignValidationInjection
|
||||
value: false
|
||||
- name: NugetSecurityAnalysisWarningLevel
|
||||
value: none
|
||||
- group: DotNetPrivateBuildAccess
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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')))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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/*
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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/*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": ""
|
||||
|
||||
@@ -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
@@ -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*'
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]`,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -2,6 +2,7 @@
|
||||
"PSFeedbackProvider",
|
||||
"PSLoadAssemblyFromNativeCode",
|
||||
"PSNativeWindowsTildeExpansion",
|
||||
"PSProfileDSCResource",
|
||||
"PSSerializeJSONLongEnumAsNumber",
|
||||
"PSRedirectToVariable",
|
||||
"PSSubsystemPluginModel"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"PSFeedbackProvider",
|
||||
"PSLoadAssemblyFromNativeCode",
|
||||
"PSNativeWindowsTildeExpansion",
|
||||
"PSProfileDSCResource",
|
||||
"PSSerializeJSONLongEnumAsNumber",
|
||||
"PSRedirectToVariable",
|
||||
"PSSubsystemPluginModel"
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+21
@@ -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>
|
||||
|
||||
+105
-117
@@ -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)
|
||||
|
||||
+22
-22
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+3
-3
@@ -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));
|
||||
}
|
||||
|
||||
+28
-7
@@ -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();
|
||||
|
||||
+28
-9
@@ -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)
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+1
-4
@@ -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()
|
||||
|
||||
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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
Reference in New Issue
Block a user