Add new features: DefaultBrowser, DynamicLock, SandwichReminder
- Implemented DefaultBrowser feature to notify users when the default browser does not match the configured app. - Added DynamicLock feature to disable Dynamic Lock while connected to a specific network and re-enable it after disconnecting. - Created SandwichReminder feature to prompt users to order a sandwich during work hours based on network and time settings. Introduced helper libraries for configuration, elevation, logging, network utilities, and toast notifications. - Config.ps1: Added functions for reading and writing configuration and state files. - Elevation.ps1: Added functions to check for administrator privileges and request elevation. - Logging.ps1: Implemented a shared logging utility for consistent logging across features. - NetworkUtils.ps1: Added a function to check for DNS suffix connectivity. - ToastHelper.ps1: Created a helper for displaying Windows toast notifications. Implemented runner.ps1 as the main entry point for executing features based on configuration.
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
# PowerShell Automation Center
|
||||
|
||||
A unified framework for managing Windows automation tasks with a modular, extensible feature system. All configuration and execution flows through a single entry point: `configure.ps1`.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```powershell
|
||||
# Navigate to the root folder
|
||||
cd C:\Tools\ArnePowershellAutomation
|
||||
|
||||
# Run the main menu
|
||||
.\configure.ps1
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Entry Point: `configure.ps1`
|
||||
The only file you interact with directly. Provides a menu-driven interface for:
|
||||
- **Check**: View registration status of the background runner task
|
||||
- **Register**: Create a scheduled task that runs automation features at logon and every 2 minutes
|
||||
- **Remove**: Unregister the scheduled task
|
||||
- **Configure**: Adjust per-feature settings (toggles, custom values)
|
||||
|
||||
### Core Structure
|
||||
```
|
||||
ArnePowershellAutomation/
|
||||
├── configure.ps1 # Single user entry point
|
||||
└── internal/
|
||||
├── runner.ps1 # Scheduled task entry point (runs every 2 min + at logon)
|
||||
├── lib/ # Shared utilities
|
||||
│ ├── Logging.ps1 # Centralized logging with rotation
|
||||
│ ├── Config.ps1 # Configuration and state I/O
|
||||
│ ├── Elevation.ps1 # UAC elevation helper
|
||||
│ ├── NetworkUtils.ps1 # DNS suffix detection
|
||||
│ └── ToastHelper.ps1 # Windows 11 toast notifications
|
||||
├── features/ # Automation feature modules
|
||||
│ ├── DynamicLock.ps1 # Toggle Dynamic Lock on network presence
|
||||
│ ├── DefaultBrowser.ps1 # Monitor default browser setting
|
||||
│ └── SandwichReminder.ps1 # Time-based reminder notifications
|
||||
└── data/
|
||||
├── config.json # Feature toggles & settings (auto-seeded)
|
||||
├── state/
|
||||
│ └── state.json # Per-feature runtime state
|
||||
└── logs/
|
||||
├── automation-*.log # Daily rotating logs (7-day retention)
|
||||
├── elevated-process.log # Elevation diagnostics
|
||||
├── last-register-result.json # Last registration result
|
||||
└── last-remove-result.json # Last removal result
|
||||
```
|
||||
|
||||
### Execution Flow
|
||||
|
||||
**At Logon / Every 2 Minutes:**
|
||||
1. Scheduled task launches `internal/runner.ps1`
|
||||
2. Runner loads all lib utilities and feature modules
|
||||
3. For each enabled feature:
|
||||
- Reads configuration from `internal/data/config.json`
|
||||
- Reads previous state from `internal/data/state/state.json`
|
||||
- Calls `Invoke-Feature` function
|
||||
- Captures errors; one feature failure doesn't break others
|
||||
- Saves updated state back to file
|
||||
4. Logs all activity to `internal/data/logs/automation-YYYY-MM-DD.log`
|
||||
|
||||
**For Configuration Changes:**
|
||||
1. User selects menu option 4 (Configure)
|
||||
2. Navigate feature menu (numbers to toggle, C<number> for sub-menu)
|
||||
3. For each setting, enter value or accept default
|
||||
4. Config is saved to `internal/data/config.json` immediately
|
||||
5. Changes take effect on next scheduled task run
|
||||
|
||||
## Feature Contract
|
||||
|
||||
Each feature in `internal/features/` must export:
|
||||
|
||||
### `$FeatureMeta` Variable
|
||||
```powershell
|
||||
$FeatureMeta = @{
|
||||
Name = "My Feature"
|
||||
Description = "What this feature does"
|
||||
Settings = @(
|
||||
@{ Key = "settingKey1"; Label = "Display Label"; Type = "string"; Default = "value"; Description = "Help text" }
|
||||
@{ Key = "settingKey2"; Label = "Port"; Type = "int"; Default = 8080; Description = "Port number" }
|
||||
@{ Key = "settingKey3"; Label = "Enabled"; Type = "bool"; Default = $true; Description = "Enable this?" }
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### `Invoke-Feature` Function
|
||||
```powershell
|
||||
function Invoke-Feature {
|
||||
param(
|
||||
[Parameter(Mandatory)][hashtable]$Config, # Feature settings from config.json
|
||||
[Parameter(Mandatory)][hashtable]$State # Feature state from state.json
|
||||
)
|
||||
|
||||
# Perform work here
|
||||
# Use Write-Log -Feature "MyFeature" for logging
|
||||
|
||||
# Return updated $State hashtable
|
||||
return $State
|
||||
}
|
||||
```
|
||||
|
||||
### Built-in Utilities Available to Features
|
||||
- **Logging.ps1**: `Write-Log -Level (Info|Warn|Error) -Message -Feature`
|
||||
- **Config.ps1**: `Get-Config`, `Save-Config`, `Get-State`, `Save-State`
|
||||
- **NetworkUtils.ps1**: `Test-DnsSuffixConnected -Suffix`
|
||||
- **ToastHelper.ps1**: `Show-ToastNotification -Title -Body -Buttons`
|
||||
- **Elevation.ps1**: `Test-Administrator`, `Request-Elevation`
|
||||
|
||||
## Included Features
|
||||
|
||||
### DynamicLock
|
||||
Enables/disables Windows Dynamic Lock based on network connectivity.
|
||||
- **Settings**: Network domain suffix (e.g., "example.com"), revert timeout in minutes
|
||||
- **Logic**: When connected to configured network, enables Dynamic Lock (`EnableGoodbye`=1); when disconnected, reverts after timeout
|
||||
- **State Tracked**: Current connection status, last connected timestamp
|
||||
|
||||
### DefaultBrowser
|
||||
Monitors if your default browser matches expected ProgId (app identifier).
|
||||
- **Settings**: Target ProgId (e.g., "FirefoxURL", "OperaGXStable")
|
||||
- **Logic**: On each run, reads current ProgId; if mismatch, shows toast with link to settings; max once per day
|
||||
- **State Tracked**: Last notification date to avoid spam
|
||||
|
||||
### SandwichReminder
|
||||
Shows a toast reminder at a specific time on a specific network.
|
||||
- **Settings**: Reminder time (HH:MM), network domain suffix, reminder URL, notification window (±5 minutes)
|
||||
- **Logic**: If on configured network AND current time is within ±5 min of reminder time AND not shown today, display toast with link
|
||||
- **State Tracked**: Last notification date
|
||||
|
||||
## Usage Guide
|
||||
|
||||
### Main Menu
|
||||
```
|
||||
1) Check Registration Status
|
||||
→ Displays current task registration, last run time, next run time
|
||||
|
||||
2) Register Runner
|
||||
→ Creates scheduled task (AtLogon trigger + 2-min repetition)
|
||||
→ If not admin, prompts for UAC elevation
|
||||
→ Updates internal/data/logs/last-register-result.json
|
||||
|
||||
3) Remove Runner
|
||||
→ Unregisters the scheduled task
|
||||
→ If not admin, prompts for UAC elevation
|
||||
→ Updates internal/data/logs/last-remove-result.json
|
||||
|
||||
4) Configure Features
|
||||
→ Submenu with feature toggles and per-feature settings
|
||||
→ Enter number to toggle (on/off)
|
||||
→ Enter C<number> to configure settings
|
||||
→ Q to return to main menu
|
||||
→ Saves config.json immediately
|
||||
|
||||
Q) Quit
|
||||
→ Exits configure.ps1
|
||||
```
|
||||
|
||||
### Configuring a Feature
|
||||
When you select a feature for configuration, you'll be prompted for each setting:
|
||||
- **Type: string** → Enter any text
|
||||
- **Type: int** → Enter a number
|
||||
- **Type: bool** → Enter Y/N (yes/no)
|
||||
- **Type: time** → Enter HH:MM format
|
||||
- Press Enter to accept the displayed default
|
||||
|
||||
## Testing Plan
|
||||
|
||||
### T1 — Basic Logging & Config Load
|
||||
- [ ] Run `.\configure.ps1` → Main menu displays
|
||||
- [ ] Verify `internal/data/config.json` was created (if first run)
|
||||
- [ ] Verify `internal/data/logs/automation-<date>.log` has initialization entries
|
||||
|
||||
### T2 — Feature Discovery & Configuration Menu
|
||||
- [ ] Select menu option 4 (Configure)
|
||||
- [ ] Verify all three features appear (DynamicLock, DefaultBrowser, SandwichReminder)
|
||||
- [ ] Toggle DynamicLock (1) → Verify enabled flag flips in config.json
|
||||
- [ ] Select C1 (configure DynamicLock) → Modify network suffix, save, verify in config.json
|
||||
|
||||
### T3 — Registration & Task Scheduler
|
||||
- [ ] Select menu option 2 (Register)
|
||||
- [ ] If prompted for UAC, approve elevation
|
||||
- [ ] Verify success message and `last-register-result.json` shows success=true
|
||||
- [ ] Open Task Scheduler → Navigate to Library root → Verify "PSAutomation-Runner" task exists
|
||||
- [ ] Right-click task → Properties → Verify "Run as current user" (not elevated), AtLogOn trigger, 2-min repetition
|
||||
|
||||
### T4 — Feature Execution (DynamicLock)
|
||||
- [ ] Register the runner (option 2)
|
||||
- [ ] Enable DynamicLock in configuration menu (option 4)
|
||||
- [ ] Configure DynamicLock network to current network suffix (use `Get-DnsClient` or `ipconfig /all` to find)
|
||||
- [ ] Wait for runner to execute (at next 2-min interval or logon event)
|
||||
- [ ] Check `internal/data/logs/automation-<date>.log` for "DynamicLock" entries
|
||||
- [ ] Verify state.json has updated `isConnected` flag
|
||||
|
||||
### T5 — Feature Execution (DefaultBrowser)
|
||||
- [ ] Enable DefaultBrowser in configuration menu
|
||||
- [ ] Verify toast notification appears (if configured ProgId differs from current default)
|
||||
- [ ] Click "Yes" button on toast → Should open Settings → Default apps
|
||||
- [ ] Check logs for "DefaultBrowser" entries and state.json for `lastShownDate`
|
||||
|
||||
### T6 — Feature Execution (SandwichReminder)
|
||||
- [ ] Enable SandwichReminder in configuration menu
|
||||
- [ ] Configure reminderTime to a time 1–2 minutes in the future (e.g., if current time is 14:35, set to 14:37)
|
||||
- [ ] Configure to current network suffix
|
||||
- [ ] Wait for runner to execute
|
||||
- [ ] Verify toast appears with configured URL
|
||||
- [ ] Check logs for "SandwichReminder" entries and state.json for `lastShownDate`
|
||||
|
||||
### T7 — Error Isolation
|
||||
- [ ] Intentionally break one feature (e.g., edit `internal/features/DynamicLock.ps1`, add invalid syntax)
|
||||
- [ ] Wait for runner to execute
|
||||
- [ ] Verify broken feature logged an error in automation-<date>.log
|
||||
- [ ] Verify other features still executed successfully (logged their own entries)
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- **Default Browser Registry Protection**: On Windows 11, Microsoft protects the default browser registry key with a hash. The DefaultBrowser feature reads the ProgId but cannot *set* the default browser programmatically. The toast provides a link to Settings for manual override.
|
||||
- **Windows 11 Only for Toasts**: Toast notifications require WinRT (Windows 10+), but full Windows 11 support is assumed. Win10 may work but is untested.
|
||||
- **Scheduled Task Security**: The runner task executes as the current user with Limited RunLevel (no elevation). Features cannot perform system-wide administrative tasks; they're limited to user-level operations.
|
||||
- **DNS Suffix Matching**: Network detection relies on `Get-DnsClient` adapter DNS suffix. Make sure your network adapter is configured with the correct suffix for accurate detection.
|
||||
|
||||
## Extending with New Features
|
||||
|
||||
To add a new automation feature:
|
||||
|
||||
1. Create `internal/features/MyFeature.ps1`:
|
||||
```powershell
|
||||
#Requires -Version 5.1
|
||||
param() # Empty; loaded via dot-source
|
||||
|
||||
$FeatureMeta = @{
|
||||
Name = "My Feature"
|
||||
Description = "Does something cool"
|
||||
Settings = @(
|
||||
@{ Key = "enabled"; Label = "Enable"; Type = "bool"; Default = $true; Description = "Turn on/off" }
|
||||
)
|
||||
}
|
||||
|
||||
function Invoke-Feature {
|
||||
param([hashtable]$Config, [hashtable]$State)
|
||||
|
||||
if (-not $Config.enabled) { return $State }
|
||||
|
||||
try {
|
||||
Write-Log -Level Info -Message "Running My Feature" -Feature "MyFeature"
|
||||
# Do work here
|
||||
}
|
||||
catch {
|
||||
Write-Log -Level Error -Message "Error: $_" -Feature "MyFeature"
|
||||
}
|
||||
|
||||
return $State
|
||||
}
|
||||
```
|
||||
|
||||
2. Drop it in `internal/features/` — it will auto-discover on next configure.ps1 run
|
||||
|
||||
3. Run `.\configure.ps1` → menu option 4 → Your feature appears with toggles and config sub-menu
|
||||
|
||||
## Logging
|
||||
|
||||
All activity is logged to `internal/data/logs/automation-YYYY-MM-DD.log` (UTC date).
|
||||
- **Retention**: 7 days (older logs auto-deleted)
|
||||
- **Format**: `[YYYY-MM-DD HH:mm:ss] [LEVEL] [Feature] Message`
|
||||
- **Levels**: Info, Warn, Error
|
||||
|
||||
Elevation diagnostics go to `internal/data/logs/elevated-process.log` for troubleshooting registration issues.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Task not running?**
|
||||
- Check Task Scheduler (root library) for "PSAutomation-Runner" task
|
||||
- Verify "Run as current user" and Limited RunLevel
|
||||
- Check `automation-<date>.log` for errors
|
||||
|
||||
**Features not executing?**
|
||||
- Verify feature is enabled in menu option 4
|
||||
- Check `internal/data/config.json` for `"enabled": true` on that feature
|
||||
- Look for errors in `automation-<date>.log` with feature name
|
||||
|
||||
**Elevation prompts failing?**
|
||||
- Check `elevated-process.log` for bootstrap diagnostics
|
||||
- Ensure PowerShell isn't blocked by execution policy: `Get-ExecutionPolicy`
|
||||
- If needed, set: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
|
||||
|
||||
## License
|
||||
|
||||
None. Use freely.
|
||||
Reference in New Issue
Block a user