Skip to content
Open
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
41 changes: 41 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Lint Scripts

on:
Comment on lines +1 to +3

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/*' | sort

printf '%s\n' '--- lint workflow ---'
cat -n .github/workflows/lint.yml

printf '%s\n' '--- repository permission declarations ---'
rg -n --glob '.github/workflows/*.yml' --glob '.github/workflows/*.yaml' '^[[:space:]]*permissions:|^[[:space:]]*(contents|actions|checks|pull-requests):' .github/workflows || true

printf '%s\n' '--- lint workflow references and documentation ---'
rg -n -C 3 'Lint Scripts|lint\.yml|checks run|every push|pull request|GITHUB_TOKEN|permissions' README.md .github . 2>/dev/null | head -250

Repository: terjeinnerdal/scripts

Length of output: 7842


🌐 Web query:

site:docs.github.com GitHub Actions GITHUB_TOKEN permissions unspecified permissions contents read default repository organization settings

💡 Result:

When you explicitly define the permissions key in a GitHub Actions workflow file, any permissions not listed in that key are automatically set to none (no access), with the sole exception of the metadata scope, which always retains read access [1][2]. This behavior applies whether you are configuring permissions at the workflow level or the job level [1]. By default, if the permissions key is omitted entirely, the GITHUB_TOKEN receives permissions based on the settings configured at the enterprise, organization, or repository level [1][3][4]. These repository and organization settings offer two primary modes [5][6]: 1. Permissive: Grants read/write access to most scopes, including contents and packages. 2. Restricted: Limits the GITHUB_TOKEN to read-only access for contents and packages, with all other scopes set to none. Because the explicit use of the permissions key overrides these defaults and sets unspecified permissions to none, it is considered a security best practice to use this key to follow the principle of least privilege, explicitly granting only the access required for your specific workflow or job [7][1][2].

Citations:


🌐 Web query:

site:github.com/actions/checkout README token contents read permission

💡 Result:

The phrase "README token contents read permission" refers to a common configuration requirement for GitHub Actions workflows, specifically when using the actions/checkout action [1][2][3]. In GitHub Actions, when you define the permissions key within a workflow file, all permissions not explicitly specified are set to none [4]. Because the actions/checkout action requires access to the repository's files to clone them, it specifically necessitates the contents: read permission [1][5][6]. If you configure other permissions for your job (like pages: write or id-token: write) but fail to include contents: read, the checkout action will fail because the GITHUB_TOKEN used by the runner will lack the necessary access to read the repository's contents [4][6]. To ensure the action functions correctly, it is recommended to include the following in your workflow YAML [1][7][3]: permissions: contents: read This setting provides the minimum required scope for the action to pull the code from the repository [8][3]. If you are using a Personal Access Token (PAT) instead of the default GITHUB_TOKEN, that token must also be granted at least read access to Contents and Metadata [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

path = Path(".github/workflows/lint.yml")
text = path.read_text()

print("permissions_key_present:", bool(re.search(r"(?m)^permissions:\s*$", text)))
print("github_token_references:", len(re.findall(r"GITHUB_TOKEN|github\.token", text)))

uses = re.findall(r"(?m)^\s*uses:\s*([^\s#]+)", text)
print("actions_used:", uses)
print("only_read_or_analysis_actions:", all(
    action.startswith(("actions/checkout@", "ludeeus/action-shellcheck@", "microsoft/psscriptanalyzer-action@"))
    for action in uses
))
PY

printf '%s\n' '--- public repository metadata ---'
curl -fsSL -H 'Accept: application/vnd.github+json' \
  https://api.github.com/repos/terjeinnerdal/scripts |
  jq '{private, visibility, default_branch, archived}'

printf '%s\n' '--- publicly accessible Actions permission metadata ---'
curl -fsSL -H 'Accept: application/vnd.github+json' \
  https://api.github.com/repos/terjeinnerdal/scripts/actions/permissions |
  jq '{enabled, default_workflow_permissions, can_approve_pull_request_reviews}' || true

Repository: terjeinnerdal/scripts

Length of output: 630


Restrict the workflow token permissions.

Because permissions is omitted, GITHUB_TOKEN uses repository, organization, or enterprise defaults, which can grant write access. The jobs only check out and analyze source files. Set workflow-level contents: read; unspecified permissions then remain unavailable.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 1-37: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 1 - 3, Update the workflow-level
configuration in “Lint Scripts” to explicitly set GITHUB_TOKEN permissions with
contents read access, ensuring all unspecified permissions remain unavailable
while preserving the existing lint behavior.

Source: Linters/SAST tools

push:
branches: [ main, master, "feature/**" ]
pull_request:
branches: [ main, master ]
Comment on lines +3 to +7

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the trigger filters with the documented CI scope.

README.md states that the checks run on every push and pull request. This workflow runs only on selected branches. Remove the branch filters if all refs are required. Otherwise, document the restricted scope in README.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 3 - 7, Update the workflow trigger
configuration under on in lint.yml to run for every push and pull_request by
removing both branches filters, matching the documented CI scope in README.md.

workflow_dispatch:

jobs:
shellcheck:
name: ShellCheck (Bash)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false


- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
with:
scandir: '.'
severity: style

psscriptanalyzer:
name: PSScriptAnalyzer (PowerShell)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Run PSScriptAnalyzer
uses: microsoft/psscriptanalyzer-action@v1.1
with:
path: './PowerShell'
recurse: true
settings: PSGallery
24 changes: 7 additions & 17 deletions PowerShell/DeleteEmptyFolders.ps1
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
# DeleteEmptyFolders.ps1

[CmdletBinding()]
Param (
<#
.SYNOPSIS
Deletes empty subdirectories within a specified path (wrapper for ManageEmptyFolders.ps1).
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param (
[Parameter(Mandatory = $true, Position = 0, HelpMessage = "The root path to search for empty folders.")]
[string]$Path
)

# Validate that the path exists and is a directory
if (-not (Test-Path -Path $Path -PathType Container)) {
Write-Error "The path '$Path' does not exist or is not a folder."
exit 1
}

Write-Verbose "Searching for empty folders under '$Path'..."

# Get all directories recursively, then filter for those that have no child items (files or folders).
# The results are sorted by the length of the FullName in descending order to ensure subdirectories are deleted before their parents.
Get-ChildItem -Path $Path -Recurse -Directory | Where-Object { -not $_.GetFileSystemInfos() } | Sort-Object { $_.FullName.Length } -Descending | Remove-Item -Force -Verbose

Write-Host "Finished deleting empty folders."
& "$PSScriptRoot/ManageEmptyFolders.ps1" -Path $Path -Delete
Comment on lines +5 to +11

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- tracked PowerShell files ---'
git ls-files 'PowerShell/*'

printf '%s\n' '--- DeleteEmptyFolders.ps1 ---'
cat -n PowerShell/DeleteEmptyFolders.ps1

printf '%s\n' '--- ManageEmptyFolders.ps1 ---'
cat -n PowerShell/ManageEmptyFolders.ps1

printf '%s\n' '--- related ShouldProcess/delegation patterns ---'
rg -n -C 3 'ShouldProcess|SupportsShouldProcess|ManageEmptyFolders|DeleteEmptyFolders|WhatIf|Confirm' PowerShell

Repository: terjeinnerdal/scripts

Length of output: 10026


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- PowerShell runtime availability ---'
if command -v pwsh >/dev/null 2>&1; then
  pwsh --version
  printf '%s\n' '--- common-parameter preference propagation probe ---'
  pwsh -NoProfile -NonInteractive -Command '
    function Child {
      [CmdletBinding(SupportsShouldProcess = $true)]
      param()
      [pscustomobject]@{
        WhatIfPreference = $WhatIfPreference
        ConfirmPreference = $ConfirmPreference
        ShouldProcess = $PSCmdlet.ShouldProcess("target", "action")
      }
    }
    function Wrapper {
      [CmdletBinding(SupportsShouldProcess = $true)]
      param()
      Child
    }
    "normal:"
    Wrapper | Format-List
    "whatif:"
    Wrapper -WhatIf | Format-List
    "confirm:"
    Wrapper -Confirm:$false | Format-List
  '
else
  echo 'pwsh is unavailable'
fi

printf '%s\n' '--- reusable function implementation ---'
cat -n PowerShell/ManageEmptyFolders.Function.ps1

Repository: terjeinnerdal/scripts

Length of output: 3325


🏁 Script executed:

#!/bin/bash
set -eu

pwsh -NoProfile -NonInteractive -Command '
  $child = {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param()
    [pscustomobject]@{
      WhatIfPreference = $WhatIfPreference
      ConfirmPreference = $ConfirmPreference
    }
  }

  $wrapper = {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param()
    & $child
  }

  "normal:"
  & $wrapper | Format-List
  "whatif:"
  & $wrapper -WhatIf | Format-List
  "confirm:"
  & $wrapper -Confirm | Format-List
'

Repository: terjeinnerdal/scripts

Length of output: 341


Call ShouldProcess() before invoking the child script.

ManageEmptyFolders.ps1 already honors inherited -WhatIf and -Confirm preferences. Add a wrapper-level $PSCmdlet.ShouldProcess() check and pass -Confirm:$false to the child to prevent duplicate prompts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PowerShell/DeleteEmptyFolders.ps1` around lines 5 - 11, Update the wrapper
around the child-script invocation in DeleteEmptyFolders.ps1 to call
$PSCmdlet.ShouldProcess() before deletion, using the target path and a
descriptive action. Invoke ManageEmptyFolders.ps1 only when approved, and pass
-Confirm:$false so inherited confirmation preferences do not trigger a second
prompt.

Source: Coding guidelines

56 changes: 56 additions & 0 deletions PowerShell/DeleteFiles.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<#
.SYNOPSIS
Deletes files based on path, filter, and age with safety dry-run support (-WhatIf).

.DESCRIPTION
Safely removes files matching specified patterns and optionally older than a given number of days.
Supports -WhatIf and -Confirm standard PowerShell switches.

.EXAMPLE
.\DeleteFiles.ps1 -Path "C:\Logs" -Filter "*.log" -DaysOld 30 -Recurse -WhatIf
#>

[CmdletBinding(SupportsShouldProcess = $true)]
param(
[Parameter(Mandatory = $false, Position = 0)]
[string]$Path = ".",

[Parameter(Mandatory = $false)]
[string]$Filter = "*",

[Parameter(Mandatory = $false)]
[int]$DaysOld = 0,
Comment on lines +21 to +22

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file=$(git ls-files | rg '^PowerShell/DeleteFiles\.ps1$')
cat -n "$file"
printf '\n--- references to DaysOld and cutoff ---\n'
rg -n -C 3 '\$DaysOld|cutoffDate|AddDays|ShouldProcess|Remove-Item|Test-Path' "$file"

Repository: terjeinnerdal/scripts

Length of output: 3317


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- PowerShell runtime availability ---'
if command -v pwsh >/dev/null 2>&1; then
  pwsh -NoProfile -NonInteractive -Command '
    $now = Get-Date
    foreach ($days in @(-1, 0, 30)) {
      $cutoff = $now.AddDays(-$days)
      [pscustomobject]@{
        DaysOld = $days
        CutoffRelativeToNow = $cutoff - $now
        OlderThanCutoff = ((New-TimeSpan -Start $now.AddDays(-365) -End $cutoff).TotalDays -gt 0)
      }
    } | ConvertTo-Json -Compress
  '
else
  echo 'pwsh unavailable'
fi

printf '%s\n' '--- deterministic date comparison model ---'
python3 - <<'PY'
from datetime import datetime, timedelta, timezone
now = datetime(2026, 8, 1, tzinfo=timezone.utc)
for days in (-1, 0, 30):
    cutoff = now - timedelta(days=days)
    old_file = now - timedelta(days=365)
    print({
        "DaysOld": days,
        "cutoff_delta_days": (cutoff - now).days,
        "old_file_is_selected": old_file < cutoff,
    })
PY

Repository: terjeinnerdal/scripts

Length of output: 322


🏁 Script executed:

#!/bin/bash
set -eu
if command -v pwsh >/dev/null 2>&1; then
  pwsh -NoProfile -NonInteractive -Command '
    $now = Get-Date
    $results = foreach ($days in @(-1, 0, 30)) {
      $cutoff = $now.AddDays(-$days)
      [pscustomobject]@{
        DaysOld = $days
        CutoffDeltaDays = ($cutoff - $now).TotalDays
        OldFileSelected = (($now.AddDays(-365)) -lt $cutoff)
      }
    }
    $results | ConvertTo-Json -Compress
  '
else
  echo 'pwsh unavailable'
fi

Repository: terjeinnerdal/scripts

Length of output: 342


Reject negative DaysOld values.

When DaysOld is -1, $cutoffDate is one day in the future. The filter then selects nearly every matching file. Reject values below zero before calculating $cutoffDate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PowerShell/DeleteFiles.ps1` around lines 21 - 22, Validate the DaysOld
parameter in the DeleteFiles script and reject values below zero before
calculating $cutoffDate. Preserve the existing behavior for zero and positive
values, and provide a clear validation error for negative input.


[Parameter(Mandatory = $false)]
[switch]$Recurse
)

if (-not (Test-Path -Path $Path)) {
Write-Error "Target path '$Path' does not exist."
exit 1
}

$cutoffDate = (Get-Date).AddDays(-$DaysOld)

$getParams = @{
Path = $Path
Filter = $Filter
File = $true
Recurse = $Recurse
Comment on lines +28 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- file list ---'
git ls-files 'PowerShell/DeleteFiles.ps1'
printf '%s\n' '--- file outline ---'
ast-grep outline PowerShell/DeleteFiles.ps1 --lang powershell 2>/dev/null || true
printf '%s\n' '--- relevant source ---'
cat -n PowerShell/DeleteFiles.ps1

Repository: terjeinnerdal/scripts

Length of output: 2131


🏁 Script executed:

#!/bin/bash
set -eu
if command -v pwsh >/dev/null 2>&1; then
  pwsh -NoProfile -NonInteractive -Command '
    [pscustomobject]@{
      FileAcceptedByTestPath = Test-Path -Path "/etc/passwd"
      DirectoryAcceptedByTestPath = Test-Path -Path "/etc"
      WildcardAcceptedByTestPath = Test-Path -Path "/etc/pass*"
      WildcardChildItems = @((Get-ChildItem -Path "/etc/pass*" -File -ErrorAction Stop).FullName)
      LiteralFileIsContainer = Test-Path -LiteralPath "/etc/passwd" -PathType Container
      LiteralDirectoryIsContainer = Test-Path -LiteralPath "/etc" -PathType Container
    } | ConvertTo-Json -Compress
  '
elif command -v powershell >/dev/null 2>&1; then
  powershell -NoProfile -NonInteractive -Command '
    [pscustomobject]@{
      FileAcceptedByTestPath = Test-Path -Path "/etc/passwd"
      DirectoryAcceptedByTestPath = Test-Path -Path "/etc"
      WildcardAcceptedByTestPath = Test-Path -Path "/etc/pass*"
      WildcardChildItems = @((Get-ChildItem -Path "/etc/pass*" -File -ErrorAction Stop).FullName)
      LiteralFileIsContainer = Test-Path -LiteralPath "/etc/passwd" -PathType Container
      LiteralDirectoryIsContainer = Test-Path -LiteralPath "/etc" -PathType Container
    } | ConvertTo-Json -Compress
  '
else
  printf '%s\n' 'No PowerShell runtime is available; source-level verification only.'
fi

Repository: terjeinnerdal/scripts

Length of output: 363


Require a directory and use literal paths.

Test-Path -Path $Path accepts files and wildcard patterns. Get-ChildItem -Path $Path also expands wildcard patterns. Use -LiteralPath $Path -PathType Container for validation and LiteralPath in $getParams.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PowerShell/DeleteFiles.ps1` around lines 28 - 39, Update the path validation
to use Test-Path with -LiteralPath $Path and -PathType Container, ensuring the
target is an existing directory rather than a file or wildcard pattern. In the
$getParams used by Get-ChildItem, replace Path with LiteralPath while preserving
the existing Filter, File, and Recurse options.

Source: Coding guidelines

}

$filesToDelete = Get-ChildItem @getParams | Where-Object { $_.LastWriteTime -lt $cutoffDate }
Comment on lines +35 to +42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- tracked file ---'
git ls-files -- 'PowerShell/DeleteFiles.ps1'
printf '%s\n' '--- outline ---'
ast-grep outline 'PowerShell/DeleteFiles.ps1' --lang powershell 2>/dev/null || true
printf '%s\n' '--- relevant source ---'
cat -n 'PowerShell/DeleteFiles.ps1'

Repository: terjeinnerdal/scripts

Length of output: 2129


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- PowerShell runtimes available ---'
command -v pwsh || true
command -v powershell || true
printf '%s\n' '--- enumeration and deletion calls ---'
rg -n -C 3 'Get-ChildItem|filesToDelete|Remove-Item|ShouldProcess|Test-Path' -- 'PowerShell/DeleteFiles.ps1'
printf '%s\n' '--- repository-wide callers or wrappers ---'
rg -n 'DeleteFiles\.ps1|Get-ChildItem `@getParams`' . --glob '!PowerShell/DeleteFiles.ps1' || true

Repository: terjeinnerdal/scripts

Length of output: 1592


🏁 Script executed:

pwsh -NoProfile -NonInteractive -Command '
$valid = [System.IO.Path]::GetTempPath()
$missing = Join-Path $valid "__coderabbit_missing_path__"
$records = @(Get-ChildItem -Path @($valid, $missing) -File -ErrorAction Continue 2>$null)
"partial_count=$($records.Count)"
try {
    $records = @(Get-ChildItem -Path @($valid, $missing) -File -ErrorAction Stop)
    "stop_threw=False"
}
catch {
    "stop_threw=True"
    "exception_type=$($_.Exception.GetType().FullName)"
}
'

Repository: terjeinnerdal/scripts

Length of output: 258


Fail closed when enumeration reports an error.

Get-ChildItem can return a partial list after a non-terminating error. With -Recurse, the script can then delete only accessible files. Add -ErrorAction Stop and handle enumeration errors before the deletion loop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PowerShell/DeleteFiles.ps1` around lines 35 - 42, Update the Get-ChildItem
invocation used to populate $filesToDelete with -ErrorAction Stop, and wrap that
enumeration in error handling that exits or otherwise prevents the deletion loop
from running when an error occurs. Preserve the existing filtering and deletion
behavior for successful enumeration.


if ($filesToDelete.Count -eq 0) {
Write-Host "No files found matching criteria in '$Path'." -ForegroundColor Yellow
return
}

Write-Host "Found $($filesToDelete.Count) file(s) to process." -ForegroundColor Cyan

foreach ($file in $filesToDelete) {
if ($PSCmdlet.ShouldProcess($file.FullName, "Delete file")) {
Remove-Item -Path $file.FullName -Force
Comment on lines +52 to +53

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- file inventory ---'
git ls-files 'PowerShell/DeleteFiles.ps1'

printf '%s\n' '--- script outline ---'
if command -v ast-grep >/dev/null 2>&1; then
  ast-grep outline PowerShell/DeleteFiles.ps1 --lang powershell || true
fi

printf '%s\n' '--- relevant source ---'
nl -ba PowerShell/DeleteFiles.ps1 | sed -n '1,130p'

printf '%s\n' '--- runtime availability ---'
command -v pwsh || true
command -v powershell || true

Repository: terjeinnerdal/scripts

Length of output: 457


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
p = Path("PowerShell/DeleteFiles.ps1")
text = p.read_text()
for term in ("Remove-Item -Path", "Remove-Item -LiteralPath", "ShouldProcess", "Get-ChildItem"):
    print(f"{term}: {text.count(term)} occurrence(s)")
PY

Repository: terjeinnerdal/scripts

Length of output: 298


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- relevant source ---'
sed -n '1,130p' PowerShell/DeleteFiles.ps1

printf '%s\n' '--- PowerShell parameter semantics ---'
pwsh -NoProfile -NonInteractive -Command '
  $cmd = Get-Command Remove-Item
  $path = $cmd.Parameters["Path"]
  $literal = $cmd.Parameters["LiteralPath"]
  [pscustomobject]@{
    PathPosition = $path.Attributes.Position
    PathValueFromRemainingArguments = $path.Attributes.ValueFromRemainingArguments
    LiteralPathPosition = $literal.Attributes.Position
    LiteralPathValueFromRemainingArguments = $literal.Attributes.ValueFromRemainingArguments
  } | Format-List
  Get-Help Remove-Item -Parameter Path -ErrorAction SilentlyContinue |
    Out-String -Width 200
  Get-Help Remove-Item -Parameter LiteralPath -ErrorAction SilentlyContinue |
    Out-String -Width 200
'

Repository: terjeinnerdal/scripts

Length of output: 2563


🏁 Script executed:

#!/bin/bash
set -eu

pwsh -NoProfile -NonInteractive -Command '
  Set-Location (Get-Location).Path

  $wildcard = Join-Path (Get-Location) "PowerShell/[D]eleteFiles.ps1"
  $literal = Join-Path (Get-Location) "PowerShell/DeleteFiles.ps1"

  Write-Output "--- -Path wildcard probe ---"
  Remove-Item -Path $wildcard -Force -WhatIf -ErrorAction Continue

  Write-Output "--- -LiteralPath wildcard probe ---"
  Remove-Item -LiteralPath $wildcard -Force -WhatIf -ErrorAction Continue

  Write-Output "--- exact path probe ---"
  Remove-Item -Path $literal -Force -WhatIf -ErrorAction Continue
'

Repository: terjeinnerdal/scripts

Length of output: 743


Use -LiteralPath for deletion.

Remove-Item -Path interprets wildcard characters in $file.FullName. Use Remove-Item -LiteralPath $file.FullName -Force to target only the selected file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PowerShell/DeleteFiles.ps1` around lines 52 - 53, Update the Remove-Item
invocation within the ShouldProcess deletion block to use -LiteralPath instead
of -Path with $file.FullName, ensuring wildcard characters are treated literally
and only the selected file is deleted.

Source: Coding guidelines

Write-Host "Deleted: $($file.FullName)" -ForegroundColor Green
Comment on lines +52 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
set -eu

printf '%s\n' '--- file list ---'
git ls-files 'PowerShell/DeleteFiles.ps1'

printf '%s\n' '--- relevant source ---'
cat -n PowerShell/DeleteFiles.ps1 | sed -n '1,110p'

printf '%s\n' '--- deletion and error handling references ---'
rg -n -C 3 'Remove-Item|ErrorAction|try|catch|ShouldProcess|Write-Host' PowerShell/DeleteFiles.ps1

Repository: terjeinnerdal/scripts

Length of output: 2986


🏁 Script executed:

#!/bin/sh
set -eu

if command -v pwsh >/dev/null 2>&1; then
    PS= pwsh
elif command -v powershell >/dev/null 2>&1; then
    PS= powershell
else
    printf '%s\n' 'No PowerShell executable is available in the sandbox.'
    exit 0
fi

"$PS" -NoProfile -NonInteractive -Command '
$Error.Clear()
Remove-Item -Path "/path/that/does/not/exist/coderabbit-probe" -Force
Write-Output ("success-message-reached; error-count=" + $Error.Count + "; last-success=" + $?)
'

Repository: terjeinnerdal/scripts

Length of output: 868


Stop on Remove-Item failure before reporting deletion

Remove-Item continues after a non-terminating error. The script can report Deleted when removal fails. Add -ErrorAction Stop or handle the failure before writing the success message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PowerShell/DeleteFiles.ps1` around lines 52 - 54, Update the Remove-Item call
within the ShouldProcess block to use terminating error behavior, such as
ErrorAction Stop, so failures prevent execution from reaching the Write-Host
success message. Keep the existing deletion and success-reporting flow unchanged
for successful removals.

}
}
16 changes: 6 additions & 10 deletions PowerShell/FindEmptySubDirectories.ps1
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
# findEmptySubDirectories.ps1

<#
.SYNOPSIS
Finds empty subdirectories within a specified path (wrapper for ManageEmptyFolders.ps1).
#>
[CmdletBinding()]
Param(
param (
[Parameter(Mandatory = $true, Position = 0, HelpMessage = "The root path to search for empty directories.")]
[string]$Path
)

if (-not (Test-Path -Path $Path -PathType Container)) {
Write-Error "The path '$Path' does not exist or is not a folder."
exit 1
}

# Get all directories recursively, then filter for those that have no child items (files or folders).
Get-ChildItem -Path $Path -Recurse -Directory | Where-Object { -not $_.GetFileSystemInfos() } | Select-Object -ExpandProperty FullName
& "$PSScriptRoot/ManageEmptyFolders.ps1" -Path $Path
6 changes: 0 additions & 6 deletions PowerShell/FindEmptySubDirectories.txt

This file was deleted.

6 changes: 3 additions & 3 deletions PowerShell/ManageEmptyFolders.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
A switch parameter that, if present, causes the script to delete the empty folders it finds.

.EXAMPLE
.\Manage-EmptyFolders.ps1 -Path "C:\Users\Me\Documents"
.\ManageEmptyFolders.ps1 -Path "C:\Users\Me\Documents"
Description: Lists all empty folders found under C:\Users\Me\Documents.

.EXAMPLE
.\Manage-EmptyFolders.ps1 -Path "C:\Temp" -Delete
.\ManageEmptyFolders.ps1 -Path "C:\Temp" -Delete
Description: Deletes all empty folders found under C:\Temp after prompting for confirmation.

.EXAMPLE
.\Manage-EmptyFolders.ps1 -Path "C:\Temp" -Delete -WhatIf
.\ManageEmptyFolders.ps1 -Path "C:\Temp" -Delete -WhatIf
Description: Shows which empty folders would be deleted under C:\Temp without actually deleting them.
#>
[CmdletBinding(SupportsShouldProcess = $true)]
Expand Down
59 changes: 0 additions & 59 deletions PowerShell/ManageEmptyFolders2.ps1

This file was deleted.

108 changes: 102 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,107 @@
# scripts
# Scripts Repository

PowerShell (.ps1), python (.py), bash (.sh)
A curated collection of automation scripts, tools, and utility modules for **Bash**, **PowerShell**, and **Docker**.

## Bash
---

Different bash scripts
## 🛠️ Repository Overview

### NordVPN
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language to the repository-tree code fence.

markdownlint-cli2 reports MD040 for this fence. Use text because the block contains a directory tree.

Proposed fix
-```
+```text
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 9, Update the repository-tree fenced code block in the
README to specify the text language, changing the unlabeled fence to a
text-labeled fence so markdownlint MD040 passes.

Source: Linters/SAST tools

.
├── .github/
│ └── workflows/
│ └── lint.yml # GitHub Actions CI workflow (ShellCheck & PSScriptAnalyzer)
├── bash/
│ ├── AutoRemoveSnapd.sh # Removes snapd and associated packages on Debian/Ubuntu
│ ├── countLines.sh # Counts lines in a text file with line numbers
│ ├── import-kali.sh # Imports Kali Linux package repositories and GPG keys
│ ├── docker/
│ │ └── compose.yaml # Docker Compose setup
│ ├── encryption/
│ │ ├── encrypt_file.sh # AES-256-CBC file encryption (OpenSSL + PBKDF2)
│ │ └── decrypt_file.sh # AES-256-CBC file decryption (OpenSSL)
│ └── nord/ # NordVPN Meshnet control scripts
│ ├── config.sh # NordVPN configuration helper
│ ├── connect.sh # Connects to NordVPN
│ ├── copy_scripts.sh # Deployment helper script
│ ├── exit_node.sh # Manages and sets Meshnet exit node routing
│ ├── list_peers.sh # Lists Meshnet peers filtered by status
│ ├── login.sh # Interactive NordVPN login
│ ├── logout.sh # NordVPN logout
│ ├── nord_watchdog.sh # NordVPN connection watchdog daemon
│ ├── reset.sh # NordVPN settings reset
│ └── set_nickname.sh # Sets peer nickname in Meshnet
└── PowerShell/
├── CreateFile.ps1 # Creates test files of a specified size
├── DeleteEmptyFolders.ps1 # Wrapper to delete empty subdirectories
├── DeleteFiles.ps1 # Safe file deletion with age filtering & -WhatIf dry-run
├── FindEmptySubDirectories.ps1 # Wrapper to find empty subdirectories
├── ManageEmptyFolders.ps1 # Primary script for listing and removing empty folders
├── ManageEmptyFolders.Function.ps1 # Reusable PowerShell function module
└── NextPVR.ps1 # NextPVR media recorder helper
```

Make me an exit_node god damn it!
---

## 🚀 Usage Guide

### Bash Scripts (`bash/`)

#### 🔐 File Encryption & Decryption (`bash/encryption/`)
Encrypt and decrypt files using OpenSSL AES-256-CBC with PBKDF2 key derivation (100,000 iterations):

```bash
# Encrypt a file (prompts securely for passphrase)
./bash/encryption/encrypt_file.sh document.pdf

# Decrypt an encrypted file
./bash/encryption/decrypt_file.sh document.pdf.enc
```

#### 🌐 NordVPN Meshnet Tools (`bash/nord/`)
Manage NordVPN connections and Meshnet exit nodes:

```bash
# List online Meshnet peers
./bash/nord/list_peers.sh online

# Configure an exit node
./bash/nord/exit_node.sh

# Run watchdog daemon to monitor and maintain VPN connectivity
./bash/nord/nord_watchdog.sh
```

---

### PowerShell Scripts (`PowerShell/`)

#### 📁 Empty Folder Cleanup (`PowerShell/ManageEmptyFolders.ps1`)
Find or delete empty folder hierarchies (deepest nested folders deleted first):

```powershell
# List all empty subdirectories under a path
.\PowerShell\ManageEmptyFolders.ps1 -Path "C:\Data"

# Delete empty subdirectories safely with confirmation
.\PowerShell\ManageEmptyFolders.ps1 -Path "C:\Data" -Delete

# Dry-run deletion using -WhatIf
.\PowerShell\ManageEmptyFolders.ps1 -Path "C:\Data" -Delete -WhatIf
Comment on lines +86 to +90

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- README excerpt ---'
sed -n '70,100p' README.md

printf '%s\n' '--- candidate script files ---'
fd -i 'ManageEmptyFolders.ps1' .

printf '%s\n' '--- parameter and deletion logic ---'
script="$(fd -i -t f 'ManageEmptyFolders.ps1' . | head -n 1)"
if [ -n "$script" ]; then
  rg -n -C 4 'param|Confirm|WhatIf|ShouldProcess|Delete|Remove-Item|SupportsShouldProcess|ConfirmImpact' "$script"
fi

printf '%s\n' '--- related documentation references ---'
rg -n -i -C 3 'ManageEmptyFolders|WhatIf|Confirm|confirmation|empty subdirector' README.md PowerShell 2>/dev/null || true

Repository: terjeinnerdal/scripts

Length of output: 10569


🌐 Web query:

PowerShell SupportsShouldProcess ConfirmImpact default ConfirmPreference default -Confirm automatic confirmation behavior

💡 Result:

In PowerShell, the automatic confirmation behavior is governed by the interaction between the ConfirmImpact of a command and the $ConfirmPreference preference variable [1][2]. The default values are: - $ConfirmPreference: High [2][3][4] - ConfirmImpact (for a function using [CmdletBinding(SupportsShouldProcess)] without an explicit ConfirmImpact specified): Medium [1][5] Automatic confirmation behavior: PowerShell automatically prompts the user for confirmation when a command's assigned ConfirmImpact level is equal to or greater than the current value of the $ConfirmPreference variable [2][6]. Because the default $ConfirmPreference is High and the default ConfirmImpact for a function supporting ShouldProcess is Medium, functions with the default impact do not prompt the user for confirmation by default [1][5][6]. To trigger an automatic confirmation prompt for such a function, you must either explicitly set the function's ConfirmImpact to High or lower the user's $ConfirmPreference to Medium or Low [1][5]. If the $ConfirmPreference variable is set to None, PowerShell will never automatically prompt for confirmation, regardless of the command's ConfirmImpact [2]. Users can always override this behavior for a single command by using the -Confirm or -Confirm:$false parameters [2].

Citations:


Add -Confirm to the destructive example.

-Delete does not prompt by default because the script uses the default ConfirmImpact of Medium, while $ConfirmPreference defaults to High. Add -Confirm, or change the description to state that confirmation requires the -Confirm switch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 86 - 90, Update the destructive
ManageEmptyFolders.ps1 README example to include the -Confirm switch with
-Delete, and update the dry-run example if needed to keep the documented command
behavior consistent.

```

#### 🧹 Safe File Cleanup (`PowerShell/DeleteFiles.ps1`)
Remove files matching a filter pattern and age threshold:

```powershell
# Delete .log files older than 30 days under C:\Logs (dry-run mode)
.\PowerShell\DeleteFiles.ps1 -Path "C:\Logs" -Filter "*.log" -DaysOld 30 -Recurse -WhatIf
```

---

## 🤖 Code Quality & CI

This repository uses [GitHub Actions](file:///.github/workflows/lint.yml) to ensure code quality on every push and pull request:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use a repository-relative workflow link.

file:///.github/workflows/lint.yml is a local file URI. It does not point to the repository file on GitHub. Use a relative link.

Proposed fix
-This repository uses [GitHub Actions](file:///.github/workflows/lint.yml) to ensure code quality on every push and pull request:
+This repository uses [GitHub Actions](./.github/workflows/lint.yml) to ensure code quality on every push and pull request:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
This repository uses [GitHub Actions](file:///.github/workflows/lint.yml) to ensure code quality on every push and pull request:
This repository uses [GitHub Actions](./.github/workflows/lint.yml) to ensure code quality on every push and pull request:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 105, Update the GitHub Actions link in the README to use
the repository-relative path .github/workflows/lint.yml instead of the local
file:// URI, preserving the surrounding description.

- **ShellCheck**: Static analysis for all `.sh` scripts.
- **PSScriptAnalyzer**: Best-practice rules for `.ps1` scripts.
2 changes: 1 addition & 1 deletion bash/AutoRemoveSnapd.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! /usr/bin/bash
#!/usr/bin/env bash

sudo apt autoremove snapd

2 changes: 1 addition & 1 deletion bash/countLines.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#! /usr/bin/bash
#!/usr/bin/env bash
FILENAME="$1"
echo "$FILENAME"
COUNTER=0
Expand Down
Loading
Loading