diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..20a9f7f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +*.cmd text eol=crlf +*.ps1 text eol=crlf +*.py text eol=lf +*.yml text eol=lf +*.md text eol=lf +*.MD text eol=lf diff --git a/.github/workflows/script-checks.yml b/.github/workflows/script-checks.yml new file mode 100644 index 0000000..6d02e31 --- /dev/null +++ b/.github/workflows/script-checks.yml @@ -0,0 +1,55 @@ +name: Windows script safety checks + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + validate: + runs-on: windows-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Run static safety tests + run: python -m unittest discover -s tests -v + - name: Parse PowerShell helpers + shell: powershell + run: | + $hasErrors = $false + Get-ChildItem -Path scripts -Filter *.ps1 -Recurse | ForEach-Object { + $tokens = $null + $parseErrors = $null + $null = [System.Management.Automation.Language.Parser]::ParseFile( + $_.FullName, [ref]$tokens, [ref]$parseErrors + ) + foreach ($parseError in $parseErrors) { + Write-Host "FAILED: $($_.Name): $($parseError.Message)" + $hasErrors = $true + } + } + if ($hasErrors) { exit 1 } + - name: Smoke-test non-mutating previews + shell: powershell + run: | + $scripts = @( + 'active-directory\launch-ad-console-as-user.cmd', + 'adobe-acrobat\disable-javascript.cmd', + 'ms-teams\clear-teams-login.cmd', + 'ms-teams\full-clear-teams-cache.cmd', + 'onedrive\disable-onedrive.cmd', + 'outlook\fix-outlook-crashes-june-2025-update.cmd', + 'outlook\hide-new-outlook-button.cmd', + 'windows\remove-bloatware.cmd' + ) + foreach ($script in $scripts) { + Write-Host "PREVIEW: $script" + & cmd.exe /d /c $script /preview + if ($LASTEXITCODE -ne 0) { throw "Preview failed for $script (exit $LASTEXITCODE)" } + } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7a615e2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,13 @@ +# Repository agent instructions + +This repository contains Windows administrative scripts. Treat it as a **high-impact support toolkit**, not a general-purpose cleanup collection. + +- All code, comments, commit messages, branches, PR titles and PR descriptions must be in English. +- Keep the existing CMD entry points and `/preview`, `/apply` and `/help` interface. Preview must never change a user profile, registry, apps or files. +- Never silently delete `%UserProfile%\OneDrive`, shared Microsoft identity stores, user documents or arbitrary package matches. Changes with data-loss potential require a specific target list, explicit opt-in and a documented backup/recovery path. +- Never force-close Teams, Outlook or OneDrive during preview. An apply operation should require the user to close applications and acknowledge data or sync risks. +- Respect current-user versus elevated administrator context. Prefer checking prerequisites and refusing unsupported installations over guessing registry paths. +- Use exact package names instead of wildcards or broad regular expressions when removing Appx applications. Do not claim that deprovisioning permanently prevents Store reinstalls. +- Preserve previous policy settings when possible; offer reversible changes. Do not remove organization-managed GPO/MDM settings or add credential logging. +- For all code changes, update README documentation and regression checks; run the Windows workflow. CI must never execute any apply/restore action. +- Open a task branch and PR for review; do not push directly to `main`, merge or deploy without explicit approval. Report checks not run and remaining assumptions. diff --git a/README.MD b/README.MD index 4c2f45a..f4c60a3 100644 --- a/README.MD +++ b/README.MD @@ -1,70 +1,68 @@ -# ๐Ÿ› ๏ธ Sysadmin Scripts +# Sysadmin Scripts -A collection of Windows **batch scripts** for system administrators to quickly **fix**, **clean**, and **configure** common issues in Microsoft Office, Teams, Adobe Acrobat, and Windows itself. +A small collection of **Windows** support scripts for Active Directory, Acrobat/Reader, Microsoft Teams, OneDrive, classic Outlook and optional Windows apps. All eight CMD entry points default to **preview only**. No script downloads or executes remote code. -These scripts are designed to save time and reduce troubleshooting effort for **IT professionals**, **Helpdesk technicians**, and **power users**. +> **Read this before using the scripts on a user's PC.** `/apply` changes the current machine or account, sometimes irreversibly. Test in an isolated VM, review the proposed targets, keep backups and verify the affected account. The repository is MIT-licensed and provided without warranty. ---- +## Quick start -## ๐Ÿ“„ Script Overview +Open a terminal in the repository and run a script without arguments, or explicitly pass `/preview`: -### ๐Ÿ“‚ Adobe Acrobat -- [`adobe-acrobat/disable-javascript.cmd`](adobe-acrobat/disable-javascript.cmd) - ๐Ÿ”’ Disables JavaScript and trusted folders in Adobe Acrobat Reader DC for enhanced security using registry keys. +```cmd +ms-teams\full-clear-teams-cache.cmd /preview +``` ---- +Review its output. To carry out the operation, use `/apply`: -### ๐Ÿ’ฌ Microsoft Teams -- [`ms-teams/clear-teams-login.cmd`](ms-teams/clear-teams-login.cmd) - ๐Ÿงน Clears cached login folders for Microsoft Teams to ensure credentials must be re-entered. +```cmd +ms-teams\full-clear-teams-cache.cmd /apply +``` -- [`ms-teams/full-clear-teams-cache.cmd`](ms-teams/full-clear-teams-cache.cmd) - ๐Ÿงผ Completely removes all Teams cache and login data to fix lag, crashes, or syncing issues. +Destructive or machine-wide helpers also require a typed confirmation. `/help` prints usage. A successful preview does **not** establish that applying the change will succeed. Scripts return exit code `0` for success or a safe skip, `1` for an operational failure and `2` for invalid usage or a cancelled operation. ---- +**Account context matters:** Run Teams, Outlook and per-user Appx tasks as the **affected user**. Starting them through an alternate administrator account changes the administrator's own profile/HKCU instead. Use an elevated terminal for machine-wide Adobe/OneDrive policy changes and Appx deprovisioning, preferably by elevating the intended account rather than switching accounts. -### ๐Ÿ“ง Outlook -- [`outlook/fix-outlook-crashes-june-2025-update.cmd`](outlook/fix-outlook-crashes-june-2025-update.cmd) - ๐Ÿ› ๏ธ Fixes crashes caused by the June 2025 Outlook Classic update by creating the missing `FORMS2` folder in `%APPDATA%`. - ๐Ÿ”— [Microsoft Support Article](https://support.microsoft.com/en-us/office/classic-outlook-crashes-opening-or-starting-a-new-email-1b413573-7dfc-4147-9c53-c2f1183b89b8) +The PowerShell helpers use Windows PowerShell 5.1 and the built-in Windows utilities. CMD launchers set the execution policy only for their own PowerShell process; organization-enforced policies still apply. Inspect the local, trusted scripts before execution. -- [`outlook/hide-new-outlook-button.cmd`](outlook/hide-new-outlook-button.cmd) - ๐Ÿ™ˆ Hides the "Try the New Outlook" toggle in Outlook 2016/2019/365 using registry configuration. - ๐Ÿ”— [Microsoft Learn: Control Install](https://learn.microsoft.com/de-de/microsoft-365-apps/outlook/get-started/control-install) +## Scripts ---- +| Entry point | Preview / apply behavior | Scope and recovery | +| --- | --- | --- | +| [Launch AD console](active-directory/launch-ad-console-as-user.cmd) | `/apply` securely prompts for a domain, account and password, then starts `dsa.msc` with alternate credentials. | Requires RSAT Active Directory tools and a simple DNS/NetBIOS domain and sAMAccountName. No password is stored. Closing the console undoes the action. | +| [Disable Adobe JavaScript](adobe-acrobat/disable-javascript.cmd) | Detects supported Reader/Acrobat DC installations before setting `bDisableJavaScript=1` and `bDisableTrustedFolders=1`. | Machine-wide Adobe policy. Existing target keys are exported to a timestamped REG backup in `%TEMP%` before modification. Review managed policies before restoring a backup. | +| [Reset Teams login cache](ms-teams/clear-teams-login.cmd) | Only clears the **new Teams-specific cache** after Teams is closed and you type `RESET`. | Does **not** delete OneAuth, TokenBroker, IdentityCache or AAD BrokerPlugin. It cannot guarantee sign-out with Windows SSO; use Teams' own Sign out if account separation is required. | +| [Reset full Teams caches](ms-teams/full-clear-teams-cache.cmd) | Clears new Teams' cache and known classic Teams cache subfolders after confirmation. | Preserves shared M365 identity stores and classic diagnostic logs outside the listed caches. Some personalization may be lost; restart Teams. | +| [Disable/remove OneDrive](onedrive/disable-onedrive.cmd) | After confirmation, sets the documented `DisableFileSyncNGSC=1` machine policy and invokes an available OneDrive uninstaller. | **Never deletes `%UserProfile%\OneDrive` or its contents.** Verify sync and backup status manually before applying. An existing policy key is exported to `%TEMP%`. Reverting requires changing the policy, and reinstalling OneDrive if removed. | +| [Classic Outlook FORMS2 workaround](outlook/fix-outlook-crashes-june-2025-update.cmd) | Only creates the quoted `%LOCALAPPDATA%\Microsoft\FORMS2` folder when missing and Outlook is closed. | For the specific June 2025 classic Outlook forms-library crash. Microsoft has fixed the issue in subsequent builds: update Office first. | +| [Hide New Outlook toggle](outlook/hide-new-outlook-button.cmd) | `/apply` sets the current user's `HideNewOutlookToggle=1`; `/restore` removes the local value after confirmation. | Only affects the classic Outlook toggle, not installation of the new app. Do not override organization-managed settings. | +| [Remove selected Appx apps](windows/remove-bloatware.cmd) | Shows **exact package names** first; `/apply` requires admin rights and typed `REMOVE`. | Only removes matching installed apps from the current user and deprovisions matching packages for future accounts. Does not affect every existing user and does **not** permanently block Store/Windows reinstallation. | -### โ˜๏ธ Windows / OneDrive -- [`onedrive/disable-onedrive.cmd`](onedrive/disable-onedrive.cmd) - ๐Ÿšซ Uninstalls OneDrive, removes leftover files and disables the sync client via registry settings. +### Microsoft Teams troubleshooting -- [`windows/remove-bloatware.cmd`](windows/remove-bloatware.cmd) - ๐Ÿงฝ Removes pre-installed bloatware apps (e.g. Xbox, Candy Crush, Paint 3D) and blocks their future installation using PowerShell. +Microsoft does **not** recommend cache deletion as a general fix for missing chats, channels or notifications: it can remove diagnostics that support needs. Use the cache scripts only for a targeted local-client problem after checking network, service and account status. Official procedure: [Clear the Teams client cache](https://learn.microsoft.com/en-us/troubleshoot/microsoftteams/teams-administration/clear-teams-cache). ---- +### OneDrive and managed devices -### ๐Ÿง‘โ€๐Ÿ’ผ Active Directory -- [`active-directory/launch-ad-console-as-user.cmd`](active-directory/launch-ad-console-as-user.cmd) - ๐Ÿ” Prompts for domain credentials and launches the Active Directory Users and Computers console (`dsa.msc`) as another user without switching sessions. +`DisableFileSyncNGSC` is the Windows policy that prevents OneDrive sync; it affects the machine and may be re-applied or controlled by Group Policy/MDM. See [Microsoft's OneDrive policy guidance](https://support.microsoft.com/en-us/onedrive/onedrive-won-t-start). Do not use the uninstaller until you have independently checked the user's sync queue and backups. The script cannot prove that cloud files are fully synchronized. ---- +### Adobe scope -> โš ๏ธ **Note:** Most scripts require **administrator privileges**. Always review scripts before running in production environments. Use at your own risk. +The script supports detected DC registry footprints for Reader/Acrobat and their 32-/64-bit policy paths. If installation detection fails, it makes no changes. Confirm your Adobe package's registry layout and deploy managed policy through GPO/MDM where appropriate. The keys disable JavaScript and trusted folders; they do not promise to disable every other trust mechanism. See [Adobe's enterprise registry guidance](https://helpx.adobe.com/business/enterprise/deploy-apps-updates/deploy-packages/acrobat-64-bit-for-enterprises.html). ---- +### Outlook scope -## ๐Ÿ“Œ Planned / To-Do +Microsoft documents the `FORMS2` workaround for a **fixed** June 2025 crash. Check [the known-issue article](https://support.microsoft.com/en-us/support/known-issues/classic-outlook-crashes-opening-or-starting-a-new-email) and installed build before applying it. The [New Outlook toggle setting](https://learn.microsoft.com/en-us/microsoft-365-apps/outlook/get-started/control-install) does not remove the new Outlook application. -- [ ] Office license cleanup -- [ ] Reset network stack (DNS/Winsock/IP) -- [ ] Fix printer spooler issues -- [ ] Systeminfo export and log collection -- [ ] Startup entry report -- [ ] Enable dark mode via registry -- [ ] Reset Outlook profile +## Validation ---- +GitHub Actions parses every PowerShell helper, runs static safety tests and smoke-tests every CMD launcher in `/preview` mode on Windows. The workflow intentionally never calls `/apply` or `/restore`. Follow [the VM test plan](docs/TESTING.md) for manual changes that cannot safely be exercised in CI. -## ๐Ÿ“œ License +## Planned additions -This project is licensed under the [MIT License](LICENSE). +- Non-destructive Office licensing diagnostics and report export +- Network, print spooler and system information collection (preview-first) +- A targeted Outlook profile diagnostic, without deleting a user's profile + +## License + +[MIT](LICENSE). diff --git a/active-directory/launch-ad-console-as-user.cmd b/active-directory/launch-ad-console-as-user.cmd index abb7dc1..51b66e9 100644 --- a/active-directory/launch-ad-console-as-user.cmd +++ b/active-directory/launch-ad-console-as-user.cmd @@ -1,25 +1,26 @@ -:: ============================================= -:: Launch Active Directory Console as Domain Admin -:: ============================================= -:: This script prompts for a domain and username, then launches the -:: Active Directory Users and Computers console (dsa.msc) using those credentials. -:: Useful for admins who are logged in with a non-privileged account. -:: -:: Requires: Domain credentials with AD permissions -:: @echo off setlocal - -:: Prompt for domain and username -set /p domain=Enter domain name (e.g. mydomain.local): -set /p username=Enter username (e.g. adminuser): - -:: Build full user string -set fulluser=%domain%\%username% - -:: Launch MMC with dsa.msc under different credentials -echo. -echo You will now be prompted for the password of %fulluser% -runas /user:%fulluser% "mmc dsa.msc" - -endlocal +if not "%~2"=="" goto usage +set "apply=" +if "%~1"=="" goto run +if /i "%~1"=="/preview" goto run +if /i "%~1"=="/apply" ( + set "apply=-Apply" + goto run +) +if /i "%~1"=="/help" goto help +goto usage +:run +set "ps=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" +if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "ps=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" +"%ps%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\scripts\Launch-ADConsole.ps1" %apply% +exit /b %errorlevel% +:help +echo Launch AD Users and Computers with separately prompted credentials. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +echo Default: preview only; /apply may require interactive confirmation. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +exit /b 2 diff --git a/adobe-acrobat/disable-javascript.cmd b/adobe-acrobat/disable-javascript.cmd index 1ae419a..9a72efa 100644 --- a/adobe-acrobat/disable-javascript.cmd +++ b/adobe-acrobat/disable-javascript.cmd @@ -1,11 +1,26 @@ -:: ============================================= -:: Disable JavaScript in Adobe Acrobat Reader DC -:: ============================================= -:: This script fully disables JavaScript and trusted folders in Adobe Acrobat Reader DC -:: by setting the appropriate registry keys for FeatureLockDown policies. -:: Requires administrative privileges. -:: -:: Usage: Run as administrator -:: -reg add "HKLM\SOFTWARE\WOW6432Node\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown" /v "bDisableJavaScript" /t REG_DWORD /d "0x00000001" /f -reg add "HKLM\SOFTWARE\WOW6432Node\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown" /v "bDisableTrustedFolders" /t REG_DWORD /d "0x00000001" /f \ No newline at end of file +@echo off +setlocal +if not "%~2"=="" goto usage +set "apply=" +if "%~1"=="" goto run +if /i "%~1"=="/preview" goto run +if /i "%~1"=="/apply" ( + set "apply=-Apply" + goto run +) +if /i "%~1"=="/help" goto help +goto usage +:run +set "ps=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" +if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "ps=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" +"%ps%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\scripts\Set-AcrobatJavaScriptPolicy.ps1" %apply% +exit /b %errorlevel% +:help +echo Disable Acrobat/Reader JavaScript and trusted folders for detected DC installations. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +echo Default: preview only; /apply may require interactive confirmation. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +exit /b 2 diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..1ec9b64 --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,22 @@ +# Manual Windows VM test plan + +Use a disposable, snapshotted Windows 11 VM and a separate test account. **Never use real user files or active corporate accounts as test data.** GitHub Actions covers PowerShell parsing, static safety rules and non-mutating previews only. + +## Preparation + +1. Take a VM snapshot, create a test user's profile and document the Windows, Teams, OneDrive, Office, Acrobat and PowerShell versions. +2. If testing OneDrive, use a disposable test account and put uniquely named **backed-up** sample files in the test OneDrive directory. Verify which files are cloud-only, locally available, unsynchronized or synced; do not claim the script can determine this. +3. Run all eight `/preview` commands from the affected account; record exit codes and verify file timestamps, registry values and installed package lists do not change. +4. Run each script with `/help` and invalid arguments. The invalid-usage exit code should be 2. +5. Test operations with missing applications, missing RSAT, already-applied policies, non-ASCII or spaced profile paths and a different admin account. + +## Apply cases + +- **Teams:** Keep Teams open and verify `/apply` refuses the deletion. Quit Teams, run `/apply`, decline confirmation and check that no files changed. Repeat with confirmation on disposable local cache data. Verify that shared M365 identity paths still exist. +- **OneDrive:** Verify a declined confirmation is a no-op. With a disposable synchronized profile, confirm that the OneDrive user directory and its files are byte-for-byte unchanged after applying policy/uninstall. Inspect policy backup and `DisableFileSyncNGSC`; verify the app uninstall result separately. Restore the VM snapshot. +- **Appx:** Run as the intended admin test account, compare preview package IDs with the exact allowlist and provisioned package IDs. Decline, then confirm; verify only listed packages changed. Check that an unrelated Xbox/Windows dependency remains installed. Restore the snapshot. +- **Acrobat:** With a supported Reader/Acrobat install, verify a declined confirmation changes nothing. After confirmation, inspect both DWORD values and the exported REG backup. Repeat on a device without the product and verify a safe skip. +- **Outlook:** Verify the toggle only changes the current user's HKCU key; test `/restore` with and without a pre-existing override. For `FORMS2`, verify Outlook-open refusal, idempotent creation and correct behavior with a spaced user-profile path. +- **AD console:** Verify missing RSAT, invalid account input and canceling the credential prompt. With a non-production test domain, verify the console uses the intended credentials. Never record passwords. + +Record results, failed/unsupported combinations and a screenshot or log excerpt that contains no secrets. Do not mark live system behavior as tested based only on a green CI run. diff --git a/ms-teams/clear-teams-login.cmd b/ms-teams/clear-teams-login.cmd index 4c3e0ca..58d7f88 100644 --- a/ms-teams/clear-teams-login.cmd +++ b/ms-teams/clear-teams-login.cmd @@ -1,14 +1,26 @@ -:: ============================================= -:: Clear Microsoft Teams Login Cache -:: ============================================= -:: This script deletes all folders that cache your last logins in Microsoft Teams, -:: ensuring that nobody can access your account without entering a password. -:: -:: Requires: Run as the current user -:: @echo off -@RD /S /Q "%LocalAppData%\Packages\MSTeams_8wekyb3d8bbwe" -@RD /S /Q "%LocalAppData%\Packages\Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy" -@RD /S /Q "%LocalAppData%\Microsoft\OneAuth" -@RD /S /Q "%LocalAppData%\Microsoft\TokenBroker" -@RD /S /Q "%LocalAppData%\Microsoft\IdentityCache" \ No newline at end of file +setlocal +if not "%~2"=="" goto usage +set "apply=" +if "%~1"=="" goto run +if /i "%~1"=="/preview" goto run +if /i "%~1"=="/apply" ( + set "apply=-Apply" + goto run +) +if /i "%~1"=="/help" goto help +goto usage +:run +set "ps=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" +if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "ps=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" +"%ps%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\scripts\Reset-TeamsCache.ps1" -Mode Login %apply% +exit /b %errorlevel% +:help +echo Reset the NEW Teams local cache only. Windows SSO may sign you back in. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +echo Default: preview only; /apply may require interactive confirmation. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +exit /b 2 diff --git a/ms-teams/full-clear-teams-cache.cmd b/ms-teams/full-clear-teams-cache.cmd index abf157f..1ae0755 100644 --- a/ms-teams/full-clear-teams-cache.cmd +++ b/ms-teams/full-clear-teams-cache.cmd @@ -1,22 +1,26 @@ -:: ============================================= -:: Full Microsoft Teams Cache Clear -:: ============================================= -:: This script completely clears all Microsoft Teams cache and login data to fix lag, -:: crashes, and stubborn issues that Microsoft does not provide an option for. -:: -:: Requires: Run as the current user -:: - -@RD /S /Q "%LocalAppData%\Packages\MSTeams_8wekyb3d8bbwe" -@RD /S /Q "%LocalAppData%\Packages\Microsoft.AAD.BrokerPlugin_cw5n1h2txyewy" -@RD /S /Q "%LocalAppData%\Microsoft\OneAuth" -@RD /S /Q "%LocalAppData%\Microsoft\TokenBroker" -@RD /S /Q "%LocalAppData%\Microsoft\IdentityCache" -@RD /S /Q "%appdata%\Microsoft\teams\application cache\cache" -@RD /S /Q "%appdata%\Microsoft\teams\blob_storage" -@RD /S /Q "%appdata%\Microsoft\teams\databases" -@RD /S /Q "%appdata%\Microsoft\teams\GPUcache" -@RD /S /Q "%appdata%\Microsoft\teams\IndexedDB" -@RD /S /Q "%appdata%\Microsoft\teams\Local Storage" -@RD /S /Q "%appdata%\Microsoft\teams\tmp" -@RD /S /Q "%appdata%\Microsoft\teams\Cache" \ No newline at end of file +@echo off +setlocal +if not "%~2"=="" goto usage +set "apply=" +if "%~1"=="" goto run +if /i "%~1"=="/preview" goto run +if /i "%~1"=="/apply" ( + set "apply=-Apply" + goto run +) +if /i "%~1"=="/help" goto help +goto usage +:run +set "ps=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" +if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "ps=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" +"%ps%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\scripts\Reset-TeamsCache.ps1" -Mode Full %apply% +exit /b %errorlevel% +:help +echo Reset the new Teams cache and known classic Teams cache folders; preserve Microsoft identity broker data. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +echo Default: preview only; /apply may require interactive confirmation. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +exit /b 2 diff --git a/onedrive/disable-onedrive.cmd b/onedrive/disable-onedrive.cmd index 738e849..4ca3aec 100644 --- a/onedrive/disable-onedrive.cmd +++ b/onedrive/disable-onedrive.cmd @@ -1,32 +1,26 @@ -:: ============================================= -:: Disable and Remove OneDrive from Windows -:: ============================================= -:: This script uninstalls OneDrive, deletes leftover files and folders, -:: and disables it via registry so it no longer appears or auto-starts. -:: -:: Requires: Run as Administrator -:: @echo off - -:: Kill running OneDrive process -taskkill /f /im OneDrive.exe >nul 2>&1 - -:: Uninstall OneDrive (handles both 32-bit and 64-bit) -IF EXIST "%SystemRoot%\SysWOW64\OneDriveSetup.exe" ( - "%SystemRoot%\SysWOW64\OneDriveSetup.exe" /uninstall -) ELSE ( - "%SystemRoot%\System32\OneDriveSetup.exe" /uninstall +setlocal +if not "%~2"=="" goto usage +set "apply=" +if "%~1"=="" goto run +if /i "%~1"=="/preview" goto run +if /i "%~1"=="/apply" ( + set "apply=-Apply" + goto run ) - -:: Delete leftover OneDrive folders -@RD /S /Q "%UserProfile%\OneDrive" -@RD /S /Q "%LocalAppData%\Microsoft\OneDrive" -@RD /S /Q "%ProgramData%\Microsoft OneDrive" -@RD /S /Q "C:\OneDriveTemp" - -:: Disable OneDrive sync via registry -reg add "HKLM\Software\Policies\Microsoft\Windows\OneDrive" /v "DisableFileSync" /t REG_DWORD /d 1 /f - -:: Hide OneDrive from File Explorer navigation pane -reg add "HKCR\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /v "System.IsPinnedToNameSpaceTree" /t REG_DWORD /d 0 /f -reg add "HKCR\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /v "System.IsPinnedToNameSpaceTree" /t REG_DWORD /d 0 /f +if /i "%~1"=="/help" goto help +goto usage +:run +set "ps=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" +if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "ps=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" +"%ps%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\scripts\Disable-OneDrive.ps1" %apply% +exit /b %errorlevel% +:help +echo Apply the OneDrive sync policy and run its uninstaller without deleting any user files. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +echo Default: preview only; /apply may require interactive confirmation. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +exit /b 2 diff --git a/outlook/fix-outlook-crashes-june-2025-update.cmd b/outlook/fix-outlook-crashes-june-2025-update.cmd index 9164c13..bc7f631 100644 --- a/outlook/fix-outlook-crashes-june-2025-update.cmd +++ b/outlook/fix-outlook-crashes-june-2025-update.cmd @@ -1,9 +1,46 @@ -:: ============================================= -:: Fix Outlook Classic Crashes (June 2025 Update) -:: ============================================= -:: This script creates the required FORMS2 folder in your AppData to prevent lags -:: and crashes introduced by the June 2025 update of Outlook Classic. -:: -:: Requires: Run as the current user -:: -mkdir %localappdata%\Microsoft\FORMS2 \ No newline at end of file +@echo off +setlocal +if not "%~2"=="" goto usage +if "%~1"=="" goto preview +if /i "%~1"=="/preview" goto preview +if /i "%~1"=="/apply" goto apply +if /i "%~1"=="/help" goto help +goto usage +:preview +if not defined LOCALAPPDATA ( + echo FAILED: LOCALAPPDATA is not available for the current account. + exit /b 1 +) +echo PREVIEW: Create "%LOCALAPPDATA%\Microsoft\FORMS2" for this account if missing. +echo This was a workaround for a specific 2025 Outlook crash; install Office updates first. +exit /b 0 +:apply +if not defined LOCALAPPDATA ( + echo FAILED: LOCALAPPDATA is not available for the current account. + exit /b 1 +) +tasklist /FI "IMAGENAME eq OUTLOOK.EXE" 2>nul | findstr /I /C:"OUTLOOK.EXE" >nul +if not errorlevel 1 ( + echo FAILED: Close Outlook and other Office apps before running this workaround. + exit /b 1 +) +if exist "%LOCALAPPDATA%\Microsoft\FORMS2\." ( + echo SKIPPED: FORMS2 already exists. + exit /b 0 +) +mkdir "%LOCALAPPDATA%\Microsoft\FORMS2" 2>nul +if errorlevel 1 ( + echo FAILED: Could not create FORMS2 for the current account. + exit /b 1 +) +echo SUCCESS: Created FORMS2 for the current account. +exit /b 0 +:help +echo Create the current user's FORMS2 folder for the historical June 2025 Outlook crash. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +echo Default: preview only. Update Outlook before using this workaround. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +exit /b 2 diff --git a/outlook/hide-new-outlook-button.cmd b/outlook/hide-new-outlook-button.cmd index e260f1a..09c76af 100644 --- a/outlook/hide-new-outlook-button.cmd +++ b/outlook/hide-new-outlook-button.cmd @@ -1,9 +1,32 @@ -:: ============================================= -:: Hide "Try the New Outlook" Button in Outlook 2016/2019/365 -:: ============================================= -:: This script hides the "Try the New Outlook" toggle button in Outlook by setting -:: the appropriate registry key. Works for Office 16.0 (Outlook 2016/2019/365). -:: -:: Requires: Run as the current user -:: -reg add "HKCU\Software\Microsoft\Office\16.0\Outlook\Options\General" /v HideNewOutlookToggle /t REG_DWORD /d 1 /f \ No newline at end of file +@echo off +setlocal +if not "%~2"=="" goto usage +set "apply=" +set "restore=" +if "%~1"=="" goto run +if /i "%~1"=="/preview" goto run +if /i "%~1"=="/apply" ( + set "apply=-Apply" + goto run +) +if /i "%~1"=="/restore" ( + set "restore=-Restore" + set "apply=-Apply" + goto run +) +if /i "%~1"=="/help" goto help +goto usage +:run +set "ps=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" +if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "ps=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" +"%ps%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\scripts\Set-OutlookToggle.ps1" %apply% %restore% +exit /b %errorlevel% +:help +echo Hide the New Outlook toggle for the CURRENT user, or restore the local override. +echo Usage: %~nx0 [/preview ^| /apply ^| /restore ^| /help] +echo Default: preview only. /restore requires confirmation. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /restore ^| /help] +exit /b 2 diff --git a/scripts/Common.ps1 b/scripts/Common.ps1 new file mode 100644 index 0000000..d3d1ca3 --- /dev/null +++ b/scripts/Common.ps1 @@ -0,0 +1,52 @@ +# Shared safety checks for interactive Windows support tasks. +Set-StrictMode -Version Latest + +function Require-Administrator { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = [Security.Principal.WindowsPrincipal]::new($identity) + if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + throw 'Run this command from an elevated terminal using the intended account.' + } +} + +function Confirm-Token { + param( + [Parameter(Mandatory)][string]$Token, + [Parameter(Mandatory)][string]$Description + ) + Write-Host $Description + $answer = Read-Host ("Type {0} to proceed" -f $Token) + if ($answer -cne $Token) { + Write-Host 'CANCELLED: No changes were made by this confirmation step.' + return $false + } + return $true +} + +function Assert-SafeDeleteTarget { + param( + [Parameter(Mandatory)][string]$Path, + [Parameter(Mandatory)][string]$Root + ) + $fullRoot = [IO.Path]::GetFullPath($Root).TrimEnd([char[]]@('\', '/')) + [IO.Path]::DirectorySeparatorChar + $fullPath = [IO.Path]::GetFullPath($Path) + if (-not $fullPath.StartsWith($fullRoot, [StringComparison]::OrdinalIgnoreCase)) { + throw "Refusing to remove a path outside its expected profile root: $fullPath" + } + $relative = $fullPath.Substring($fullRoot.Length) + $parts = @($relative -split '[\\/]') + if ($parts.Count -lt 2) { + throw "Refusing to remove a profile root or direct child: $fullPath" + } + $current = $fullRoot.TrimEnd([char[]]@('\', '/')) + foreach ($part in $parts) { + if (-not $part) { continue } + $current = Join-Path $current $part + if (Test-Path -LiteralPath $current) { + $item = Get-Item -LiteralPath $current -Force + if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + throw "Refusing to follow a reparse point during deletion: $current" + } + } + } +} diff --git a/scripts/Disable-OneDrive.ps1 b/scripts/Disable-OneDrive.ps1 new file mode 100644 index 0000000..b817821 --- /dev/null +++ b/scripts/Disable-OneDrive.ps1 @@ -0,0 +1,52 @@ +param([switch]$Apply) +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'Common.ps1') + +try { + $policy = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive' + $registry = 'HKLM\SOFTWARE\Policies\Microsoft\Windows\OneDrive' + $candidates = @( + (Join-Path $env:SystemRoot 'SysWOW64\OneDriveSetup.exe'), + (Join-Path $env:SystemRoot 'System32\OneDriveSetup.exe'), + (Join-Path $env:LOCALAPPDATA 'Microsoft\OneDrive\Update\OneDriveSetup.exe') + ) + $setup = @($candidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1) + Write-Host 'OneDrive policy: DisableFileSyncNGSC=1 (machine-wide).' + if ($setup.Count -gt 0) { + Write-Host "OneDrive uninstaller: $($setup[0])" + } + else { + Write-Host 'NOTICE: No bundled OneDriveSetup.exe found; only the policy can be applied.' + } + Write-Host 'The OneDrive user folder and its files will NEVER be deleted by this script.' + if (-not $Apply) { + Write-Host 'PREVIEW ONLY: No processes, registry values, applications or files were changed.' + exit 0 + } + Require-Administrator + if (-not (Confirm-Token -Token 'DISABLE' -Description 'Confirm all OneDrive data is synced or independently backed up, and close OneDrive. This affects every user on this PC.')) { + exit 2 + } + if (Test-Path -LiteralPath $policy) { + $backup = Join-Path $env:TEMP ("OneDrive-policy-{0}.reg" -f (Get-Date -Format 'yyyyMMdd-HHmmssfff')) + & reg.exe export $registry $backup /y | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'Could not export the existing OneDrive policy; no changes were applied.' } + Write-Host "BACKUP: $backup" + } + New-Item -Path $policy -Force | Out-Null + New-ItemProperty -Path $policy -Name 'DisableFileSyncNGSC' -PropertyType DWord -Value 1 -Force | Out-Null + Write-Host 'SUCCESS: OneDrive sync is disabled by local machine policy.' + if ($setup.Count -gt 0) { + $process = Start-Process -FilePath $setup[0] -ArgumentList '/uninstall' -Wait -PassThru -ErrorAction Stop + if ($process.ExitCode -ne 0) { throw "OneDrive uninstaller exited with code $($process.ExitCode); policy remains set." } + Write-Host 'SUCCESS: OneDrive uninstaller returned success.' + } + else { + Write-Host 'SKIPPED: Uninstall step (setup executable not available).' + } + exit 0 +} +catch { + Write-Host "FAILED: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/Launch-ADConsole.ps1 b/scripts/Launch-ADConsole.ps1 new file mode 100644 index 0000000..c5a81b8 --- /dev/null +++ b/scripts/Launch-ADConsole.ps1 @@ -0,0 +1,36 @@ +param([switch]$Apply) +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'Common.ps1') + +try { + $console = Join-Path $env:WINDIR 'System32\dsa.msc' + if (-not $Apply) { + Write-Host "PREVIEW: Open Active Directory Users and Computers using alternate credentials: $console" + if (-not (Test-Path -LiteralPath $console)) { + Write-Host 'NOTICE: RSAT Active Directory tools are not installed on this machine.' + } + exit 0 + } + if (-not (Test-Path -LiteralPath $console)) { + throw 'dsa.msc was not found. Install the RSAT Active Directory tools first.' + } + $domain = (Read-Host 'Domain (DNS or NetBIOS name)').Trim() + $user = (Read-Host 'Account name (sAMAccountName)').Trim() + if ($domain -cnotmatch '^[A-Za-z0-9][A-Za-z0-9.-]*$' -or + $user -cnotmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$') { + throw 'Invalid domain or account name. Use a DNS/NetBIOS domain and a simple sAMAccountName.' + } + $credential = Get-Credential -UserName ($domain + '\' + $user) -Message 'Credentials for Active Directory Users and Computers' + if ($null -eq $credential) { + Write-Host 'CANCELLED: No console was opened.' + exit 2 + } + $mmc = Join-Path $env:WINDIR 'System32\mmc.exe' + $process = Start-Process -FilePath $mmc -ArgumentList ('"' + $console + '"') -Credential $credential -PassThru -ErrorAction Stop + Write-Host "SUCCESS: Started Active Directory Users and Computers (PID $($process.Id))." + exit 0 +} +catch { + Write-Host "FAILED: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/Remove-Bloatware.ps1 b/scripts/Remove-Bloatware.ps1 new file mode 100644 index 0000000..9fe4110 --- /dev/null +++ b/scripts/Remove-Bloatware.ps1 @@ -0,0 +1,70 @@ +param([switch]$Apply) +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'Common.ps1') + +# Intentionally exact package names: no wildcards, no all-user removal, and no Store policy. +$packageNames = @( + 'Microsoft.3DBuilder', + 'Microsoft.ZuneMusic', + 'Microsoft.ZuneVideo', + 'Microsoft.BingWeather', + 'Microsoft.WindowsMaps', + 'Microsoft.MicrosoftSolitaireCollection', + 'Microsoft.XboxApp', + 'Microsoft.GetHelp', + 'Microsoft.WindowsFeedbackHub', + 'Microsoft.Microsoft3DViewer', + 'Microsoft.MixedReality.Portal' +) +try { + $installed = @(Get-AppxPackage -ErrorAction Stop | Where-Object { $packageNames -contains $_.Name }) + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $isAdmin = [Security.Principal.WindowsPrincipal]::new($identity).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + $provisioned = @() + if ($isAdmin) { + $provisioned = @(Get-AppxProvisionedPackage -Online -ErrorAction Stop | + Where-Object { $packageNames -contains ($_.DisplayName -split '_')[0] }) + } + Write-Host "Running as: $env:USERDOMAIN\$env:USERNAME. Installed packages below belong only to this user." + foreach ($item in $installed) { Write-Host "USER PACKAGE: $($item.Name) [$($item.PackageFullName)]" } + foreach ($item in $provisioned) { Write-Host "PROVISIONED PACKAGE: $($item.PackageName)" } + if (-not $isAdmin) { Write-Host 'NOTICE: Run elevated to inspect or remove provisioned packages.' } + if ($installed.Count -eq 0 -and $provisioned.Count -eq 0) { Write-Host 'SKIPPED: No matching packages were found.' } + if (-not $Apply) { + Write-Host 'PREVIEW ONLY: No packages were removed. This is not a permanent block on reinstalling apps.' + exit 0 + } + Require-Administrator + if ($installed.Count -eq 0 -and $provisioned.Count -eq 0) { exit 0 } + if (-not (Confirm-Token -Token 'REMOVE' -Description 'Remove exactly the listed packages for this user and deprovision them for future users?')) { + exit 2 + } + $failed = $false + foreach ($item in $installed) { + try { + Remove-AppxPackage -Package $item.PackageFullName -ErrorAction Stop + Write-Host "SUCCESS: Removed current-user package $($item.Name)." + } + catch { + Write-Host "FAILED: $($item.Name): $($_.Exception.Message)" + $failed = $true + } + } + foreach ($item in $provisioned) { + try { + Remove-AppxProvisionedPackage -Online -PackageName $item.PackageName -ErrorAction Stop | Out-Null + Write-Host "SUCCESS: Deprovisioned $($item.PackageName)." + } + catch { + Write-Host "FAILED: $($item.PackageName): $($_.Exception.Message)" + $failed = $true + } + } + if ($failed) { exit 1 } + Write-Host 'SUCCESS: Selected packages removed. The Store or future OS updates may install them again.' + exit 0 +} +catch { + Write-Host "FAILED: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/Reset-TeamsCache.ps1 b/scripts/Reset-TeamsCache.ps1 new file mode 100644 index 0000000..7a53e79 --- /dev/null +++ b/scripts/Reset-TeamsCache.ps1 @@ -0,0 +1,63 @@ +param( + [Parameter(Mandatory)][ValidateSet('Login', 'Full')][string]$Mode, + [switch]$Apply +) +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'Common.ps1') + +try { + if (-not $env:LOCALAPPDATA -or -not $env:APPDATA) { + throw 'The current user profile is missing LOCALAPPDATA or APPDATA.' + } + $newTeams = Join-Path $env:LOCALAPPDATA 'Packages\MSTeams_8wekyb3d8bbwe\LocalCache\Microsoft\MSTeams' + $targets = @([pscustomobject]@{ Name = 'New Teams local cache'; Path = $newTeams; Root = $env:LOCALAPPDATA }) + if ($Mode -eq 'Full') { + foreach ($folder in @('Cache', 'application cache\cache', 'blob_storage', 'databases', 'GPUcache', 'IndexedDB', 'Local Storage', 'tmp')) { + $targets += [pscustomobject]@{ + Name = "Classic Teams $folder" + Path = (Join-Path (Join-Path $env:APPDATA 'Microsoft\Teams') $folder) + Root = $env:APPDATA + } + } + } + foreach ($target in $targets) { + Assert-SafeDeleteTarget -Path $target.Path -Root $target.Root + if (Test-Path -LiteralPath $target.Path) { + Write-Host ("{0}: {1}" -f $(if ($Apply) { 'TARGET' } else { 'PREVIEW' }), $target.Path) + } + else { + Write-Host "SKIPPED (not present): $($target.Path)" + } + } + if (-not $Apply) { + Write-Host 'PREVIEW ONLY: No files or Microsoft identity broker data were changed.' + Write-Host 'Cache removal cannot guarantee sign-out when Windows single sign-on is enabled.' + exit 0 + } + $running = @(Get-Process -Name 'ms-teams', 'Teams' -ErrorAction SilentlyContinue) + if ($running.Count -gt 0) { + throw 'Quit Teams completely (including the tray icon) before resetting its cache.' + } + if (-not (Confirm-Token -Token 'RESET' -Description 'This deletes Teams-only local caches. Personalization may be reset; do not use it to fix missing chats or channels.')) { + exit 2 + } + $failed = $false + foreach ($target in $targets) { + if (-not (Test-Path -LiteralPath $target.Path)) { continue } + try { + Remove-Item -LiteralPath $target.Path -Recurse -Force -ErrorAction Stop + Write-Host "SUCCESS: Removed $($target.Name)." + } + catch { + Write-Host "FAILED: $($target.Name): $($_.Exception.Message)" + $failed = $true + } + } + if ($failed) { exit 1 } + Write-Host 'SUCCESS: Teams cache reset completed. Restart Teams.' + exit 0 +} +catch { + Write-Host "FAILED: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/Set-AcrobatJavaScriptPolicy.ps1 b/scripts/Set-AcrobatJavaScriptPolicy.ps1 new file mode 100644 index 0000000..b8ebc0a --- /dev/null +++ b/scripts/Set-AcrobatJavaScriptPolicy.ps1 @@ -0,0 +1,43 @@ +param([switch]$Apply) +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'Common.ps1') + +$products = @( + @{ Name = 'Acrobat Reader 64-bit'; Install = 'HKLM:\SOFTWARE\Adobe\Acrobat Reader\DC'; Policy = 'HKLM:\SOFTWARE\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown'; Registry = 'HKLM\SOFTWARE\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown' }, + @{ Name = 'Acrobat Reader 32-bit'; Install = 'HKLM:\SOFTWARE\WOW6432Node\Adobe\Acrobat Reader\DC'; Policy = 'HKLM:\SOFTWARE\WOW6432Node\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown'; Registry = 'HKLM\SOFTWARE\WOW6432Node\Policies\Adobe\Acrobat Reader\DC\FeatureLockDown' }, + @{ Name = 'Adobe Acrobat 64-bit'; Install = 'HKLM:\SOFTWARE\Adobe\Adobe Acrobat\DC'; Policy = 'HKLM:\SOFTWARE\Policies\Adobe\Adobe Acrobat\DC\FeatureLockDown'; Registry = 'HKLM\SOFTWARE\Policies\Adobe\Adobe Acrobat\DC\FeatureLockDown' }, + @{ Name = 'Adobe Acrobat 32-bit'; Install = 'HKLM:\SOFTWARE\WOW6432Node\Adobe\Adobe Acrobat\DC'; Policy = 'HKLM:\SOFTWARE\WOW6432Node\Policies\Adobe\Adobe Acrobat\DC\FeatureLockDown'; Registry = 'HKLM\SOFTWARE\WOW6432Node\Policies\Adobe\Adobe Acrobat\DC\FeatureLockDown' } +) +try { + $targets = @($products | Where-Object { Test-Path -LiteralPath $_.Install }) + if ($targets.Count -eq 0) { + Write-Host 'SKIPPED: No supported Acrobat/Reader DC installation was detected.' + if ($Apply) { exit 2 } + exit 0 + } + foreach ($target in $targets) { + Write-Host ("{0}: {1} -> bDisableJavaScript=1, bDisableTrustedFolders=1" -f $(if ($Apply) { 'TARGET' } else { 'PREVIEW' }), $target.Registry) + } + if (-not $Apply) { exit 0 } + Require-Administrator + if (-not (Confirm-Token -Token 'HARDEN' -Description 'This changes machine-wide Adobe security policies. Close Adobe applications first.')) { + exit 2 + } + foreach ($target in $targets) { + if (Test-Path -LiteralPath $target.Policy) { + $backup = Join-Path $env:TEMP ("Adobe-policy-{0}-{1}.reg" -f ($target.Name -replace '[^A-Za-z0-9]', '-'), (Get-Date -Format 'yyyyMMdd-HHmmssfff')) + & reg.exe export $target.Registry $backup /y | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not back up $($target.Registry)." } + Write-Host "BACKUP: $backup" + } + New-Item -Path $target.Policy -Force | Out-Null + New-ItemProperty -Path $target.Policy -Name 'bDisableJavaScript' -Value 1 -PropertyType DWord -Force | Out-Null + New-ItemProperty -Path $target.Policy -Name 'bDisableTrustedFolders' -Value 1 -PropertyType DWord -Force | Out-Null + Write-Host "SUCCESS: Updated $($target.Name)." + } + exit 0 +} +catch { + Write-Host "FAILED: $($_.Exception.Message)" + exit 1 +} diff --git a/scripts/Set-OutlookToggle.ps1 b/scripts/Set-OutlookToggle.ps1 new file mode 100644 index 0000000..ed9791c --- /dev/null +++ b/scripts/Set-OutlookToggle.ps1 @@ -0,0 +1,40 @@ +param( + [switch]$Apply, + [switch]$Restore +) +$ErrorActionPreference = 'Stop' +. (Join-Path $PSScriptRoot 'Common.ps1') + +try { + $path = 'Software\Microsoft\Office\16.0\Outlook\Options\General' + $name = 'HideNewOutlookToggle' + if (-not $Apply) { + Write-Host ("PREVIEW: {0} HKCU:\{1}\{2} for the current account." -f $(if ($Restore) { 'Delete' } else { 'Set to DWORD 1' }), $path, $name) + Write-Host 'This only changes the classic Outlook switch, not the installed app or an organization policy.' + exit 0 + } + if ($Restore) { + if (-not (Confirm-Token -Token 'RESTORE' -Description 'Remove the per-user toggle override only if you own this setting.')) { + exit 2 + } + $key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($path, $true) + if ($null -eq $key) { Write-Host 'SKIPPED: The Outlook options key does not exist.'; exit 0 } + try { + if ($null -eq $key.GetValue($name)) { Write-Host 'SKIPPED: The toggle override was already absent.'; exit 0 } + $key.DeleteValue($name, $false) + } + finally { $key.Close() } + Write-Host 'SUCCESS: Removed the current-user toggle override.' + exit 0 + } + $key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey($path) + if ($null -eq $key) { throw 'Could not open the per-user Outlook options key.' } + try { $key.SetValue($name, 1, [Microsoft.Win32.RegistryValueKind]::DWord) } + finally { $key.Close() } + Write-Host 'SUCCESS: The New Outlook switch is hidden for the current account.' + exit 0 +} +catch { + Write-Host "FAILED: $($_.Exception.Message)" + exit 1 +} diff --git a/tests/test_scripts.py b/tests/test_scripts.py new file mode 100644 index 0000000..40a4f8f --- /dev/null +++ b/tests/test_scripts.py @@ -0,0 +1,77 @@ +"""Non-destructive safeguards for the CMD entry points and PowerShell helpers.""" + +from pathlib import Path +import re +import unittest + +ROOT = Path(__file__).resolve().parents[1] +ENTRY_POINTS = ( + "active-directory/launch-ad-console-as-user.cmd", + "adobe-acrobat/disable-javascript.cmd", + "ms-teams/clear-teams-login.cmd", + "ms-teams/full-clear-teams-cache.cmd", + "onedrive/disable-onedrive.cmd", + "outlook/fix-outlook-crashes-june-2025-update.cmd", + "outlook/hide-new-outlook-button.cmd", + "windows/remove-bloatware.cmd", +) + + +def content(path): + return (ROOT / path).read_text(encoding="utf-8") + + +class ScriptSafetyTests(unittest.TestCase): + def test_all_entry_points_are_preview_first(self): + for path in ENTRY_POINTS: + with self.subTest(path=path): + script = content(path).lower() + self.assertIn('"%~1"==""', script) + self.assertIn('"/preview"', script) + self.assertIn('"/apply"', script) + self.assertIn('"/help"', script) + self.assertIn("exit /b", script) + + def test_no_shared_microsoft_identity_store_deletion(self): + for path in ( + "ms-teams/clear-teams-login.cmd", + "ms-teams/full-clear-teams-cache.cmd", + "scripts/Reset-TeamsCache.ps1", + ): + with self.subTest(path=path): + self.assertIsNone( + re.search(r"OneAuth|TokenBroker|IdentityCache|AAD[.]BrokerPlugin", content(path), re.I) + ) + + def test_onedrive_only_disables_policy_and_never_removes_user_data(self): + script = content("scripts/Disable-OneDrive.ps1") + self.assertIn("DisableFileSyncNGSC", script) + self.assertIsNone(re.search(r"\bDisableFileSync\b", script)) + self.assertNotIn("Remove-Item", script) + self.assertNotIn("OneDriveTemp", script) + + def test_bloatware_uses_exact_allowlist(self): + script = content("scripts/Remove-Bloatware.ps1") + self.assertIn("$packageNames -contains $_.Name", script) + self.assertNotIn(" -AllUsers", script) + self.assertNotIn(" -match ", script) + self.assertNotIn("*Xbox*", script) + self.assertIn("Confirm-Token -Token 'REMOVE'", script) + + def test_confirmation_and_path_guard_exist(self): + common = content("scripts/Common.ps1") + self.assertIn("function Confirm-Token", common) + self.assertIn("function Assert-SafeDeleteTarget", common) + self.assertIn("ReparsePoint", common) + self.assertIn("Confirm-Token -Token 'RESET'", content("scripts/Reset-TeamsCache.ps1")) + self.assertIn("Confirm-Token -Token 'DISABLE'", content("scripts/Disable-OneDrive.ps1")) + + def test_no_ci_apply_or_restore(self): + workflow = content(".github/workflows/script-checks.yml") + self.assertIn("Windows script safety checks", workflow) + self.assertNotIn("/apply", workflow.lower()) + self.assertNotIn("/restore", workflow.lower()) + + +if __name__ == "__main__": + unittest.main() diff --git a/windows/remove-bloatware.cmd b/windows/remove-bloatware.cmd index 282b217..869c9a8 100644 --- a/windows/remove-bloatware.cmd +++ b/windows/remove-bloatware.cmd @@ -1,35 +1,26 @@ -:: ============================================= -:: Remove & Block Default Windows Bloatware -:: ============================================= -:: This script removes common pre-installed apps from Windows -:: and blocks them from being reinstalled automatically via the Store. -:: -:: Requires: Run as administrator -:: @echo off -title Windows Bloatware Removal Script -setlocal EnableDelayedExpansion - -:: Check for admin rights -openfiles >nul 2>&1 -if %errorlevel% NEQ 0 ( - echo This script must be run as administrator. - pause - exit /b +setlocal +if not "%~2"=="" goto usage +set "apply=" +if "%~1"=="" goto run +if /i "%~1"=="/preview" goto run +if /i "%~1"=="/apply" ( + set "apply=-Apply" + goto run ) - -echo Removing removable apps... -set apps=3DBuilder ZuneMusic ZuneVideo BingWeather WindowsMaps SolitaireCollection CandyCrush Xbox GetHelp FeedbackHub Paint3D MixedReality - -for %%A in (%apps%) do ( - echo Trying to remove: %%A - powershell -Command "Get-AppxPackage *%%A* | Remove-AppxPackage -ErrorAction SilentlyContinue" -) - -echo. -echo Blocking future installations... -powershell -Command "Get-AppxProvisionedPackage -Online | Where-Object { $_.DisplayName -match '3DBuilder|ZuneMusic|ZuneVideo|BingWeather|WindowsMaps|SolitaireCollection|CandyCrush|Xbox|GetHelp|FeedbackHub|Paint3D|MixedReality' } | Remove-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue" - -echo. -echo Bloatware removal and blocking complete. -pause +if /i "%~1"=="/help" goto help +goto usage +:run +set "ps=%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" +if exist "%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" set "ps=%SystemRoot%\Sysnative\WindowsPowerShell\v1.0\powershell.exe" +"%ps%" -NoProfile -ExecutionPolicy Bypass -File "%~dp0..\scripts\Remove-Bloatware.ps1" %apply% +exit /b %errorlevel% +:help +echo Preview and optionally remove an exact allowlist of Windows Appx packages. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +echo Default: preview only; /apply may require interactive confirmation. +exit /b 0 +:usage +echo ERROR: Invalid arguments. +echo Usage: %~nx0 [/preview ^| /apply ^| /help] +exit /b 2