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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ release notes.
IntuneScriptLab` as an `IslModuleDependency` warning, which failed a build at `-FailOn Warning`.
Folders are searched by the gate now and `*.Tests.ps1` files are left out; a suite named directly
on `-Path` is still analyzed.
- The gate also analyzed the contents of a Win32 app's `Package` folder (`Win32\<app>\Package\`,
what becomes the `.intunewin`), and an `install.ps1` there, which the agent runs through the
install command as SYSTEM, was read as a user-context platform script: every machine-wide write
became an `IslContextIssue` warning, and the build failed at `-FailOn Warning`. Files under a
`Package` folder are left out of a folder search; a `Packages` folder, which holds Win32 apps,
stays in, and a file named directly on `-Path` is still analyzed.
- The gate's GitHub annotations percent-encode the `file=` and `title=` values as well as the
message, so a script outside `-Root`, whose path stays absolute, no longer ends the property list
at its drive letter.
Expand Down
21 changes: 15 additions & 6 deletions Examples/Invoke-IntuneScriptGate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@

.PARAMETER Path
Folders or scripts to analyze; folders are searched for .ps1 files recursively, leaving out
Pester suites (*.Tests.ps1), which the suite template places next to the scripts. A suite
named directly is analyzed. Default: the current directory.
Pester suites (*.Tests.ps1), which the suite template places next to the scripts, and the
contents of a Win32 app's Package folder (Win32\<app>\Package\, what becomes the .intunewin),
which the agent runs through the install command rather than as a script. A file named
directly is analyzed. Default: the current directory.

.PARAMETER FailOn
The lowest severity that fails the build: Error (default), Warning, Information, or None
Expand Down Expand Up @@ -155,14 +157,21 @@ function ConvertTo-AnnotationProperty {
(ConvertTo-AnnotationText -Text $Text).Replace(':', '%3A').Replace(',', '%2C')
}

# Folders are searched here rather than by Test-IntuneScript, so that a Pester suite next to the
# scripts (IntuneScripts.Tests.ps1, where the suite template puts it) is not analyzed as an Intune
# script; a suite named directly on -Path still is
# Folders are searched here rather than by Test-IntuneScript, so that two kinds of file the suite
# template puts under the script folders are not analyzed as Intune scripts: a Pester suite next to
# the scripts (IntuneScripts.Tests.ps1) and the contents of a Win32 app's Package folder
# (Win32\<app>\Package\), which become the .intunewin and run through the install command as
# SYSTEM, where an install.ps1 would otherwise be read as a user-context platform script. A file
# named directly on -Path is still analyzed. Only the singular segment 'Package' is left out: a
# 'Packages' folder is one that holds Win32 apps, and their detection scripts stay in.
$scriptFiles = @(foreach ($item in $Path) {
foreach ($resolved in (Resolve-Path -Path $item)) {
if (Test-Path -LiteralPath $resolved.ProviderPath -PathType Container) {
Get-ChildItem -LiteralPath $resolved.ProviderPath -Recurse -Filter *.ps1 -File |
Where-Object { $_.Name -notlike '*.Tests.ps1' } | ForEach-Object { $_.FullName }
Where-Object {
$_.Name -notlike '*.Tests.ps1' -and
"$($_.DirectoryName)\" -notmatch '(?i)[\\/]Package[\\/]'

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The separator is appended so a Package folder that is the file's own parent matches too: DirectoryName ends in '\Package' with nothing after it, and the pattern needs a separator on both sides to reject 'Packages' and 'MyPackage'. The class accepts both slashes so the same line holds on a Linux runner.

} | ForEach-Object { $_.FullName }
}
else { $resolved.ProviderPath }
}
Expand Down
3 changes: 2 additions & 1 deletion Examples/intune-script-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ env:
# Folders holding the scripts: remediations, platform scripts, Win32 detection and requirement
# scripts. Folder and file names tell the analysis what each script is (Remediations\<name>\
# Detect.ps1, Win32\<app>\Detect.ps1, PlatformScripts\*.ps1); a directive comment overrides.
# Pester suites under them (*.Tests.ps1) are left out of the analysis.
# Pester suites under them (*.Tests.ps1) and the contents of a Win32 app's Package folder
# (Win32\<app>\Package\, what becomes the .intunewin) are left out of the analysis.
SCRIPT_PATHS: ./Intune
# Error fails the build; Warning is stricter; None reports only
FAIL_ON: Error
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -660,8 +660,9 @@ as a Markdown table per kind, Broken first.
repository of Intune scripts: on every pull request that touches a script it takes the module from a
checked-in copy when `ISL_MODULE_PATH` names its manifest, or installs it from the Gallery,
runs [Examples/Invoke-IntuneScriptGate.ps1](./Examples/Invoke-IntuneScriptGate.ps1) over the script
folders (a Pester suite under them, `*.Tests.ps1`, is left out) and fails the build at the chosen
severity. The gate script annotates the changed files
folders (a Pester suite under them, `*.Tests.ps1`, and the contents of a Win32 app's `Package`
folder are left out) and fails the build at the chosen severity. The gate script annotates the
changed files
with each finding (file, line, rule, message), appends a Markdown summary to the job, and returns
the findings with `-PassThru`; it works on any CI with `-Annotate:$false`. With `-SarifPath` it
also writes a SARIF 2.1.0 log (`Export-IntuneFindingSarif`: a rule entry per rule with its help
Expand Down
20 changes: 20 additions & 0 deletions Tests/Integration/ScriptGate.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ BeforeAll {
# A Pester suite next to the scripts, as the suite template places it: not an Intune script
[System.IO.File]::WriteAllText((Join-Path $script:Scripts 'IntuneScripts.Tests.ps1'),
"BeforeAll { Import-Module IntuneScriptLab }`r`nDescribe 'x' { It 'y' { 1 | Should-Be 1 } }`r`n", $utf8Bom)
# A Win32 app's package content, as the suite template lays it out: the installer runs through
# the install command as SYSTEM, so it is not an Intune script either. Read as one, its
# machine-wide write is a user-context warning. 'Packages' is a folder of Win32 apps and stays in.
$null = New-Item -ItemType Directory -Path (Join-Path $script:Scripts 'Win32\Widget\Package') -Force
$null = New-Item -ItemType Directory -Path (Join-Path $script:Scripts 'Packages\Gadget') -Force
[System.IO.File]::WriteAllText((Join-Path $script:Scripts 'Win32\Widget\Package\install.ps1'),
"New-Item -Path HKLM:\SOFTWARE\Widget -Force | Out-Null`r`nexit 0`r`n", $utf8Bom)
[System.IO.File]::WriteAllText((Join-Path $script:Scripts 'Packages\Gadget\Detect.ps1'),
"Write-Output 'installed'`r`nexit 0`r`n", $utf8Bom)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Kept clean on purpose. Its first version returned before exit 1 and carried an error-level finding, and 'Packages/Gadget' sorts before 'Remediations/Widget', so it became the first error annotation and the earlier test's assertion on that line failed. A clean Win32 detection still appears in the output through its IslAssumedContext line, which is all the new test needs.


# -File carries the script's exit code out as the process exit code on both hosts; a switch
# cannot be negated that way, so the quiet run uses -Command (and expects exit 0 anyway)
Expand Down Expand Up @@ -110,6 +119,17 @@ Describe 'Invoke-IntuneScriptGate' -Tag 'Integration', 'Gate' {
($direct.Output -join "`n") | Should-BeLikeString '*IslModuleDependency*'
}

It 'leaves a Win32 Package folder out, keeps a Packages folder in, and analyzes a named installer' {
$run = Invoke-GateFile -Arguments @('-Path', $script:Scripts, '-Annotate', '-Root', $script:Scripts,
'-FailOn', 'None')
$run.ExitCode | Should-Be 0
($run.Output -join "`n") | Should-NotBeLikeString '*Package\install.ps1*'
($run.Output -join "`n") | Should-BeLikeString '*Packages\Gadget\Detect.ps1*'
$installer = Join-Path $script:Scripts 'Win32\Widget\Package\install.ps1'
$direct = Invoke-GateFile -Arguments @('-Path', $installer, '-FailOn', 'None')
($direct.Output -join "`n") | Should-BeLikeString '*IslContextIssue*'
}

It 'percent-encodes the colon of an absolute path outside -Root in the file property' {
# With -Root below the scripts, every path stays absolute and carries a drive letter
$run = Invoke-GateFile -Arguments @('-Path', $script:Scripts, '-Annotate', '-FailOn', 'None',
Expand Down
Loading