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
56 changes: 44 additions & 12 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
name: Publish Module
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
Publish:
uses: HeyItsGilbert/.github/.github/workflows/PublishModule.yml@main
secrets: inherit
name: Publish Module
on:
push:
branches:
- main
workflow_dispatch:
inputs:
version:
description: "The version to publish. Leave empty to use the version in the module manifest."
required: false
type: string
force:
description: "If true, bypass the PSGallery version existence check. Use when re-triggering a failed publish job (pattern: force=true, create_release=false, publish=true)."
required: false
type: boolean
default: false
dry_run:
description: "If true, skip actual publishing and just validate the workflow logic."
required: false
type: boolean
default: false
create_release:
description: "If false, skip creating the GitHub release and tag."
required: false
type: boolean
default: true
publish:
description: "If false, skip publishing to PowerShell Gallery."
required: false
type: boolean
default: true
permissions:
contents: write
jobs:
publish:
name: Publish Module
uses: HeyItsGilbert/.github/.github/workflows/PublishModule.yml@main
with:
version: ${{ inputs.version || '' }}
force: ${{ inputs.force || false }}
dry_run: ${{ inputs.dry_run || false }}
create_release: ${{ github.event_name != 'workflow_dispatch' || inputs.create_release }}
publish: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }}
secrets: inherit
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,37 @@ $secureString | ConvertFrom-SecureString -Key $keyBytes

**Why it matters:** Without a key, encrypted strings cannot be decrypted on different machines or by different users, limiting portability and automation scenarios.

### Measure-GremlinCharacter

**Severity:** Varies (Error / Warning / Information depending on the character)

Detects invisible or visually deceptive Unicode characters ("gremlins") such as
zero-width spaces, non-breaking spaces, bidirectional overrides, and curly quotes
(`‘ ’ “ ”`). These characters are nearly impossible to spot in an editor but can
introduce subtle bugs or security issues.

```powershell
# This will trigger the rule (curly quotes instead of straight ASCII quotes):
Write-Host “Hello, World!”
```

**Why it matters:** Gremlin characters can silently break string comparisons,
sneak past code review, or be used to disguise malicious code. Surfacing them at
analysis time makes the invisible visible.

> [!IMPORTANT]
> **File encoding affects detection.** The bytes a parser sees depend on how the
> file is decoded, and that default differs by PowerShell edition:
>
> - **PowerShell 7+** reads files without a byte-order mark (BOM) as **UTF-8**.
> - **Windows PowerShell 5.1** reads BOM-less files using the system **ANSI code
> page** (e.g. Windows-1252).
>
> A multi-byte UTF-8 gremlin (for example an en dash `U+2013`, stored as
> `E2 80 93`) is decoded correctly under PowerShell 7, but under 5.1 those bytes
> are interpreted as separate Windows-1252 characters — so the rule may report a
> different code point, or miss it entirely. **Save files as UTF-8 with a BOM** to
> get consistent results across both editions.

## Examples

Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/Gremlin.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function Test-Suppressed {
function Test-Suppressed {
param()
Write-Host '–hello'
}
2 changes: 1 addition & 1 deletion tests/fixtures/GremlinSuppressed.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function Test-Suppressed {
function Test-Suppressed {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
'GoodEnoughRules\Measure-GremlinCharacter',
'',
Expand Down
Loading