Repository moved to Forgejo

https://forgejo.oskamp.info/ivooskamp/Random_scripts.git
This commit is contained in:
Ivo Oskamp 2026-06-20 21:30:50 +02:00
parent 5514553dd3
commit d26a5d4a0a
19 changed files with 6 additions and 1917 deletions

13
.gitignore vendored
View File

@ -1,13 +0,0 @@
# Runtime/output
output/*
!output/.gitkeep
# PowerShell logs/transcripts
*.log
*.txt
# OS/editor
.DS_Store
Thumbs.db
.vscode/
.idea/

View File

@ -1,43 +1,9 @@
# Random Scripts
# Random_scripts — moved
Collection of operational scripts for Microsoft 365, Windows endpoint management, macOS, and Docker automation.
This repository has **moved to Forgejo**.
## Structure
➡️ **New location:** https://forgejo.oskamp.info/ivooskamp/Random_scripts.git
- `scripts/m365/smtp/`
- `Test-SMTPRelay-M365.ps1`
- `Test-SMTPSend-Auth-M365.ps1`
- `scripts/m365/exchange/`
- `get-mailbox-with-permissions.ps1`
- `scripts/m365/identity/`
- `m365-users-mfa.ps1`
- `scripts/m365/sharepoint/`
- `get-sharepoint-permissions.ps1`
- `scripts/windows/autopilot/`
- `Get-WindowsAutoPilotInfo.ps1`
- `scripts/windows/endpoint/`
- `Update rmm.ps1`
- `schedule_reboot.ps1`
- `uninstall-MS-Visual-C++-2010.ps1`
- `uuid.ps1`
- `scripts/macos/`
- `serialToAdminPassword.sh`
- `scripts/content/`
- `convert-md.bat`
- `automation/docker/`
- `build-and-push.sh`
- `update-and-build.sh`
- `docs/`
- working practices and conventions
- `output/`
- generated output files (CSV/logs), not committed
## Important Note
Legacy script filenames are intentionally **unchanged** to preserve compatibility with existing guides and runbooks. New scripts may use consistent lowercase naming.
## Usage
- Run scripts from the correct environment (PowerShell/Bash/Batch).
- Check required modules/permissions per script.
- Write exports to `output/` when possible.
```bash
git remote set-url origin https://forgejo.oskamp.info/ivooskamp/Random_scripts.git
```

View File

@ -1,269 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# ============================================================================
# build-and-push.sh
# Location: repo root
#
# Purpose:
# - Automatic version bump:
# 1 = patch, 2 = minor, 3 = major, t = test
# - Test builds: only update :dev (no commit/tag)
# - Release builds: update version.txt, commit, tag, push (to the current branch)
# - Build & push Docker images for each service under ./compose/*
# - Preflight checks: Docker daemon up, logged in to registry, valid names/tags
# - Summary: show all images + tags built and pushed
# - Branch visibility:
# - Shows currently checked out branch (authoritative)
# - Reads .last-branch for info (if present) when BRANCH is not set
# - Writes the current branch back to .last-branch at the end
#
# Usage:
# BRANCH=<branch> ./build-and-push.sh [bump] # BRANCH is optional; informative only
# ./build-and-push.sh [bump]
# If [bump] is omitted, you will be prompted (default = t).
# ============================================================================
DOCKER_REGISTRY="gitea.oskamp.info"
DOCKER_NAMESPACE="ivooskamp"
VERSION_FILE="version.txt"
START_VERSION="v0.1.0"
COMPOSE_DIR="containers"
LAST_BRANCH_FILE=".last-branch" # stored in repo root
# --- Input: prompt if missing ------------------------------------------------
BUMP="${1:-}"
if [[ -z "${BUMP}" ]]; then
echo "Select bump type: [1] patch, [2] minor, [3] major, [t] test (default: t)"
read -r BUMP
BUMP="${BUMP:-t}"
fi
if [[ "$BUMP" != "1" && "$BUMP" != "2" && "$BUMP" != "3" && "$BUMP" != "t" ]]; then
echo "[ERROR] Unknown bump type '$BUMP' (use 1, 2, 3, or t)."
exit 1
fi
# --- Helpers -----------------------------------------------------------------
read_version() {
if [[ -f "$VERSION_FILE" ]]; then
tr -d ' \t\n\r' < "$VERSION_FILE"
else
echo "$START_VERSION"
fi
}
write_version() {
echo "$1" > "$VERSION_FILE"
}
bump_version() {
local cur="$1"
local kind="$2"
local core="${cur#v}"
IFS='.' read -r MA MI PA <<< "$core"
case "$kind" in
1) PA=$((PA + 1));;
2) MI=$((MI + 1)); PA=0;;
3) MA=$((MA + 1)); MI=0; PA=0;;
*) echo "[ERROR] Unknown bump kind"; exit 1;;
esac
echo "v${MA}.${MI}.${PA}"
}
check_docker_ready() {
if ! docker info >/dev/null 2>&1; then
echo "[ERROR] Docker daemon not reachable. Is Docker running and do you have permission to use it?"
exit 1
fi
}
ensure_registry_login() {
local cfg="${HOME}/.docker/config.json"
if [[ ! -f "$cfg" ]]; then
echo "[ERROR] Docker config not found at $cfg. Please login: docker login ${DOCKER_REGISTRY}"
exit 1
fi
if ! grep -q "\"${DOCKER_REGISTRY}\"" "$cfg"; then
echo "[ERROR] No registry auth found for ${DOCKER_REGISTRY}. Please run: docker login ${DOCKER_REGISTRY}"
exit 1
fi
}
validate_repo_component() {
local comp="$1"
if [[ ! "$comp" =~ ^[a-z0-9]+([._-][a-z0-9]+)*$ ]]; then
echo "[ERROR] Invalid repository component '$comp'."
echo " Must match: ^[a-z0-9]+([._-][a-z0-9]+)*$ (lowercase, digits, ., _, - as separators)."
return 1
fi
}
validate_tag() {
local tag="$1"
local len="${#tag}"
if (( len < 1 || len > 128 )); then
echo "[ERROR] Invalid tag length ($len). Must be between 1 and 128 characters."
return 1
fi
if [[ ! "$tag" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]]; then
echo "[ERROR] Invalid tag '$tag'. Allowed: [A-Za-z0-9_.-], must start with alphanumeric or underscore."
return 1
fi
}
# --- Preflight ---------------------------------------------------------------
if [[ ! -d ".git" ]]; then
echo "[ERROR] Not a git repository (.git missing)."
exit 1
fi
if [[ ! -d "$COMPOSE_DIR" ]]; then
echo "[ERROR] '$COMPOSE_DIR' directory missing. Expected ./compose/<service>/ with a Dockerfile."
exit 1
fi
check_docker_ready
ensure_registry_login
validate_repo_component "$DOCKER_NAMESPACE"
# Detect currently checked out branch (authoritative for this script)
DETECTED_BRANCH="$(git branch --show-current 2>/dev/null || true)"
if [[ -z "$DETECTED_BRANCH" ]]; then
DETECTED_BRANCH="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
fi
if [[ -z "$DETECTED_BRANCH" ]]; then
# Try to derive from upstream
UPSTREAM_REF_DERIVED="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || true)"
if [[ -n "$UPSTREAM_REF_DERIVED" ]]; then
DETECTED_BRANCH="${UPSTREAM_REF_DERIVED#origin/}"
fi
fi
if [[ -z "$DETECTED_BRANCH" ]]; then
DETECTED_BRANCH="main"
fi
# Optional signals: BRANCH env and .last-branch (informational only)
ENV_BRANCH="${BRANCH:-}"
LAST_BRANCH_FILE_PATH="$(pwd)/$LAST_BRANCH_FILE"
LAST_BRANCH_VALUE=""
if [[ -z "$ENV_BRANCH" && -f "$LAST_BRANCH_FILE_PATH" ]]; then
LAST_BRANCH_VALUE="$(tr -d ' \t\n\r' < "$LAST_BRANCH_FILE_PATH")"
fi
UPSTREAM_REF="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null || echo "origin/$DETECTED_BRANCH")"
HEAD_SHA="$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")"
echo "[INFO] Repo: $(pwd)"
echo "[INFO] Current branch: $DETECTED_BRANCH"
echo "[INFO] Upstream: $UPSTREAM_REF"
echo "[INFO] HEAD (sha): $HEAD_SHA"
if [[ -n "$ENV_BRANCH" && "$ENV_BRANCH" != "$DETECTED_BRANCH" ]]; then
echo "[WARNING] BRANCH='$ENV_BRANCH' differs from checked out branch '$DETECTED_BRANCH'."
echo "[WARNING] This script does not switch branches; continuing on '$DETECTED_BRANCH'."
fi
if [[ -n "$LAST_BRANCH_VALUE" && "$LAST_BRANCH_VALUE" != "$DETECTED_BRANCH" && -z "$ENV_BRANCH" ]]; then
echo "[INFO] .last-branch suggests '$LAST_BRANCH_VALUE', but current checkout is '$DETECTED_BRANCH'."
echo "[INFO] If you intended to build '$LAST_BRANCH_VALUE', switch branches first (use update-and-build.sh)."
fi
# --- Versioning --------------------------------------------------------------
CURRENT_VERSION="$(read_version)"
NEW_VERSION="$CURRENT_VERSION"
DO_TAG_AND_BUMP=true
if [[ "$BUMP" == "t" ]]; then
echo "[INFO] Test build: keeping version $CURRENT_VERSION; will only update :dev."
DO_TAG_AND_BUMP=false
else
NEW_VERSION="$(bump_version "$CURRENT_VERSION" "$BUMP")"
echo "[INFO] New version: $NEW_VERSION"
fi
if $DO_TAG_AND_BUMP; then
validate_tag "$NEW_VERSION"
fi
validate_tag "latest"
# --- Version update + VCS ops (release builds only) --------------------------
if $DO_TAG_AND_BUMP; then
echo "[INFO] Writing $NEW_VERSION to $VERSION_FILE"
write_version "$NEW_VERSION"
echo "[INFO] Git add + commit (branch: $DETECTED_BRANCH)"
git add "$VERSION_FILE"
git commit -m "Release $NEW_VERSION on branch $DETECTED_BRANCH (bump type $BUMP)"
echo "[INFO] Git tag $NEW_VERSION"
git tag -a "$NEW_VERSION" -m "Release $NEW_VERSION"
echo "[INFO] Git push + tags"
git push origin "$DETECTED_BRANCH"
git push --tags
else
echo "[INFO] Skipping commit/tagging (test build)."
fi
# --- Build & push per service ------------------------------------------------
shopt -s nullglob
services=( "$COMPOSE_DIR"/* )
if [[ ${#services[@]} -eq 0 ]]; then
echo "[ERROR] No services found under $COMPOSE_DIR"
exit 1
fi
BUILT_IMAGES=()
for svc_path in "${services[@]}"; do
[[ -d "$svc_path" ]] || continue
svc="$(basename "$svc_path")"
dockerfile="$svc_path/Dockerfile"
validate_repo_component "$svc"
if [[ ! -f "$dockerfile" ]]; then
echo "[WARNING] Skipping '${svc}': Dockerfile not found in ${svc_path}"
continue
fi
IMAGE_BASE="${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${svc}"
if $DO_TAG_AND_BUMP; then
echo "============================================================"
echo "[INFO] Building ${svc} -> tags: ${NEW_VERSION}, latest"
echo "============================================================"
docker build -t "${IMAGE_BASE}:${NEW_VERSION}" -t "${IMAGE_BASE}:dev" "$svc_path"
docker push "${IMAGE_BASE}:${NEW_VERSION}"
docker push "${IMAGE_BASE}:dev"
BUILT_IMAGES+=("${IMAGE_BASE}:${NEW_VERSION}" "${IMAGE_BASE}:dev")
else
echo "============================================================"
echo "[INFO] Test build ${svc} -> tag: latest"
echo "============================================================"
docker build -t "${IMAGE_BASE}:dev" "$svc_path"
docker push "${IMAGE_BASE}:dev"
BUILT_IMAGES+=("${IMAGE_BASE}:dev")
fi
done
# --- Persist current branch to .last-branch ----------------------------------
# (This helps script 1 to preselect next time, and is informative if you run script 2 standalone)
echo "$DETECTED_BRANCH" > "$LAST_BRANCH_FILE_PATH"
# --- Summary -----------------------------------------------------------------
echo ""
echo "============================================================"
echo "[SUMMARY] Build & push complete (branch: $DETECTED_BRANCH)"
if $DO_TAG_AND_BUMP; then
echo "[INFO] Release version: $NEW_VERSION"
else
echo "[INFO] Test build (no version bump)"
fi
echo "[INFO] Images pushed:"
for img in "${BUILT_IMAGES[@]}"; do
echo " - $img"
done
echo "============================================================"

View File

@ -1,232 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# ============================================================================
# update-and-build.sh
# Location: /docker/develop
#
# Purpose:
# - Laat je een branch kiezen (of via BRANCH= meegeven)
# - Onthoudt de laatst gebruikte branch in .last-branch in de repo root
# - Commit en pusht altijd lokale wijzigingen (AUTO_COMMIT standaard aan)
# - Haalt daarna updates op uit origin/<branch> (git pull --rebase)
# - Roept daarna het build-script in de repo aan (build-and-push.sh) met de
# gekozen bump type (1/2/3/t)
#
# Gebruik:
# BRANCH=<branch> sudo bash /docker/develop/update-and-build.sh [bump]
# (BRANCH optioneel; zonder krijg je een menu met keuze, of kun je
# handmatig een nieuwe branchnaam intypen)
# ============================================================================
REPO_DIR="/docker/develop/backup-v9" # pas aan indien nodig
BUILD_SCRIPT_NAME="build-and-push.sh"
LAST_BRANCH_FILE=".last-branch" # wordt in REPO_DIR opgeslagen
# Omgeving: optionele flags voor non-interactief gebruik
AUTO_COMMIT="${AUTO_COMMIT:-1}" # 1 = altijd committen als er wijzigingen zijn
AUTO_PUSH="${AUTO_PUSH:-1}" # 1 = push naar origin/<branch> na commit
AUTO_CREATE_BRANCH="${AUTO_CREATE_BRANCH:-1}" # 1 = nieuwe branch lokaal maken als hij nog niet bestaat
# Commit message (optioneel prefix via COMMIT_MSG_PREFIX)
DEFAULT_COMMIT_MSG_PREFIX="${COMMIT_MSG_PREFIX:-Auto-commit local changes before build}"
# --- Input: bump type --------------------------------------------------------
BUMP_TYPE="${1:-}"
if [[ -z "${BUMP_TYPE}" ]]; then
echo "Select bump type: [1] patch, [2] minor, [3] major, [t] test (default: t)"
read -r BUMP_TYPE
BUMP_TYPE="${BUMP_TYPE:-t}"
fi
if [[ "$BUMP_TYPE" != "1" && "$BUMP_TYPE" != "2" && "$BUMP_TYPE" != "3" && "$BUMP_TYPE" != "t" ]]; then
echo "[ERROR] Invalid bump type '$BUMP_TYPE' (use 1, 2, 3, or t)."
exit 1
fi
# --- Naar repo gaan ----------------------------------------------------------
echo "[INFO] Switching to repo: $REPO_DIR"
cd "$REPO_DIR"
# --- Fetch alle branches -----------------------------------------------------
echo "[INFO] Fetching all branches (origin)..."
git fetch --all --prune
# Lijst met origin branches (zonder origin/ prefix, HEAD gefilterd)
mapfile -t BRANCHES < <(
git for-each-ref \
--format='%(refname:short)' refs/remotes/origin \
| sed 's|^origin/||' \
| grep -vE '^HEAD$' \
| sort -u
)
# Fallback: als er geen origin branches zijn, kijk dan naar lokale branches
if [[ "${#BRANCHES[@]}" -eq 0 ]]; then
mapfile -t BRANCHES < <(
git for-each-ref \
--format='%(refname:short)' refs/heads \
| sort -u
)
fi
if [[ "${#BRANCHES[@]}" -eq 0 ]]; then
echo "[ERROR] Geen branches gevonden (noch lokaal, noch bij origin)."
exit 1
fi
# --- Bepaal default branch/last branch ---------------------------------------
ENV_BRANCH="${BRANCH:-}"
LAST_BRANCH=""
if [[ -f "$LAST_BRANCH_FILE" ]]; then
LAST_BRANCH="$(<"$LAST_BRANCH_FILE")"
fi
CURRENT_BRANCH="$(git branch --show-current 2>/dev/null || true)"
DEFAULT_BRANCH="$CURRENT_BRANCH"
if [[ -z "$DEFAULT_BRANCH" ]]; then
if [[ -n "$LAST_BRANCH" ]]; then
DEFAULT_BRANCH="$LAST_BRANCH"
else
DEFAULT_BRANCH="${BRANCHES[0]}"
fi
fi
CHOSEN_BRANCH=""
# 1) Als BRANCH= in de omgeving is gezet, gebruik die direct
if [[ -n "$ENV_BRANCH" ]]; then
CHOSEN_BRANCH="$ENV_BRANCH"
echo "[INFO] Branch via BRANCH env: $CHOSEN_BRANCH"
else
echo "[INFO] Beschikbare bestaande branches:"
i=1
DEFAULT_INDEX=1
for b in "${BRANCHES[@]}"; do
if [[ "$b" == "$DEFAULT_BRANCH" ]]; then
echo " [$i] $b (default)"
DEFAULT_INDEX="$i"
else
echo " [$i] $b"
fi
((i++))
done
echo
echo "Je kunt een nummer kiezen voor een bestaande branch,"
echo "of een nieuwe branch-naam intypen."
read -r -p "Selecteer nummer of typ nieuwe branch-naam (leeg = default): " choice
# Leeg = default branch
if [[ -z "$choice" ]]; then
CHOSEN_BRANCH="$DEFAULT_BRANCH"
echo "[INFO] Default branch geselecteerd: $CHOSEN_BRANCH"
# Als gebruiker een nummer kiest
elif [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#BRANCHES[@]} )); then
CHOSEN_BRANCH="${BRANCHES[choice-1]}"
echo "[INFO] Bestaande branch gekozen: $CHOSEN_BRANCH"
else
# Anders: dit is een nieuwe branch
CHOSEN_BRANCH="$choice"
echo "[INFO] Nieuwe branch gekozen: $CHOSEN_BRANCH (wordt aangemaakt als hij nog niet bestaat)"
fi
fi
# Schrijf gekozen branch weg als last-branch
echo "$CHOSEN_BRANCH" > "$LAST_BRANCH_FILE"
# --- Branch checkout / aanmaken ----------------------------------------------
REMOTE_REF_EXISTS=0
if git show-ref --verify --quiet "refs/remotes/origin/$CHOSEN_BRANCH"; then
REMOTE_REF_EXISTS=1
fi
LOCAL_REF_EXISTS=0
if git show-ref --verify --quiet "refs/heads/$CHOSEN_BRANCH"; then
LOCAL_REF_EXISTS=1
fi
if [[ "$LOCAL_REF_EXISTS" -eq 1 ]]; then
echo "[INFO] Checkout bestaande lokale branch: $CHOSEN_BRANCH"
git switch "$CHOSEN_BRANCH"
else
if [[ "$REMOTE_REF_EXISTS" -eq 1 ]]; then
echo "[INFO] Nieuwe lokale branch maken vanaf origin/$CHOSEN_BRANCH"
git switch -c "$CHOSEN_BRANCH" --track "origin/$CHOSEN_BRANCH"
else
if [[ "$AUTO_CREATE_BRANCH" -eq 1 ]]; then
echo "[INFO] Nieuwe lokale branch maken (zonder bestaande remote): $CHOSEN_BRANCH"
git switch -c "$CHOSEN_BRANCH"
else
echo "[ERROR] Branch '$CHOSEN_BRANCH' bestaat niet lokaal en niet bij origin."
exit 1
fi
fi
fi
# --- Functie: auto-commit lokale wijzigingen --------------------------------
auto_commit_if_needed() {
if [[ "$AUTO_COMMIT" -ne 1 ]]; then
return 0
fi
# Check of er iets te committen is
if [[ -z "$(git status --porcelain)" ]]; then
echo "[INFO] Geen lokale wijzigingen om te committen."
return 0
fi
echo "[INFO] Lokale wijzigingen gedetecteerd in branch '$CHOSEN_BRANCH':"
git status --short
echo "[INFO] Auto-commit lokale wijzigingen..."
git add -A
# Commit message opbouwen (met timestamp)
local ts
ts="$(date +'%Y-%m-%d %H:%M:%S')"
local msg="${DEFAULT_COMMIT_MSG_PREFIX} (${ts})"
git commit -m "$msg"
if [[ "$AUTO_PUSH" -eq 1 ]]; then
echo "[INFO] Pushing naar origin/$CHOSEN_BRANCH..."
git push -u origin "$CHOSEN_BRANCH"
else
echo "[INFO] AUTO_PUSH=0, push wordt overgeslagen."
fi
}
# --- Eerst altijd lokale wijzigingen committen (voor git pull) --------------
auto_commit_if_needed
# --- Daarna updaten vanaf origin (als remote bestaat) ------------------------
if git rev-parse --verify --quiet "origin/$CHOSEN_BRANCH" >/dev/null; then
echo "[INFO] Pulling latest changes from origin/$CHOSEN_BRANCH (rebase)..."
if ! git pull --rebase origin "$CHOSEN_BRANCH"; then
echo "[ERROR] git pull --rebase gaf een fout. Los eerst conflicten op en probeer opnieuw."
exit 1
fi
else
echo "[INFO] Geen origin/$CHOSEN_BRANCH gevonden; sla git pull over."
fi
# --- Na pull nogmaals checken of er iets open staat --------------------------
if [[ -n "$(git status --porcelain)" ]]; then
echo "[INFO] Er zijn na git pull nog niet-gecommit-te wijzigingen."
auto_commit_if_needed
fi
# --- Build script uitvoeren --------------------------------------------------
if [[ ! -f "./$BUILD_SCRIPT_NAME" ]]; then
echo "[ERROR] Build script ./$BUILD_SCRIPT_NAME niet gevonden in $REPO_DIR"
exit 1
fi
chmod +x "./$BUILD_SCRIPT_NAME"
echo "[INFO] Running build: ./$BUILD_SCRIPT_NAME $BUMP_TYPE"
"./$BUILD_SCRIPT_NAME" "$BUMP_TYPE"
echo "[INFO] Done."

View File

@ -1,40 +0,0 @@
# Repository Conventions
## 1. Naming
- Existing script filenames stay unchanged for documentation compatibility.
- New scripts should use clear, stable names and should not be renamed later without a migration plan.
## 2. Placement
- Place scripts by platform + domain:
- `scripts/m365/...`
- `scripts/windows/...`
- `scripts/macos/...`
- `scripts/content/...`
- Place build/deploy automation in `automation/...`.
## 3. Script Header (minimum)
Use these fields at the top of each script where possible:
- Purpose/Synopsis
- Requirements (modules, permissions, OS)
- Input parameters
- Output (file/console)
- Example usage
## 4. Secrets and Variables
- Do not hardcode secrets.
- Use variables, prompts, or a secure vault mechanism.
## 5. Output and Logging
- Default output goes to `output/` unless operational requirements dictate otherwise.
- Use predictable filenames for exports.
## 6. Change Policy
- For file moves: update README and relevant guides.
- For any future filename changes: add a temporary compatibility layer (wrapper/alias) and define a deprecation window.

View File

@ -1,45 +0,0 @@
# Runbooks Index
This overview helps update guides after the repository restructuring.
## Important
- Legacy script filenames were not changed.
- Only folder locations were updated.
## Script Locations
- `Get-WindowsAutoPilotInfo.ps1`
- New path: `scripts/windows/autopilot/Get-WindowsAutoPilotInfo.ps1`
- `Test-SMTPRelay-M365.ps1`
- New path: `scripts/m365/smtp/Test-SMTPRelay-M365.ps1`
- `Test-SMTPSend-Auth-M365.ps1`
- New path: `scripts/m365/smtp/Test-SMTPSend-Auth-M365.ps1`
- `Update rmm.ps1`
- New path: `scripts/windows/endpoint/Update rmm.ps1`
- `get-mailbox-with-permissions.ps1`
- New path: `scripts/m365/exchange/get-mailbox-with-permissions.ps1`
- `m365-users-mfa.ps1`
- New path: `scripts/m365/identity/m365-users-mfa.ps1`
- `Get-SharePointPermissions.ps1`
- New path: `scripts/m365/sharepoint/get-sharepoint-permissions.ps1`
- `schedule_reboot.ps1`
- New path: `scripts/windows/endpoint/schedule_reboot.ps1`
- `uninstall-MS-Visual-C++-2010.ps1`
- New path: `scripts/windows/endpoint/uninstall-MS-Visual-C++-2010.ps1`
- `uuid.ps1`
- New path: `scripts/windows/endpoint/uuid.ps1`
- `serialToAdminPassword.sh`
- New path: `scripts/macos/serialToAdminPassword.sh`
- `convert-md.bat`
- New path: `scripts/content/convert-md.bat`
- `build-and-push.sh`
- New path: `automation/docker/build-and-push.sh`
- `update-and-build.sh`
- New path: `automation/docker/update-and-build.sh`
## Guide Update Checklist
- Replace references to root-level paths with the new paths above.
- Check automated jobs/tasks for hardcoded script locations.
- Test at least one script per domain after path updates.

View File

View File

@ -1,65 +0,0 @@
@echo off
setlocal enabledelayedexpansion
:: ============================================================
:: EPUB Conversiescript - .md naar .epub met Pandoc
:: Laatste update: 2025-06-20
::
:: Changelog:
:: - Vervanging van verouderde 'wmic' door PowerShell Get-Date
:: - Fallback op datum van vandaag bij lege invoer
:: - Overstap van --epub-metadata XML naar --metadata vlaggen
:: - Vermeden gebruik van systeemvariabele %DATE%
:: - Invoervelden ontdaan van spaties en line breaks via PowerShell .Trim()
:: - Gebruik van !VAR! (delayed expansion) om correcte waardes te behouden
:: - PowerShell Console.Write gebruikt om onzichtbare tekens zoals '=' of '+' te voorkomen
:: - Alle metadata wordt correct opgenomen in EPUB (title, author, publisher, date)
:: - Pauzes verwijderd voor geautomatiseerd gebruik, metadata wordt alleen getoond
:: - Eindpauze toegevoegd zodat resultaat zichtbaar blijft
:: - Spaties toegevoegd bij invoer voor leesbaarheid
:: ============================================================
cd /d "C:\Users\ms-iv\Downloads"
:: Datum ophalen in jjjj-mm-dd formaat
powershell -NoProfile -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = 'en-US'; Get-Date -Format 'yyyy-MM-dd'" > temp_datum.txt
set /p TODAY=<temp_datum.txt
del temp_datum.txt
:: Metadata invoer
set /p TITLE=Voer de titel van het boek in:
set /p AUTHOR=Voer de auteur in:
set /p PUBLISHER=Voer de uitgever (publisher) in:
set /p BOOKDATEINPUT=Voer de publicatiedatum in (formaat: jjjj-mm-dd of leeg = vandaag):
:: Invoer normaliseren via PowerShell (verwijdert CR/LF/spaties)
for /f %%x in ('powershell -NoProfile -Command "[Console]::Write((\"%BOOKDATEINPUT%\").Trim())"') do set BOOKDATEINPUT=%%x
:: Boekdatum instellen (fallback op TODAY)
if not "!BOOKDATEINPUT!"=="" (
set "BOOKDATE=!BOOKDATEINPUT!"
) else (
set "BOOKDATE=!TODAY!"
)
:: Metadata tonen vóór conversie
echo.
echo Titel: !TITLE!
echo Auteur: !AUTHOR!
echo Uitgever: !PUBLISHER!
echo Publicatiedatum: !BOOKDATE!
echo.
:: Converteren van elk .md bestand
for %%f in (*.md) do (
echo Converting "%%f"...
pandoc "%%f" -o "%%~nf.epub" ^
--metadata=title:"!TITLE!" ^
--metadata=author:"!AUTHOR!" ^
--metadata=publisher:"!PUBLISHER!" ^
--metadata=date:"!BOOKDATE!"
)
echo.
echo Alle bestanden zijn geconverteerd.
pause

View File

@ -1,65 +0,0 @@
# Output file
$OutputFile = "MailboxPermissionsWithSMTPAndName.csv"
# Initialize an array for the results
$Results = @()
# Retrieve all mailboxes (all types)
$Mailboxes = Get-Mailbox -RecipientTypeDetails UserMailbox,SharedMailbox -ResultSize Unlimited
# Loop through all mailboxes
foreach ($Mailbox in $Mailboxes) {
# Retrieve the primary SMTP address and display name
$PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress
$DisplayName = $Mailbox.DisplayName
# Retrieve mailbox permissions
$Permissions = Get-MailboxPermission -Identity $Mailbox.Identity
# Check if specific permissions are set
if ($Permissions.Count -eq 0) {
# Add mailbox without additional permissions
$Results += [PSCustomObject]@{
DisplayName = $DisplayName
MailboxType = $Mailbox.RecipientTypeDetails
PrimarySmtpAddress = $PrimarySmtpAddress
User = "Default owner"
AccessRights = "FullAccess"
IsInherited = "N/A"
}
} else {
# Add permissions
foreach ($Permission in $Permissions) {
# Filter default permissions such as NT AUTHORITY\SELF
if ($Permission.User -notlike "NT AUTHORITY\SELF") {
$Results += [PSCustomObject]@{
DisplayName = $DisplayName
MailboxType = $Mailbox.RecipientTypeDetails
PrimarySmtpAddress = $PrimarySmtpAddress
User = $Permission.User
AccessRights = $Permission.AccessRights -join ", "
IsInherited = $Permission.IsInherited
}
}
}
}
}
# Add mailboxes that may not have displayed permissions
foreach ($Mailbox in $Mailboxes) {
if (-not ($Results.DisplayName -contains $Mailbox.DisplayName)) {
$Results += [PSCustomObject]@{
DisplayName = $Mailbox.DisplayName
MailboxType = $Mailbox.RecipientTypeDetails
PrimarySmtpAddress = $Mailbox.PrimarySmtpAddress
User = "Default owner"
AccessRights = "FullAccess"
IsInherited = "N/A"
}
}
}
# Export the results to a CSV file
$Results | Export-Csv -Path $OutputFile -NoTypeInformation -Encoding UTF8
Write-Host "All mailboxes, permissions, names, and SMTP addresses have been exported to $OutputFile"

View File

@ -1,41 +0,0 @@
# Install the MSOnline module if it is not already installed
Install-Module -Name MSOnline -Force
# Connect to the MSOnline service
Connect-MsolService
# Retrieve all users, suppressing any errors by redirecting to $null
$AllUsers = Get-MsolUser -All 2>$null
# Container for MFA information
$MFAInfo = @()
# Loop through all users to check their MFA status
foreach ($user in $AllUsers) {
# Check if MFA is enabled by looking at StrongAuthenticationRequirements
if ($user.StrongAuthenticationRequirements.State -ne $null) {
$MFAInfo += [pscustomobject]@{
UserPrincipalName = $user.UserPrincipalName # User's UPN (email address)
DisplayName = $user.DisplayName # User's display name
MFAEnabled = $true # MFA is enabled
Method = "Conditional Access/MFA Policy" # MFA policy used
}
} else {
# If no MFA policy is found, mark MFA as disabled
$MFAInfo += [pscustomobject]@{
UserPrincipalName = $user.UserPrincipalName
DisplayName = $user.DisplayName
MFAEnabled = $false # MFA is not enabled
Method = "None" # No MFA method applied
}
}
}
# Define the path for the CSV file
$CsvFilePath = "C:\MFA_Overview.csv"
# Export the MFA data to a CSV file without including type information
$MFAInfo | Export-Csv -Path $CsvFilePath -NoTypeInformation
# Display a message indicating that the export was successful
Write-Host "MFA overview successfully exported to $CsvFilePath"

View File

@ -1,549 +0,0 @@
#Requires -Modules Microsoft.Graph.Authentication, ImportExcel
<#
.SYNOPSIS
Audits unique SharePoint permissions at library, folder, and file level across all sites.
.DESCRIPTION
Reads site URLs from a CSV export (SharePoint Admin Center) and retrieves all
permissions via the Microsoft Graph API. Results are streamed directly to CSV
after each site to minimize memory usage. An Excel report is generated at the end.
=========================================================================
REQUIRED AZURE APP REGISTRATION PERMISSIONS
=========================================================================
Create an App Registration in Azure Portal (Entra ID) with the following
APPLICATION permissions (not Delegated) and grant Admin Consent for all:
Microsoft Graph:
- Sites.Read.All : Read all SharePoint site collections and their contents
- Files.Read.All : Read all files in all site document libraries
- User.Read.All : Resolve user IDs to display names / UPNs
- Group.Read.All : Resolve group IDs to display names
How to set up:
1. Go to Azure Portal > Microsoft Entra ID > App Registrations > New Registration
2. Give it a name (e.g. "SharePoint Permissions Audit")
3. Go to API Permissions > Add a Permission > Microsoft Graph > Application Permissions
4. Add: Sites.Read.All, Files.Read.All, User.Read.All, Group.Read.All
5. Click "Grant Admin Consent for <tenant>"
6. Go to Certificates & Secrets > New Client Secret
7. Copy the SECRET VALUE (not the Secret ID) immediately after creation
8. Note down the Application (client) ID and the Directory (tenant) ID
=========================================================================
.PARAMETER TenantId
Azure AD / Entra ID Tenant ID (found on the App Registration overview page).
.PARAMETER ClientId
Application (Client) ID of the App Registration.
.PARAMETER ClientSecret
Client Secret VALUE (not the Secret ID) of the App Registration.
.PARAMETER SitesCsvPath
Path to the CSV file exported from the SharePoint Admin Center.
The CSV must contain a column named 'URL' with the site URLs.
Export: SharePoint Admin Center > Active Sites > Export to CSV
.PARAMETER OutputPath
Folder where output files (CSV, Excel, log) will be saved. Defaults to current directory.
.PARAMETER ExcludedSites
Optional array of site URLs to skip during the scan.
.PARAMETER IncludeFileLevel
Switch to enable file-level permission scanning in addition to libraries and folders.
Warning: this significantly increases scan time on large tenants.
.EXAMPLE
# Scan all sites using the exported CSV
.\Get-SharePointPermissions.ps1 `
-TenantId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
-ClientId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
-ClientSecret "your-secret-value-here" `
-SitesCsvPath "C:\scripts\Sites.csv" `
-OutputPath "C:\scripts\Reports"
.EXAMPLE
# Include file-level permissions and skip a specific site
.\Get-SharePointPermissions.ps1 `
-TenantId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
-ClientId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
-ClientSecret "your-secret-value-here" `
-SitesCsvPath "C:\scripts\Sites.csv" `
-OutputPath "C:\scripts\Reports" `
-IncludeFileLevel `
-ExcludedSites @("https://contoso.sharepoint.com/sites/HR")
.NOTES
Monitor progress: Get-Content "C:\scripts\Reports\SharePoint_Permissions_*.csv" -Wait -Tail 20
Do NOT open the CSV in Excel while the script is running - this locks the file.
Use Notepad or Notepad++ to view the CSV during the scan.
#>
param(
[Parameter(Mandatory)]
[string]$TenantId,
[Parameter(Mandatory)]
[string]$ClientId,
[Parameter(Mandatory)]
[string]$ClientSecret,
[Parameter(Mandatory)]
[string]$SitesCsvPath,
[string]$OutputPath = (Get-Location).Path,
[string[]]$ExcludedSites = @(),
[switch]$IncludeFileLevel
)
$ErrorActionPreference = 'Stop'
#region ── Module check ──────────────────────────────────────────────────────
# Ensure required PowerShell modules are installed and loaded.
# Microsoft.Graph.Authentication : Connect to Graph API and make REST calls
# ImportExcel : Generate formatted Excel reports without Excel installed
foreach ($mod in @('Microsoft.Graph.Authentication', 'ImportExcel')) {
if (-not (Get-Module -ListAvailable -Name $mod)) {
Write-Host "[SETUP] Installing module '$mod'..." -ForegroundColor Yellow
Install-Module $mod -Scope CurrentUser -Force -AllowClobber
}
Import-Module $mod -ErrorAction Stop
}
#endregion
#region ── Output file paths ─────────────────────────────────────────────────
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$csvOut = Join-Path $OutputPath "SharePoint_Permissions_$timestamp.csv"
$xlsxOut = Join-Path $OutputPath "SharePoint_Permissions_$timestamp.xlsx"
$logOut = Join-Path $OutputPath "SharePoint_Permissions_$timestamp.log"
if (-not (Test-Path $OutputPath)) {
New-Item -ItemType Directory -Path $OutputPath | Out-Null
}
#endregion
#region ── Logging ───────────────────────────────────────────────────────────
function Write-Log {
param([string]$Message, [string]$Level = 'INFO')
$entry = "[{0}] [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
Add-Content -Path $logOut -Value $entry
switch ($Level) {
'ERROR' { Write-Host $entry -ForegroundColor Red }
'WARNING' { Write-Host $entry -ForegroundColor Yellow }
'SUCCESS' { Write-Host $entry -ForegroundColor Green }
default { Write-Host $entry }
}
}
#endregion
#region ── Authentication ────────────────────────────────────────────────────
# Uses the OAuth2 client credentials flow directly via Invoke-RestMethod.
# This avoids MSAL.PS compatibility issues with PowerShell strict mode.
Write-Log "Script started."
try {
$tokenBody = @{
grant_type = 'client_credentials'
client_id = $ClientId
client_secret = $ClientSecret
scope = 'https://graph.microsoft.com/.default'
}
$tokenResponse = Invoke-RestMethod `
-Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" `
-Method POST -Body $tokenBody -ContentType 'application/x-www-form-urlencoded'
Connect-MgGraph `
-AccessToken ($tokenResponse.access_token | ConvertTo-SecureString -AsPlainText -Force) `
-NoWelcome
Write-Log "Authentication successful." -Level 'SUCCESS'
} catch {
Write-Log "Authentication failed: $_" -Level 'ERROR'
exit 1
}
#endregion
#region ── Graph REST helper with automatic pagination ───────────────────────
# Graph API returns results in pages (default 200 items per page).
# This function follows @odata.nextLink until all results are retrieved.
function Invoke-GraphGet {
param([string]$Uri)
$results = [System.Collections.Generic.List[object]]::new()
$nextUri = $Uri
while ($nextUri) {
$resp = Invoke-MgGraphRequest -Uri $nextUri -Method GET -OutputType PSObject
$nextUri = $null
if ($resp.PSObject.Properties.Name -contains '@odata.nextLink') {
$nextUri = $resp.'@odata.nextLink'
}
if ($resp.PSObject.Properties.Name -contains 'value') {
foreach ($item in $resp.value) { $results.Add($item) }
} elseif ($null -ne $resp) {
$results.Add($resp)
}
}
return $results
}
#endregion
#region ── Recursive drive item retrieval ────────────────────────────────────
# Recursively fetches all folders (and optionally files) within a drive.
# Recurses into subfolders to build a complete tree.
function Get-AllDriveItems {
param([string]$DriveId, [string]$ItemId = 'root')
$all = [System.Collections.Generic.List[object]]::new()
try {
$children = Invoke-GraphGet -Uri "https://graph.microsoft.com/v1.0/drives/$DriveId/items/$ItemId/children"
foreach ($child in $children) {
$all.Add($child)
if ($child.PSObject.Properties.Name -contains 'folder') {
$sub = Get-AllDriveItems -DriveId $DriveId -ItemId $child.id
foreach ($s in $sub) { $all.Add($s) }
}
}
} catch {
Write-Log " Error retrieving children of '$ItemId': $_" -Level 'WARNING'
}
return $all
}
#endregion
#region ── Principal name lookup with caching ────────────────────────────────
# Resolves Azure AD user/group IDs to readable display names.
# Results are cached to avoid duplicate API calls for the same principal.
$principalCache = @{}
function Get-PrincipalName {
param([string]$Id, [string]$Type)
if (-not $Id) { return 'Unknown' }
$key = "$Type|$Id"
if ($principalCache.ContainsKey($key)) { return $principalCache[$key] }
$name = "$Type`: $Id"
try {
if ($Type -eq 'user') {
$u = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/users/${Id}?`$select=displayName,userPrincipalName" -Method GET -OutputType PSObject
$name = "$($u.displayName) ($($u.userPrincipalName))"
} elseif ($Type -eq 'group') {
$g = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/groups/${Id}?`$select=displayName" -Method GET -OutputType PSObject
$name = $g.displayName
}
} catch { }
$principalCache[$key] = $name
return $name
}
#endregion
#region ── Permission record builder ─────────────────────────────────────────
# Retrieves permissions for a single drive item and returns structured records.
# ForceInclude: always return records even if only inherited permissions exist (used for library root).
# Without ForceInclude: only returns records if explicit (non-inherited) grants are found.
function Get-ItemPermissionRecords {
param(
[string]$DriveId,
[string]$ItemId,
[string]$ItemName,
[string]$ItemPath,
[string]$ItemType, # 'Library', 'Folder', or 'File'
[string]$SiteName,
[string]$SiteUrl,
[string]$LibraryName,
[switch]$ForceInclude
)
$records = [System.Collections.Generic.List[object]]::new()
try {
$perms = Invoke-GraphGet -Uri "https://graph.microsoft.com/v1.0/drives/$DriveId/items/$ItemId/permissions"
} catch {
Write-Log " Error retrieving permissions for '$ItemPath': $_" -Level 'WARNING'
return $records
}
if (-not $perms -or $perms.Count -eq 0) { return $records }
# Only include items with explicit direct grants (not just sharing links), unless ForceInclude
$hasExplicitGrants = $perms | Where-Object {
($_.PSObject.Properties.Name -contains 'grantedToV2' -and $_.grantedToV2) -or
($_.PSObject.Properties.Name -contains 'grantedTo' -and $_.grantedTo)
}
if (-not $ForceInclude -and -not $hasExplicitGrants) { return $records }
foreach ($perm in $perms) {
$roles = if ($perm.PSObject.Properties.Name -contains 'roles' -and $perm.roles) { $perm.roles -join ', ' } else { 'read' }
$grantedTo = 'Unknown'
$linkType = ''
# Resolve who the permission is granted to (Graph API v2 properties take priority)
if ($perm.PSObject.Properties.Name -contains 'grantedToV2' -and $perm.grantedToV2) {
$g = $perm.grantedToV2
if ($g.PSObject.Properties.Name -contains 'user' -and $g.user) { $grantedTo = Get-PrincipalName -Id $g.user.id -Type 'user' }
elseif ($g.PSObject.Properties.Name -contains 'group' -and $g.group) { $grantedTo = Get-PrincipalName -Id $g.group.id -Type 'group' }
elseif ($g.PSObject.Properties.Name -contains 'siteUser' -and $g.siteUser) { $grantedTo = $g.siteUser.displayName }
elseif ($g.PSObject.Properties.Name -contains 'siteGroup' -and $g.siteGroup) { $grantedTo = $g.siteGroup.displayName }
} elseif ($perm.PSObject.Properties.Name -contains 'grantedToIdentitiesV2' -and $perm.grantedToIdentitiesV2) {
# Multiple recipients (e.g. sharing link used by multiple people)
$names = foreach ($identity in $perm.grantedToIdentitiesV2) {
if ($identity.PSObject.Properties.Name -contains 'user' -and $identity.user) { Get-PrincipalName -Id $identity.user.id -Type 'user' }
elseif ($identity.PSObject.Properties.Name -contains 'group' -and $identity.group) { Get-PrincipalName -Id $identity.group.id -Type 'group' }
elseif ($identity.PSObject.Properties.Name -contains 'siteUser' -and $identity.siteUser) { $identity.siteUser.displayName }
else { 'Unknown' }
}
$grantedTo = ($names | Where-Object { $_ }) -join '; '
} elseif ($perm.PSObject.Properties.Name -contains 'grantedTo' -and $perm.grantedTo) {
# Fallback: older grantedTo property
$g = $perm.grantedTo
if ($g.PSObject.Properties.Name -contains 'user' -and $g.user) { $grantedTo = "$($g.user.displayName) ($($g.user.email))" }
elseif ($g.PSObject.Properties.Name -contains 'group' -and $g.group) { $grantedTo = $g.group.displayName }
}
# Detect sharing links (anonymous, organization-wide, or specific people links)
if ($perm.PSObject.Properties.Name -contains 'link' -and $perm.link) {
$linkType = "SharingLink ($($perm.link.type), $($perm.link.scope))"
if ($grantedTo -eq 'Unknown') { $grantedTo = $linkType }
}
$records.Add([PSCustomObject]@{
SiteName = $SiteName
SiteUrl = $SiteUrl
Library = $LibraryName
ItemType = $ItemType
ItemName = $ItemName
ItemPath = $ItemPath
GrantedTo = $grantedTo
Permissions = $roles
LinkType = $linkType
ScannedAt = (Get-Date -Format 'yyyy-MM-dd HH:mm')
})
}
return $records
}
#endregion
#region ── Load site list from CSV ───────────────────────────────────────────
# Reads site URLs from the SharePoint Admin Center export CSV.
# Export location: SharePoint Admin Center > Active Sites > Export
Write-Log "Loading sites from CSV: $SitesCsvPath"
if (-not (Test-Path $SitesCsvPath)) {
Write-Log "CSV file not found: $SitesCsvPath" -Level 'ERROR'
exit 1
}
$csvSites = Import-Csv -Path $SitesCsvPath
$urlColumn = $csvSites[0].PSObject.Properties.Name | Where-Object { $_ -match '^url$' } | Select-Object -First 1
if (-not $urlColumn) {
Write-Log "No 'URL' column found in CSV. Available columns: $($csvSites[0].PSObject.Properties.Name -join ', ')" -Level 'ERROR'
exit 1
}
$siteUrls = $csvSites.$urlColumn | Where-Object {
$_ -and $_ -notmatch '/personal/' -and $_ -notin $ExcludedSites
}
Write-Log "Sites loaded from CSV: $($siteUrls.Count)" -Level 'SUCCESS'
#endregion
#region ── Main scan loop ────────────────────────────────────────────────────
# Streams permission records directly to CSV after each site to keep memory usage low.
# The CSV file is written incrementally - do NOT open it in Excel during the scan.
$csvHeaders = 'SiteName;SiteUrl;Library;ItemType;ItemName;ItemPath;GrantedTo;Permissions;LinkType;ScannedAt'
Set-Content -Path $csvOut -Value $csvHeaders -Encoding UTF8
$recordCount = 0
function Write-RecordToCsv {
# Appends one or more permission records to the CSV file immediately.
param([object[]]$Records)
foreach ($rec in $Records) {
$line = '"{0}";"{1}";"{2}";"{3}";"{4}";"{5}";"{6}";"{7}";"{8}";"{9}"' -f `
$rec.SiteName, $rec.SiteUrl, $rec.Library, $rec.ItemType, `
$rec.ItemName, $rec.ItemPath, $rec.GrantedTo, $rec.Permissions, `
$rec.LinkType, $rec.ScannedAt
Add-Content -Path $csvOut -Value $line -Encoding UTF8
$script:recordCount++
}
}
$siteIndex = 0
$total = $siteUrls.Count
foreach ($siteUrl in $siteUrls) {
$siteIndex++
$pct = [math]::Round(($siteIndex / $total) * 100)
Write-Progress -Activity "SharePoint Permissions Audit" `
-Status "[$siteIndex/$total] $siteUrl | Total records: $recordCount" `
-PercentComplete $pct
# Resolve site URL to Graph site ID using the hostname:/path format
try {
$uri = [Uri]$siteUrl
$hostPart = $uri.Host
$pathPart = $uri.AbsolutePath.TrimStart('/')
if ($pathPart) {
$siteObj = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/${hostPart}:/${pathPart}" -Method GET -OutputType PSObject
} else {
$siteObj = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/sites/${hostPart}" -Method GET -OutputType PSObject
}
$siteId = $siteObj.id
$siteName = if ($siteObj.PSObject.Properties.Name -contains 'displayName' -and $siteObj.displayName) { $siteObj.displayName } else { $siteObj.name }
} catch {
Write-Log "[$siteIndex/$total] Failed to resolve site ID for '$siteUrl': $_" -Level 'WARNING'
continue
}
Write-Log "[$siteIndex/$total] $siteName"
# Get all document libraries for this site (excludes system lists)
try {
$drives = Invoke-GraphGet -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/drives"
$drives = $drives | Where-Object { $_.driveType -eq 'documentLibrary' }
} catch {
Write-Log " Error retrieving document libraries: $_" -Level 'WARNING'
continue
}
$recordsBefore = $recordCount
foreach ($drive in $drives) {
$libName = $drive.name
$driveId = $drive.id
Write-Log " Library: $libName"
# Scan library root permissions (always included)
try {
$root = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/drives/$driveId/root" -Method GET -OutputType PSObject
$records = Get-ItemPermissionRecords -DriveId $driveId -ItemId $root.id `
-ItemName $libName -ItemPath "/" -ItemType 'Library' `
-SiteName $siteName -SiteUrl $siteUrl -LibraryName $libName -ForceInclude
Write-RecordToCsv -Records $records
} catch {
Write-Log " Error scanning library root '$libName': $_" -Level 'WARNING'
}
# Scan all folders (and files if -IncludeFileLevel is set)
# Only items with unique (broken) permissions are included
try {
$items = Get-AllDriveItems -DriveId $driveId
foreach ($item in $items) {
$isFolder = $item.PSObject.Properties.Name -contains 'folder'
$isFile = $item.PSObject.Properties.Name -contains 'file'
if (-not $isFolder -and -not ($IncludeFileLevel -and $isFile)) { continue }
$itemType = if ($isFolder) { 'Folder' } else { 'File' }
$parentPath = ''
if ($item.PSObject.Properties.Name -contains 'parentReference' -and
$item.parentReference.PSObject.Properties.Name -contains 'path') {
$parentPath = $item.parentReference.path -replace '.*?/root:', ''
}
$cleanPath = "$parentPath/$($item.name)"
$records = Get-ItemPermissionRecords -DriveId $driveId -ItemId $item.id `
-ItemName $item.name -ItemPath $cleanPath -ItemType $itemType `
-SiteName $siteName -SiteUrl $siteUrl -LibraryName $libName
Write-RecordToCsv -Records $records
}
} catch {
Write-Log " Error scanning items in '$libName': $_" -Level 'WARNING'
}
}
$added = $recordCount - $recordsBefore
if ($added -gt 0) {
Write-Log " -> $added new records written (total: $recordCount)" -Level 'SUCCESS'
}
}
Write-Progress -Activity "SharePoint Permissions Audit" -Completed
Write-Log "Scan complete. $recordCount permission records found." -Level 'SUCCESS'
#endregion
#region ── Build Excel report from CSV ───────────────────────────────────────
# Reads the completed CSV and generates a multi-sheet Excel report.
# Sheet 1 - All Permissions : full dataset
# Sheet 2 - Summary by Site : record counts grouped per site
# Sheet 3 - Top 20 Risk Items: items with the most unique permission grants
# Sheet 4 - Sharing Links : all items shared via external/anonymous links
Write-Log "Building Excel report from CSV..."
if ($recordCount -gt 0) {
$allRecords = Import-Csv -Path $csvOut -Delimiter ';' -Encoding UTF8
# Sheet 1: Full permission list
$allRecords | Export-Excel -Path $xlsxOut -WorksheetName 'All Permissions' `
-TableName 'TblAllPermissions' -TableStyle Medium9 -AutoSize -FreezeTopRow -BoldTopRow
# Sheet 2: Summary grouped by site
$allRecords | Group-Object SiteName | Select-Object `
@{N='Site'; E={$_.Name}},
@{N='Total'; E={$_.Count}},
@{N='Libraries'; E={($_.Group.Library | Sort-Object -Unique).Count}},
@{N='Folders'; E={($_.Group | Where-Object ItemType -eq 'Folder').Count}},
@{N='Files'; E={($_.Group | Where-Object ItemType -eq 'File').Count}} |
Sort-Object Total -Descending |
Export-Excel -Path $xlsxOut -WorksheetName 'Summary by Site' `
-TableName 'TblSummary' -TableStyle Medium2 -AutoSize -FreezeTopRow -BoldTopRow -Append
# Sheet 3: Top 20 items with most unique grants (highest permission fragmentation risk)
$allRecords | Group-Object ItemPath | Select-Object `
@{N='Site'; E={($_.Group | Select-Object -First 1).SiteName}},
@{N='Library'; E={($_.Group | Select-Object -First 1).Library}},
@{N='Type'; E={($_.Group | Select-Object -First 1).ItemType}},
@{N='Item'; E={($_.Group | Select-Object -First 1).ItemName}},
@{N='Path'; E={($_.Group | Select-Object -First 1).ItemPath}},
@{N='Unique Grants'; E={$_.Count}} |
Sort-Object 'Unique Grants' -Descending | Select-Object -First 20 |
Export-Excel -Path $xlsxOut -WorksheetName 'Top 20 Risk Items' `
-TableName 'TblTopRisk' -TableStyle Medium6 -AutoSize -FreezeTopRow -BoldTopRow -Append
# Sheet 4: Sharing links (potential external exposure)
$links = @($allRecords | Where-Object { $_.LinkType -ne '' })
if ($links.Count -gt 0) {
$links | Export-Excel -Path $xlsxOut -WorksheetName 'Sharing Links' `
-TableName 'TblSharingLinks' -TableStyle Medium3 -AutoSize -FreezeTopRow -BoldTopRow -Append
}
Write-Log "Excel report saved: $xlsxOut" -Level 'SUCCESS'
} else {
Write-Log "No records found - Excel report not created." -Level 'WARNING'
}
#endregion
Disconnect-MgGraph | Out-Null
Write-Host "`n========================================" -ForegroundColor Cyan
Write-Host " Scan complete!" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host " Records : $recordCount"
Write-Host " CSV : $csvOut"
Write-Host " Excel : $xlsxOut"
Write-Host " Log : $logOut"
Write-Host "========================================`n" -ForegroundColor Cyan

View File

@ -1,60 +0,0 @@
<#
.SYNOPSIS
Test unauthenticated SMTP relay via Microsoft 365 using MX record and IP-based connector.
.DESCRIPTION
This script sends a test email through Microsoft 365 (Exchange Online) using a connector
that allows mail relay based on the sender's IP address. It does not require authentication
and is useful for testing printers, scanners, or any other device that cannot use modern SMTP auth.
.AUTHOR
Generic / Public version (sanitized for open-source use)
.NOTES
Requires that your public IP is allowed in an Exchange Online connector.
Use the MX hostname (e.g. yourdomain-nl.mail.protection.outlook.com) as the SMTP endpoint.
# CHANGELOG
--------------------------------------------------------------------------------
Date | Version | Author | Description
------------|---------|----------------|------------------------------------------
2025-07-18 | 1.0.0 | Public / GPT | Initial release for generic SMTP relay test
--------------------------------------------------------------------------------
#>
# Variables (edit to match your domain and connector setup)
$from = "relay@yourdomain.com"
$to = "relay@yourdomain.com"
$smtpServer = "yourdomain-nl.mail.protection.outlook.com" # Replace with your actual MX record
$smtpPort = 25
$subject = "SMTP Relay Test via Microsoft 365 Connector"
$body = "This message was sent without authentication using an IP-allowed connector."
# Create email message
$message = New-Object system.net.mail.mailmessage
$message.From = $from
$message.To.Add($to)
$message.Subject = $subject
$message.Body = $body
$message.IsBodyHtml = $false
# Configure SMTP client (unauthenticated, no TLS)
$smtp = New-Object Net.Mail.SmtpClient($smtpServer, $smtpPort)
$smtp.EnableSsl = $false
$smtp.DeliveryMethod = "Network"
$smtp.UseDefaultCredentials = $true
Write-Host "⏳ Attempting SMTP relay via $smtpServer..."
try {
$smtp.Send($message)
Write-Host "`n✅ Message successfully sent via connector."
}
catch {
Write-Host "`n❌ Failed to send mail:"
Write-Host $_.Exception.Message
if ($_.Exception.InnerException) {
Write-Host "`nDetails:"
Write-Host $_.Exception.InnerException.Message
}
}

View File

@ -1,64 +0,0 @@
<#
.SYNOPSIS
Test script to verify SMTP (Microsoft 365) connectivity using an app password.
.DESCRIPTION
This script attempts to send a test message using Authenticated SMTP with Microsoft 365.
Useful for diagnosing SMTP authentication issues with accounts that have MFA enabled.
.AUTHOR
Generic / Public version (original author removed for portability)
.NOTES
This script is intended for test purposes and should not be used in production automation.
# CHANGELOG
--------------------------------------------------------------------------------
Date | Version | Author | Description
------------|---------|---------------|------------------------------------------
2025-07-18 | 1.0.0 | Public/GPT | Initial version - generic SMTP test script
--------------------------------------------------------------------------------
#>
Clear-Host
# SMTP settings
$smtpServer = "smtp.office365.com"
$smtpPort = 587
$from = "youruser@yourdomain.com"
$to = "youruser@yourdomain.com"
$subject = "SMTP Test"
$body = "This is a test message sent via smtp.office365.com with an app password."
# Secure password prompt
$securePassword = Read-Host "Enter your app password" -AsSecureString
$credential = New-Object System.Management.Automation.PSCredential($from, $securePassword)
# Create email
$mail = New-Object system.net.mail.mailmessage
$mail.From = $from
$mail.To.Add($to)
$mail.Subject = $subject
$mail.Body = $body
$mail.IsBodyHtml = $false
# Create SMTP client
$smtp = New-Object Net.Mail.SmtpClient($smtpServer, $smtpPort)
$smtp.EnableSsl = $true
$smtp.Credentials = $credential
$smtp.Timeout = 10000
Write-Host "⏳ Sending mail via ${smtpServer}:${smtpPort} as ${from}..."
try {
$smtp.Send($mail)
Write-Host "`n✅ Message sent successfully to $to"
}
catch {
Write-Host "`n❌ Failed to send message:"
Write-Host $_.Exception.Message
if ($_.Exception.InnerException) {
Write-Host "`nDetails:"
Write-Host $_.Exception.InnerException.Message
}
}

View File

@ -1,31 +0,0 @@
#!/usr/bin/env bash
#set -x
############################################################################################
##
## Script to generate Admin Password from Serial Number
##
###########################################
## Copyright (c) 2020 Microsoft Corp. All rights reserved.
## Scripts are not supported under any Microsoft standard support program or service. The scripts are provided AS IS without warranty of any kind.
## Microsoft disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a
## particular purpose. The entire risk arising out of the use or performance of the scripts and documentation remains with you. In no event shall
## Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever
## (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary
## loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility
## of such damages.
## Feedback: neiljohn@microsoft.com
##
## Notes
##
## This script is to support createAdminAccount.sh the cipher used in that script must match the cipher used here to generate the correct password
## i.e. ABCDEF000009 becomes S0xNTk9QNDQ0NDQzCg==
##
## WARNING: It is strongly recommended to change the cipher on line 45 before deploying into production
echo -ne "Enter device serial number :"
read serial
password=`echo $serial | tr '[A-Z]' '[K-ZA-J]' | tr 0-9 4-90-3 | base64`
echo "Password: $password"

View File

@ -1,190 +0,0 @@
<#PSScriptInfo
.VERSION 1.3
.GUID ebf446a3-3362-4774-83c0-b7299410b63f
.AUTHOR Michael Niehaus
.COMPANYNAME Microsoft
.COPYRIGHT
.TAGS Windows AutoPilot
.LICENSEURI
.PROJECTURI
.ICONURI
.EXTERNALMODULEDEPENDENCIES
.REQUIREDSCRIPTS
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
Version 1.0: Original published version.
Version 1.1: Added -Append switch.
Version 1.2: Added -Credential switch.
Version 1.3: Added -Partner switch.
#>
<#
.SYNOPSIS
Retrieves the Windows AutoPilot deployment details from one or more computers
.DESCRIPTION
This script uses WMI to retrieve properties needed by the Microsoft Store for Business to support Windows AutoPilot deployment.
.PARAMETER Name
The names of the computers. These can be provided via the pipeline (property name Name or one of the available aliases, DNSHostName, ComputerName, and Computer).
.PARAMETER OutputFile
The name of the CSV file to be created with the details for the computers. If not specified, the details will be returned to the PowerShell
pipeline.
.PARAMETER Append
Switch to specify that new computer details should be appended to the specified output file, instead of overwriting the existing file.
.PARAMETER Credential
Credentials that should be used when connecting to a remote computer (not supported when gathering details from the local computer).
.PARAMETER Partner
Switch to specify that the created CSV file should use the schema for Partner Center (using serial number, make, and model).
.EXAMPLE
.\Get-WindowsAutoPilotInfo.ps1 -ComputerName MYCOMPUTER -OutputFile .\MyComputer.csv
.EXAMPLE
.\Get-WindowsAutoPilotInfo.ps1 -ComputerName MYCOMPUTER -OutputFile .\MyComputer.csv -Append
.EXAMPLE
.\Get-WindowsAutoPilotInfo.ps1 -ComputerName MYCOMPUTER1,MYCOMPUTER2 -OutputFile .\MyComputers.csv
.EXAMPLE
Get-ADComputer -Filter * | .\GetWindowsAutoPilotInfo.ps1 -OutputFile .\MyComputers.csv
.EXAMPLE
Get-CMCollectionMember -CollectionName "All Systems" | .\GetWindowsAutoPilotInfo.ps1 -OutputFile .\MyComputers.csv
.EXAMPLE
.\Get-WindowsAutoPilotInfo.ps1 -ComputerName MYCOMPUTER1,MYCOMPUTER2 -OutputFile .\MyComputers.csv -Partner
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$False,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True,Position=0)][alias("DNSHostName","ComputerName","Computer")] [String[]] $Name = @($env:ComputerName),
[Parameter(Mandatory=$False)] [String] $OutputFile = "",
[Parameter(Mandatory=$False)] [Switch] $Append = $false,
[Parameter(Mandatory=$False)] [System.Management.Automation.PSCredential] $Credential = $null,
[Parameter(Mandatory=$False)] [Switch] $Partner = $false,
[Parameter(Mandatory=$False)] [Switch] $Force = $false
)
Begin
{
# Initialize empty list
$computers = @()
}
Process
{
foreach ($comp in $Name)
{
$bad = $false
# Get the common properties.
Write-Verbose "Checking $comp"
$serial = (Get-WmiObject -ComputerName $comp -Credential $Credential -Class Win32_BIOS).SerialNumber
# Get the hash (if available)
$devDetail = (Get-WMIObject -ComputerName $comp -Credential $Credential -Namespace root/cimv2/mdm/dmmap -Class MDM_DevDetail_Ext01 -Filter "InstanceID='Ext' AND ParentID='./DevDetail'")
if ($devDetail -and (-not $Force))
{
$hash = $devDetail.DeviceHardwareData
}
else
{
$bad = $true
$hash = ""
}
# If the hash isn't available, get the make and model
if ($bad -or $Force)
{
$cs = Get-WmiObject -ComputerName $comp -Credential $Credential -Class Win32_ComputerSystem
$make = $cs.Manufacturer.Trim()
$model = $cs.Model.Trim()
if ($Partner)
{
$bad = $false
}
}
else
{
$make = ""
$model = ""
}
# Getting the PKID is generally problematic for anyone other than OEMs, so let's skip it here
$product = ""
# Depending on the format requested, create the necessary object
if ($Partner)
{
# Create a pipeline object
$c = New-Object psobject -Property @{
"Device Serial Number" = $serial
"Windows Product ID" = $product
"Hardware Hash" = $hash
"Manufacturer name" = $make
"Device model" = $model
}
# From spec:
# "Manufacturer Name" = $make
# "Device Name" = $model
}
else
{
# Create a pipeline object
$c = New-Object psobject -Property @{
"Device Serial Number" = $serial
"Windows Product ID" = $product
"Hardware Hash" = $hash
}
}
# Write the object to the pipeline or array
if ($bad)
{
# Report an error when the hash isn't available
Write-Error -Message "Unable to retrieve device hardware data (hash) from computer $comp" -Category DeviceError
}
elseif ($OutputFile -eq "")
{
$c
}
else
{
$computers += $c
}
}
}
End
{
if ($OutputFile -ne "")
{
if ($Append)
{
if (Test-Path $OutputFile)
{
$computers += Import-CSV -Path $OutputFile
}
}
if ($Partner)
{
$computers | Select "Device Serial Number", "Windows Product ID", "Hardware Hash", "Manufacturer name", "Device model" | ConvertTo-CSV -NoTypeInformation | % {$_ -replace '"',''} | Out-File $OutputFile
# From spec:
# $computers | Select "Device Serial Number", "Windows Product ID", "Hardware Hash", "Manufacturer Name", "Device Name" | ConvertTo-CSV -NoTypeInformation | % {$_ -replace '"',''} | Out-File $OutputFile
}
else
{
$computers | Select "Device Serial Number", "Windows Product ID", "Hardware Hash" | ConvertTo-CSV -NoTypeInformation | % {$_ -replace '"',''} | Out-File $OutputFile
}
}
}

View File

@ -1,45 +0,0 @@
# URL to the application's installer
$downloadUrl = "https://pinotage.centrastage.net/csm/profile/downloadAgent/key-site"
# Path where the installer will be saved
$downloadPath = "C:\Windows\Temp\agentsetup.exe"
# Path to the uninstaller of the current application
$uninstallPath = "C:\Program Files (x86)\CentraStage\uninst.exe"
# Optional arguments for the uninstallation
$uninstallArgs = "/uninstall /quiet"
# Optional arguments for the installation
$installArgs = "/silent"
# Download the installer
Write-Output "Downloading the installer..."
Invoke-WebRequest -Uri $downloadUrl -OutFile $downloadPath
Write-Output "The installer has been downloaded to $downloadPath."
# Start the uninstallation
$uninstallProcess = Start-Process -FilePath $uninstallPath -ArgumentList $uninstallArgs -PassThru
# Wait until the uninstallation is completed
Write-Output "Waiting for the uninstallation to complete..."
Wait-Process -Id $uninstallProcess.Id
Write-Output "The uninstallation is complete."
# Start the installation
$installProcess = Start-Process -FilePath $downloadPath -ArgumentList $installArgs -PassThru
# Wait until the installation is completed
Write-Output "Waiting for the installation to complete..."
Wait-Process -Id $installProcess.Id
Write-Output "The installation is complete."
# Remove the downloaded installer
Write-Output "Removing the downloaded installer..."
Remove-Item -Path $downloadPath -Force
Write-Output "The downloaded installer has been removed."
Write-Output "De installatie is voltooid."

View File

@ -1,35 +0,0 @@
# Define the reboot time
$rebootTime = "21:00"
# Get the current date and time
$currentDate = Get-Date
# Parse the reboot time into a DateTime object for today
$rebootDateTime = [datetime]::ParseExact("$($currentDate.ToString('yyyy-MM-dd')) $rebootTime", "yyyy-MM-dd HH:mm", $null)
# Check if the reboot time is in the past for today; if so, schedule it for tomorrow
if ($rebootDateTime -lt $currentDate) {
$rebootDateTime = $rebootDateTime.AddDays(1)
}
# Format the DateTime object for the task scheduler
$taskTime = $rebootDateTime.ToString("HH:mm")
# Create a scheduled task action for a forced reboot
$taskName = "ForcedReboot"
$action = New-ScheduledTaskAction -Execute "shutdown.exe" -Argument "/r /f /t 0"
# Create a trigger for the specified time
$trigger = New-ScheduledTaskTrigger -Once -At $rebootDateTime
# Configure settings to allow the task to run when no user is logged in
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable `
-RunOnlyIfIdle:$false -WakeToRun
# Set the task to run with the highest privileges and even when no user is logged in
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
# Register the task
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force
Write-Host "Scheduled reboot at $rebootDateTime, even if no user is logged in."

View File

@ -1,123 +0,0 @@
# This script is designed for use in Datto RMM (CentraStage).
# Purpose: Uninstall all "Microsoft Visual C++ 2010 ... Redistributable" (x86/x64), version independent.
#
# Example run / expected output:
# Uninstall Microsoft Visual C++ 2010
# Found: Microsoft Visual C++ 2010 x64 Redistributable - 10.0.40219, Microsoft Visual C++ 2010 x86 Redistributable - 10.0.40219
# Uninstall (MSI) Microsoft Visual C++ 2010 x64 Redistributable - 10.0.40219 -> msiexec /X{1D8E6291-B0D5-35EC-8441-6616F567A0F7} /quiet /norestart
# Uninstall (MSI) Microsoft Visual C++ 2010 x86 Redistributable - 10.0.40219 -> msiexec /X{F0C3E5D1-1ADE-321E-8167-68EF0DE699A5} /quiet /norestart
# Completed: all target items successfully removed (or not present).
#
# Usage in Datto RMM:
# - Create a new Component → Script (PowerShell).
# - Paste this script in the code editor.
# - Run As: System
# - Architecture: 64-bit
# - Test on one device before wide rollout.
# - Deploy via Job or Policy at the Site level to target all devices.
#
# Exit codes:
# 0 = OK (nothing found or everything removed successfully)
# 1 = Failures occurred
$ErrorActionPreference = 'Stop'
$fails = 0
function Invoke-Uninstall {
param(
[string]$CmdLine,
[string]$AppName
)
if (-not $CmdLine) {
Write-Warning ("No UninstallString found for " + $AppName)
$script:fails++
return
}
# Normalize to exe + args
$exe = $null
$args = ""
if ($CmdLine -match '^\s*"(.+?)"\s*(.*)$') {
$exe = $Matches[1]
$args = $Matches[2]
} elseif ($CmdLine -match '^\s*(\S+)\s*(.*)$') {
$exe = $Matches[1]
$args = $Matches[2]
}
# MSI: force silent uninstall
if ($exe -match '(?i)msiexec\.exe') {
if ($args -match '(?i)/I\s*{([0-9A-F\-]+)}') {
$guid = $Matches[1]
$args = "/x {" + $guid + "} /quiet /norestart"
} else {
if ($args -notmatch '(?i)/x') { $args = "/x " + $args }
if ($args -notmatch '(?i)/quiet|/qn') { $args = $args + " /quiet" }
if ($args -notmatch '(?i)/norestart') { $args = $args + " /norestart" }
}
Write-Output ("Uninstall (MSI) " + $AppName + " -> msiexec " + $args)
} else {
# Non-MSI: try to enforce silent switches
if ($args -notmatch '(?i)/quiet|/qn|/silent|/s') { $args = $args + " /quiet" }
if ($args -notmatch '(?i)/norestart') { $args = $args + " /norestart" }
Write-Output ("Uninstall (EXE) " + $AppName + " -> " + $exe + " " + $args)
}
try {
$p = Start-Process -FilePath $exe -ArgumentList $args -Wait -PassThru -WindowStyle Hidden
$code = $p.ExitCode
# Common msiexec codes: 0=success, 3010=reboot required, 1605=not installed
if ($exe -match '(?i)msiexec\.exe') {
if ($code -in 0,3010,1605) {
if ($code -eq 3010) { Write-Output ("Note: reboot required (3010) for " + $AppName) }
elseif ($code -eq 1605) { Write-Output ("Not installed (1605) for " + $AppName + ", skipping.") }
return
}
} else {
if ($code -eq 0) { return }
}
Write-Warning ("Uninstall failed for " + $AppName + " with exit code " + $code)
$script:fails++
} catch {
$msg = $_.Exception.Message
Write-Warning ("Error while uninstalling " + $AppName + ": " + $msg)
$script:fails++
}
}
# Pre-check: look for target apps
$uninstallKeys = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
$targets = foreach ($key in $uninstallKeys) {
Get-ItemProperty -Path $key -ErrorAction SilentlyContinue |
Where-Object {
$_.DisplayName -and ($_.DisplayName -like "Microsoft Visual C++ 2010*Redistributable*")
}
}
if (-not $targets) {
Write-Output "No Microsoft Visual C++ 2010 Redistributables found. Nothing to do."
exit 0
}
$foundList = ($targets.DisplayName | Sort-Object -Unique) -join ", "
Write-Output ("Found: " + $foundList)
# Uninstall each item
foreach ($app in $targets) {
$name = $app.DisplayName
$cmd = if ($app.QuietUninstallString) { $app.QuietUninstallString } else { $app.UninstallString }
Invoke-Uninstall -CmdLine $cmd -AppName $name
}
if ($fails -gt 0) {
Write-Output ("Completed with errors: " + $fails + " item(s) failed to uninstall.")
exit 1
} else {
Write-Output "Completed: all target items successfully removed (or not present)."
exit 0
}

View File

@ -1,10 +0,0 @@
#UUID list
#Maakt een Excel lijst met UUID en serienummer
cls
$UUID = (Get-WmiObject Win32_ComputerSystemProduct).UUID
$Serial = (Get-WmiObject Win32_BIOS).SerialNumber
$a = New-object PSobject
$a | Add-Member NoteProperty "UUID" $UUID
$a | Add-Member NoteProperty "Serial" $Serial
$a | Format-Table
$a | Export-Csv -Path list.csv -Append