Add fixnetworkshare, winrm-setup-package, udc remote-execution suites
- NetworkDriveManager.ps1: S: drive repair utility - winrm-setup-package: Invoke-RemoteTask helper + Setup-WinRM.bat + HTML guide - remote-execution/udc: UDC_Update.ps1 and batch wrappers for updating DNC controllers on shop-floor PCs - Invoke-RemoteMaintenance.ps1: substantial rework (~1650 lines) - Schedule-Maintenance and complete-asset minor updates - Bump edncfix gitlink to v1.6.0 (2748bfa) - .gitignore: block inventory.csv/xlsx (CUI) and logs_*.txt (per-host logs) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ Remote maintenance toolkit for executing maintenance tasks on shopfloor PCs via
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [First-Time Setup](#first-time-setup)
|
||||
- [API Integration](#api-integration)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Start](#quick-start)
|
||||
@@ -17,6 +18,8 @@ Remote maintenance toolkit for executing maintenance tasks on shopfloor PCs via
|
||||
- [Service Management](#how-to-manage-services)
|
||||
- [Time Synchronization](#how-to-fix-time-sync-issues)
|
||||
- [DNC Configuration](#how-to-update-dnc-configurations)
|
||||
- [File Deployment](#how-to-deploy-files)
|
||||
- [Registry Import](#how-to-import-registry-files)
|
||||
- [Software Deployment](#how-to-deploy-software)
|
||||
- [Batch Operations](#how-to-run-batch-operations)
|
||||
- [Targeting Strategies](#targeting-strategies)
|
||||
@@ -32,13 +35,53 @@ This script provides a comprehensive remote maintenance toolkit for managing sho
|
||||
**Location:** `S:\dt\shopfloor\scripts\remote-execution\Invoke-RemoteMaintenance.ps1`
|
||||
|
||||
**Key Features:**
|
||||
- 19 maintenance tasks available
|
||||
- 22 maintenance tasks available
|
||||
- General-purpose file deployment (`CopyFile`) and registry import (`ImportReg`) tasks
|
||||
- Registry imports run as logged-in user via scheduled task (HKCU support)
|
||||
- Optional post-copy commands via scheduled task (service/app restarts)
|
||||
- Multiple targeting options (by name, type, business unit, or all)
|
||||
- Concurrent execution with configurable throttling
|
||||
- Integration with ShopDB for PC discovery
|
||||
|
||||
---
|
||||
|
||||
## First-Time Setup
|
||||
|
||||
Before running this script for the first time, you must allow PowerShell script execution and unblock the script file.
|
||||
|
||||
### Step 1: Allow PowerShell Script Execution
|
||||
|
||||
Open PowerShell **as Administrator** and run:
|
||||
|
||||
```powershell
|
||||
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
|
||||
When prompted, type `Y` and press Enter. This allows locally-created scripts to run and requires downloaded scripts to be signed or unblocked.
|
||||
|
||||
> **Note:** You only need to do this once per user account. `RemoteSigned` is the recommended policy — it allows local scripts while still protecting against untrusted downloads.
|
||||
|
||||
### Step 2: Unblock the Script File
|
||||
|
||||
Files downloaded from a network share or the internet are marked as "blocked" by Windows. You must unblock the script before it can run.
|
||||
|
||||
**Option A — File Explorer (GUI):**
|
||||
|
||||
1. Navigate to `S:\dt\shopfloor\scripts\remote-execution\`
|
||||
2. Right-click `Invoke-RemoteMaintenance.ps1` and select **Properties**
|
||||
3. At the bottom of the General tab, check the **Unblock** checkbox
|
||||
4. Click **Apply**, then **OK**
|
||||
|
||||
**Option B — PowerShell:**
|
||||
|
||||
```powershell
|
||||
Unblock-File -Path "S:\dt\shopfloor\scripts\remote-execution\Invoke-RemoteMaintenance.ps1"
|
||||
```
|
||||
|
||||
> **Important:** If you skip this step, you will get a security error when trying to run the script: *"cannot be loaded because running scripts is disabled on this system"* or *"cannot be loaded. The file is not digitally signed."*
|
||||
|
||||
---
|
||||
|
||||
## API Integration
|
||||
|
||||
When using `-All`, `-PcType`, or `-BusinessUnit` targeting, the script retrieves PC lists from the ShopDB API:
|
||||
@@ -53,12 +96,12 @@ GET /api.asp?action=getShopfloorPCs&businessunitid=1 # Specific business unit
|
||||
|
||||
| ID | Type | ID | Type |
|
||||
|----|------|----|------|
|
||||
| 1 | Shopfloor | 7 | Heat Treat |
|
||||
| 2 | CMM | 8 | Engineer |
|
||||
| 3 | Wax Trace | 9 | Standard |
|
||||
| 4 | Keyence | 10 | Inspection |
|
||||
| 5 | EAS1000 | 11 | Dashboard |
|
||||
| 6 | Genspect | 12 | Lobby Display |
|
||||
| 1 | Standard | 7 | Keyence |
|
||||
| 2 | Engineer | 8 | Genspect |
|
||||
| 3 | Shopfloor | 9 | Heat Treat |
|
||||
| 4 | Uncategorized | 10 | Inspection |
|
||||
| 5 | CMM | 11 | Dashboard |
|
||||
| 6 | Wax / Trace | 12 | Lobby Display |
|
||||
|
||||
**See:** [ShopDB API Reference](ShopDB-API.html) for complete API documentation.
|
||||
|
||||
@@ -133,6 +176,15 @@ The script outputs status for each PC:
|
||||
|-----------|------|-------------|
|
||||
| `-Task` | string | Maintenance task to execute |
|
||||
|
||||
### File Deployment Parameters (CopyFile / ImportReg)
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `-SourcePath` | string | Source file path (local or UNC). Required for CopyFile and ImportReg |
|
||||
| `-DestinationPath` | string | Destination file path on remote PCs. Required for CopyFile |
|
||||
| `-RunCommand` | string | Command to run after CopyFile. Runs as logged-in user by default (via scheduled task) |
|
||||
| `-AsSystem` | switch | Run ImportReg and -RunCommand in the WinRM session (SYSTEM context) instead of as logged-in user |
|
||||
|
||||
### Optional Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
@@ -140,7 +192,8 @@ The script outputs status for each PC:
|
||||
| `-Credential` | PSCredential | Prompt | Remote authentication |
|
||||
| `-ApiUrl` | string | Production | ShopDB API endpoint |
|
||||
| `-ThrottleLimit` | int | 5 | Max concurrent sessions |
|
||||
| `-DnsSuffix` | string | logon.ds.ge.com | DNS suffix for resolution |
|
||||
| `-DnsSuffix` | string | logon.ds.ge.com | DNS suffix for FQDN resolution |
|
||||
| `-LogFile` | switch | Off | Enable transcript logging to `logs/` directory |
|
||||
|
||||
### PC Types
|
||||
|
||||
@@ -194,13 +247,22 @@ TBD, Blisk, HPT, Spools, Inspection, Venture, Turn/Burn, DT
|
||||
|
||||
| Task | Description | Duration | Impact |
|
||||
|------|-------------|----------|--------|
|
||||
| `UpdateEMxAuthToken` | Update eMx auth from share | 1-2 min | None |
|
||||
| `DeployUDCWebServerConfig` | Deploy UDC config | 1-2 min | None |
|
||||
| `UpdateDNCMXHosts` | Update FtpHostPrimary/Secondary in DNC\MX registry | <1 min | None |
|
||||
| `AuditDNCConfig` | Compare DNC registry vs UDC backup JSON, export CSV | 1-5 min | None |
|
||||
| `CheckDefectTracker` | Check if Defect_Tracker.exe is running, export CSV | 1-5 min | None |
|
||||
|
||||
### File Deployment Tasks
|
||||
|
||||
| Task | Description | Duration | Impact |
|
||||
|------|-------------|----------|--------|
|
||||
| `CopyFile` | Copy file from `-SourcePath` to `-DestinationPath` on remote PCs | 1-2 min | Low |
|
||||
| `ImportReg` | Copy `.reg` file and import via scheduled task as logged-in user | 1-2 min | Low |
|
||||
|
||||
### System Tasks
|
||||
|
||||
| Task | Description | Duration | Impact |
|
||||
|------|-------------|----------|--------|
|
||||
| `GPUpdate` | Force Group Policy refresh (`gpupdate /force`) | <1 min | Low |
|
||||
| `Reboot` | Restart PC (30s delay) | 2-5 min | High |
|
||||
|
||||
### Software Deployment Tasks
|
||||
@@ -222,18 +284,19 @@ Deployment tasks require source files to be available before execution:
|
||||
|
||||
| Task | Source File Path |
|
||||
|------|------------------|
|
||||
| `InstallDashboard` | `\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\Dashboard\GEAerospaceDashboardSetup.exe` |
|
||||
| `InstallLobbyDisplay` | `\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\LobbyDisplay\GEAerospaceLobbyDisplaySetup.exe` |
|
||||
| `UpdateEMxAuthToken` | `\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\eMx\eMxInfo.txt` |
|
||||
| `DeployUDCWebServerConfig` | `\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\UDC\udc_webserver_settings.json` |
|
||||
| `InstallDashboard` | `\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\scripts\Dashboard\GEAerospaceDashboardSetup.exe` |
|
||||
| `InstallLobbyDisplay` | `\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\scripts\LobbyDisplay\GEAerospaceLobbyDisplaySetup.exe` |
|
||||
| `CopyFile` | Any file - specified via `-SourcePath` parameter |
|
||||
| `ImportReg` | Any `.reg` file - specified via `-SourcePath` parameter |
|
||||
|
||||
### How Deployment Works
|
||||
|
||||
1. **Pre-flight Check:** Script verifies source file exists
|
||||
2. **WinRM Session:** Opens remote session to target PC
|
||||
3. **File Push:** Copies source file to `C:\Windows\Temp\` on remote PC
|
||||
4. **Execution:** Runs install/copy task using pushed file
|
||||
5. **Cleanup:** Removes temp file from remote PC
|
||||
4. **Execution:** Runs install/copy/import task using pushed file
|
||||
5. **Post-action:** Optionally runs command as logged-in user via scheduled task
|
||||
6. **Cleanup:** Removes temp file from remote PC
|
||||
|
||||
```
|
||||
+---------------------+ WinRM +---------------------+
|
||||
@@ -242,50 +305,44 @@ Deployment tasks require source files to be available before execution:
|
||||
| Source Files: | Push File | Temp Location: |
|
||||
| - Setup.exe | ------------> | C:\Windows\Temp |
|
||||
| - config.json | | |
|
||||
| - eMxInfo.txt | Execute | Final Location: |
|
||||
| (network) | ------------> | C:\Program Files |
|
||||
| - settings.reg | Execute | Final Location: |
|
||||
| (any path/UNC) | ------------> | -DestinationPath |
|
||||
| | | |
|
||||
| | Sched. Task | Logged-in User: |
|
||||
| | ------------> | - regedit /s |
|
||||
| | | - RunCommand |
|
||||
+---------------------+ +---------------------+
|
||||
```
|
||||
|
||||
### Directory Structure
|
||||
### CopyFile Details
|
||||
|
||||
Ensure your script directory contains the required files:
|
||||
The `CopyFile` task:
|
||||
|
||||
```
|
||||
S:\dt\shopfloor\scripts\remote-execution\
|
||||
├── Invoke-RemoteMaintenance.ps1
|
||||
├── GEAerospaceDashboardSetup.exe # For InstallDashboard
|
||||
├── GEAerospaceLobbyDisplaySetup.exe # For InstallLobbyDisplay
|
||||
└── udc_webserver_settings.json # For DeployUDCWebServerConfig
|
||||
```
|
||||
1. **Source:** Any file specified via `-SourcePath` (local or UNC path)
|
||||
2. **Destination:** Specified via `-DestinationPath`
|
||||
3. **Backup:** Existing file is backed up as `<name>-old-<timestamp>.<ext>`
|
||||
4. **Post-action:** If `-RunCommand` is specified, runs as the logged-in user via a one-shot scheduled task (same pattern as Dashboard/Lobby kiosk relaunch)
|
||||
|
||||
### eMx Auth Token Details
|
||||
### ImportReg Details
|
||||
|
||||
The `UpdateEMxAuthToken` task:
|
||||
The `ImportReg` task:
|
||||
|
||||
1. **Source:** `\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\eMx\eMxInfo.txt`
|
||||
2. **Destinations:** (both paths if they exist)
|
||||
- `C:\Program Files\GE Aircraft Engines\DNC\eMxInfo.txt`
|
||||
- `C:\Program Files (x86)\GE Aircraft Engines\DNC\eMxInfo.txt`
|
||||
3. **Backup:** Creates `eMxInfo-old-YYYYMMDD-HHMMSS.txt` before overwriting
|
||||
4. **Post-action:** Restarts DNC service (`LDnc.exe`)
|
||||
|
||||
### UDC Web Server Config Details
|
||||
|
||||
The `DeployUDCWebServerConfig` task:
|
||||
|
||||
1. **Pre-check:** Verifies UDC is installed (`C:\Program Files\UDC` exists)
|
||||
2. **Skip:** PCs without UDC are skipped (not counted as failures)
|
||||
3. **Destination:** `C:\ProgramData\UDC\udc_webserver_settings.json`
|
||||
4. **Backup:** Creates backup before overwriting
|
||||
1. **Source:** `.reg` file specified via `-SourcePath`
|
||||
2. **Import method:** `regedit.exe /s` via one-shot scheduled task as logged-in user
|
||||
3. **HKCU support:** Runs as the logged-in user, so both HKLM and HKCU keys apply correctly
|
||||
4. **Fallback:** If no user is logged in, runs `regedit.exe /s` directly (HKLM only)
|
||||
5. **Cleanup:** Temp `.reg` file removed after import
|
||||
|
||||
### Dashboard/Lobby Display Install Details
|
||||
|
||||
Both kiosk app installers:
|
||||
|
||||
1. **Installer type:** Inno Setup (supports `/VERYSILENT`)
|
||||
2. **Execution:** Silent install with no user prompts
|
||||
3. **Cleanup:** Installer removed from temp after execution
|
||||
2. **Pre-install:** Kills running Edge kiosk via `PrepareToInstall` in Inno Setup
|
||||
3. **Execution:** Silent install with no user prompts (120-second timeout)
|
||||
4. **Post-install:** Creates a one-shot scheduled task to relaunch Edge in kiosk mode as the logged-in user (e.g. `lg044513sd`), then auto-deletes the task
|
||||
5. **Cleanup:** Installer removed from temp after execution
|
||||
6. **Connectivity:** Offline PCs are skipped with a ping check before connecting
|
||||
|
||||
**Uninstall GUIDs:**
|
||||
- Dashboard: `{9D9EEE25-4D24-422D-98AF-2ADEDA4745ED}`
|
||||
@@ -295,7 +352,7 @@ Both kiosk app installers:
|
||||
|
||||
To add a new application for deployment, edit the script in two places:
|
||||
|
||||
**Step 1: Add to `$KioskAppConfig` hashtable (~line 1388)**
|
||||
**Step 1: Add to `$KioskAppConfig` hashtable**
|
||||
|
||||
```powershell
|
||||
$KioskAppConfig = @{
|
||||
@@ -304,10 +361,11 @@ $KioskAppConfig = @{
|
||||
# Add new application
|
||||
'InstallNewApp' = @{
|
||||
Action = 'Install'
|
||||
InstallerPath = '\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\NewApp\NewAppSetup.exe'
|
||||
InstallerPath = '\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\scripts\NewApp\NewAppSetup.exe'
|
||||
InstallerName = 'NewAppSetup.exe'
|
||||
AppName = 'New Application Name'
|
||||
UninstallGuid = '{YOUR-GUID-HERE}' # Find in registry after manual install
|
||||
KioskUrl = 'https://tsgwp00525.wjs.geaerospace.net/shopdb/your-page/' # Optional: relaunch Edge kiosk after install
|
||||
}
|
||||
'UninstallNewApp' = @{
|
||||
Action = 'Uninstall'
|
||||
@@ -334,7 +392,7 @@ $KioskAppConfig = @{
|
||||
**Step 3: Place installer on network share**
|
||||
|
||||
```
|
||||
\\tsgwp00525.wjs.geaerospace.net\dt\shopfloor\scripts\NewApp\NewAppSetup.exe
|
||||
\\tsgwp00525.wjs.geaerospace.net\shared\dt\shopfloor\scripts\NewApp\NewAppSetup.exe
|
||||
```
|
||||
|
||||
**Finding the Uninstall GUID:**
|
||||
@@ -562,37 +620,129 @@ wuauclt /detectnow
|
||||
|
||||
### How to Update DNC Configurations
|
||||
|
||||
#### Update eMx Authentication Token
|
||||
#### Update DNC MX Hosts
|
||||
|
||||
**Scenario:** eMx authentication is failing on DNC PCs.
|
||||
**Scenario:** FtpHostPrimary/FtpHostSecondary in DNC\MX registry needs updating (hostname migration).
|
||||
|
||||
```powershell
|
||||
# Single PC
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "DNC-PC01" -Task UpdateEMxAuthToken -Credential $cred
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "DNC-PC01" -Task UpdateDNCMXHosts -Credential $cred
|
||||
|
||||
# All shopfloor PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -All -Task UpdateEMxAuthToken -Credential $cred
|
||||
.\Invoke-RemoteMaintenance.ps1 -All -Task UpdateDNCMXHosts -Credential $cred
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
1. Backs up existing `eMxInfo.txt` with timestamp
|
||||
2. Copies new token file from network share
|
||||
3. Verifies file was updated
|
||||
1. Checks both 32-bit (WOW6432Node) and 64-bit registry paths
|
||||
2. Only updates values matching the old hostname - skips unexpected values
|
||||
3. Safe to run on all PCs: no-ops if DNC\MX key doesn't exist
|
||||
|
||||
#### Deploy UDC Web Server Configuration
|
||||
#### Audit DNC Config vs UDC Backup
|
||||
|
||||
**Scenario:** UDC web server settings need to be updated.
|
||||
**Scenario:** Verify DNC registry settings match UDC backup JSON files.
|
||||
|
||||
```powershell
|
||||
# Deploy to PCs with UDC installed
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "UDC-PC01","UDC-PC02" -Task DeployUDCWebServerConfig -Credential $cred
|
||||
.\Invoke-RemoteMaintenance.ps1 -All -Task AuditDNCConfig -Credential $cred -LogFile
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
1. Checks if UDC is installed
|
||||
2. Backs up existing configuration
|
||||
3. Deploys new web server settings
|
||||
4. Does NOT restart UDC (requires manual restart)
|
||||
1. Reads DNC registry values (General, eFocas, Hssb, PPDCS keys)
|
||||
2. Compares against UDC backup JSON files on the network share
|
||||
3. Reports MATCH/MISMATCH/MISSING for each field
|
||||
4. Exports CSV report to `logs/`
|
||||
|
||||
#### Check Defect Tracker Status
|
||||
|
||||
```powershell
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Shopfloor -Task CheckDefectTracker -Credential $cred
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
1. Checks if `Defect_Tracker.exe` is running on each PC
|
||||
2. Reports machine number + running status
|
||||
3. Exports CSV report to `logs/`
|
||||
|
||||
---
|
||||
|
||||
### How to Deploy Files
|
||||
|
||||
**Scenario:** Push any file to remote PCs with automatic backup of existing files.
|
||||
|
||||
#### Basic File Copy
|
||||
|
||||
```powershell
|
||||
# Copy a config file to a specific destination
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC01","PC02" -Task CopyFile `
|
||||
-SourcePath "\\server\share\config.json" `
|
||||
-DestinationPath "C:\ProgramData\App\config.json" `
|
||||
-Credential $cred
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
1. Copies source file to `C:\Windows\Temp\` on remote PC via WinRM
|
||||
2. Creates backup of existing file (if any) with timestamp
|
||||
3. Moves temp file to final destination
|
||||
4. Verifies deployment
|
||||
|
||||
#### File Copy with Post-Copy Command
|
||||
|
||||
**Scenario:** Deploy a file and restart a service/app in the logged-in user's session.
|
||||
|
||||
```powershell
|
||||
# Deploy eMxInfo.txt and kill DNC so it picks up the new file
|
||||
.\Invoke-RemoteMaintenance.ps1 -All -Task CopyFile `
|
||||
-SourcePath "\\server\share\eMxInfo.txt" `
|
||||
-DestinationPath "C:\Program Files (x86)\DNC\Server Files\eMxInfo.txt" `
|
||||
-RunCommand "taskkill /IM DNCMain.exe /F" `
|
||||
-Credential $cred
|
||||
|
||||
# Deploy UDC config (no restart needed)
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Shopfloor -Task CopyFile `
|
||||
-SourcePath "\\server\share\udc_webserver_settings.json" `
|
||||
-DestinationPath "C:\ProgramData\UDC\udc_webserver_settings.json" `
|
||||
-Credential $cred
|
||||
```
|
||||
|
||||
The `-RunCommand` runs via a one-shot scheduled task as the logged-in user, so it works for user-session processes (same pattern as Dashboard/Lobby Display kiosk relaunch).
|
||||
|
||||
---
|
||||
|
||||
### How to Import Registry Files
|
||||
|
||||
**Scenario:** Apply registry settings from a `.reg` file to remote PCs.
|
||||
|
||||
```powershell
|
||||
# Import a .reg file on all shopfloor PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Shopfloor -Task ImportReg `
|
||||
-SourcePath "\\server\share\intranet-zone.reg" `
|
||||
-Credential $cred
|
||||
|
||||
# Import on specific PCs
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "PC01" -Task ImportReg `
|
||||
-SourcePath "C:\Scripts\my-settings.reg" `
|
||||
-Credential $cred
|
||||
```
|
||||
|
||||
**What it does (default — as logged-in user):**
|
||||
1. Copies `.reg` file to `C:\Windows\Temp\` on remote PC via WinRM
|
||||
2. Creates a one-shot scheduled task as the logged-in user
|
||||
3. Runs `regedit.exe /s` to silently import the registry file
|
||||
4. Cleans up temp file and scheduled task
|
||||
|
||||
**HKCU support:** Because the import runs as the logged-in user (via scheduled task), both `HKLM` and `HKCU` keys in the `.reg` file are applied correctly. If no user is logged in, it falls back to direct import (HKLM only).
|
||||
|
||||
#### HKLM-Only / System Context
|
||||
|
||||
Use `-AsSystem` when the `.reg` file only contains `HKLM` keys and you want to skip the scheduled task overhead:
|
||||
|
||||
```powershell
|
||||
# Import HKLM-only registry settings directly as SYSTEM
|
||||
.\Invoke-RemoteMaintenance.ps1 -All -Task ImportReg `
|
||||
-SourcePath "\\server\share\machine-policy.reg" `
|
||||
-AsSystem -Credential $cred
|
||||
```
|
||||
|
||||
This runs `regedit.exe /s` directly in the WinRM session (SYSTEM context). Faster, but HKCU keys will not apply to any user.
|
||||
|
||||
---
|
||||
|
||||
@@ -612,22 +762,21 @@ $kiosks = @("KIOSK-01", "KIOSK-02", "KIOSK-03")
|
||||
```
|
||||
|
||||
**What it does:**
|
||||
1. Copies installer from network share
|
||||
2. Runs silent installation
|
||||
3. Configures auto-start
|
||||
4. Cleans up installer
|
||||
1. Pings target PC (skips if offline)
|
||||
2. Copies installer from network share to `C:\Windows\Temp\`
|
||||
3. Kills running Edge kiosk
|
||||
4. Runs silent installation (120-second timeout)
|
||||
5. Relaunches Edge kiosk via scheduled task as the logged-in user
|
||||
6. Cleans up installer and scheduled task
|
||||
|
||||
**After installation:**
|
||||
- Run data collection to update PC type
|
||||
- Reboot PC to complete setup
|
||||
**No reboot required** — Edge relaunches automatically in the logged-in user's session.
|
||||
|
||||
```powershell
|
||||
# Complete Dashboard deployment sequence
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "KIOSK-01" -Task InstallDashboard -Credential $cred
|
||||
.\Invoke-RemoteMaintenance.ps1 -ComputerName "KIOSK-01" -Task Reboot -Credential $cred
|
||||
# Deploy to all Dashboard kiosks
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType Dashboard -Task InstallDashboard -Credential $cred
|
||||
|
||||
# After reboot, update ShopDB
|
||||
.\Update-ShopfloorPCs-Remote.ps1 -ComputerName "KIOSK-01" -Credential $cred
|
||||
# Deploy to all Lobby Display kiosks
|
||||
.\Invoke-RemoteMaintenance.ps1 -PcType "Lobby Display" -Task InstallLobbyDisplay -Credential $cred
|
||||
```
|
||||
|
||||
#### Install Lobby Display
|
||||
|
||||
@@ -23,7 +23,7 @@ powershell-scripts/
|
||||
## Table of Contents
|
||||
|
||||
1. [Asset Collection Scripts](#asset-collection-scripts) (`asset-collection/`)
|
||||
2. [Remote Execution Scripts](#remote-execution-scripts) (`remote-execution/`)
|
||||
2. [Remote Execution Scripts](#remote-execution-scripts) (`S:\dt\shopfloor\scripts\remote-execution\`)
|
||||
3. [Setup & Utility Scripts](#setup--utility-scripts) (`setup-utilities/`)
|
||||
4. [Registry Backup Scripts](#registry-backup-scripts) (`registry-backup/`)
|
||||
5. [WinRM HTTPS Scripts](#winrm-https-scripts) (`winrm-https/`)
|
||||
|
||||
308
docs/convert_to_docx.py
Normal file
308
docs/convert_to_docx.py
Normal file
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Convert Markdown documentation to Word documents (.docx)
|
||||
With proper code block formatting (shaded boxes)
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
from docx import Document
|
||||
from docx.shared import Inches, Pt, RGBColor, Twips
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.enum.style import WD_STYLE_TYPE
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.oxml.ns import qn, nsmap
|
||||
from docx.oxml import OxmlElement
|
||||
|
||||
def set_cell_shading(cell, color="E8E8E8"):
|
||||
"""Set cell background shading color."""
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
shd = OxmlElement('w:shd')
|
||||
shd.set(qn('w:fill'), color)
|
||||
shd.set(qn('w:val'), 'clear')
|
||||
tcPr.append(shd)
|
||||
|
||||
def set_cell_borders(cell, color="CCCCCC"):
|
||||
"""Set cell border color."""
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
tcBorders = OxmlElement('w:tcBorders')
|
||||
for border_name in ['top', 'left', 'bottom', 'right']:
|
||||
border = OxmlElement(f'w:{border_name}')
|
||||
border.set(qn('w:val'), 'single')
|
||||
border.set(qn('w:sz'), '4')
|
||||
border.set(qn('w:color'), color)
|
||||
tcBorders.append(border)
|
||||
tcPr.append(tcBorders)
|
||||
|
||||
def add_code_block(doc, code_text, language=""):
|
||||
"""Add a formatted code block with shading."""
|
||||
# Create a single-cell table for the code block
|
||||
table = doc.add_table(rows=1, cols=1)
|
||||
table.autofit = True
|
||||
|
||||
cell = table.rows[0].cells[0]
|
||||
|
||||
# Set cell shading (light gray background)
|
||||
set_cell_shading(cell, "F5F5F5")
|
||||
set_cell_borders(cell, "DDDDDD")
|
||||
|
||||
# Clear default paragraph and add code
|
||||
cell.paragraphs[0].clear()
|
||||
|
||||
# Add each line of code
|
||||
lines = code_text.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if i == 0:
|
||||
para = cell.paragraphs[0]
|
||||
else:
|
||||
para = cell.add_paragraph()
|
||||
|
||||
para.paragraph_format.space_before = Pt(0)
|
||||
para.paragraph_format.space_after = Pt(0)
|
||||
para.paragraph_format.line_spacing = 1.0
|
||||
|
||||
run = para.add_run(line if line else ' ') # Use space for empty lines
|
||||
run.font.name = 'Consolas'
|
||||
run.font.size = Pt(9)
|
||||
run.font.color.rgb = RGBColor(0, 0, 0)
|
||||
|
||||
# Add spacing after the code block
|
||||
doc.add_paragraph()
|
||||
|
||||
def parse_markdown(md_content):
|
||||
"""Parse markdown content into structured elements."""
|
||||
lines = md_content.split('\n')
|
||||
elements = []
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
# Skip empty lines
|
||||
if not line.strip():
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Headers
|
||||
if line.startswith('# '):
|
||||
elements.append(('h1', line[2:].strip()))
|
||||
i += 1
|
||||
elif line.startswith('## '):
|
||||
elements.append(('h2', line[3:].strip()))
|
||||
i += 1
|
||||
elif line.startswith('### '):
|
||||
elements.append(('h3', line[4:].strip()))
|
||||
i += 1
|
||||
elif line.startswith('#### '):
|
||||
elements.append(('h4', line[5:].strip()))
|
||||
i += 1
|
||||
|
||||
# Horizontal rule
|
||||
elif line.strip() == '---':
|
||||
elements.append(('hr', ''))
|
||||
i += 1
|
||||
|
||||
# Code blocks
|
||||
elif line.strip().startswith('```'):
|
||||
code_lang = line.strip()[3:]
|
||||
code_lines = []
|
||||
i += 1
|
||||
while i < len(lines) and not lines[i].strip().startswith('```'):
|
||||
code_lines.append(lines[i])
|
||||
i += 1
|
||||
# Store language info with code
|
||||
elements.append(('code', (code_lang, '\n'.join(code_lines))))
|
||||
i += 1 # Skip closing ```
|
||||
|
||||
# Tables
|
||||
elif '|' in line and i + 1 < len(lines) and '---' in lines[i + 1]:
|
||||
table_lines = [line]
|
||||
i += 1
|
||||
while i < len(lines) and '|' in lines[i]:
|
||||
table_lines.append(lines[i])
|
||||
i += 1
|
||||
elements.append(('table', table_lines))
|
||||
|
||||
# Bullet lists
|
||||
elif line.strip().startswith('- ') or line.strip().startswith('* '):
|
||||
list_items = []
|
||||
while i < len(lines) and (lines[i].strip().startswith('- ') or lines[i].strip().startswith('* ') or (lines[i].startswith(' ') and lines[i].strip())):
|
||||
if lines[i].strip().startswith('- ') or lines[i].strip().startswith('* '):
|
||||
list_items.append(lines[i].strip()[2:])
|
||||
elif lines[i].startswith(' ') and list_items:
|
||||
list_items[-1] += ' ' + lines[i].strip()
|
||||
i += 1
|
||||
elements.append(('bullet', list_items))
|
||||
|
||||
# Numbered lists
|
||||
elif re.match(r'^\d+\.\s', line.strip()):
|
||||
list_items = []
|
||||
while i < len(lines) and (re.match(r'^\d+\.\s', lines[i].strip()) or lines[i].startswith(' ')):
|
||||
if re.match(r'^\d+\.\s', lines[i].strip()):
|
||||
list_items.append(re.sub(r'^\d+\.\s', '', lines[i].strip()))
|
||||
elif lines[i].startswith(' ') and list_items:
|
||||
list_items[-1] += ' ' + lines[i].strip()
|
||||
i += 1
|
||||
elements.append(('numbered', list_items))
|
||||
|
||||
# Regular paragraph
|
||||
else:
|
||||
para_lines = [line]
|
||||
i += 1
|
||||
while i < len(lines) and lines[i].strip() and not lines[i].startswith('#') and not lines[i].startswith('```') and not lines[i].startswith('- ') and not lines[i].startswith('* ') and '|' not in lines[i] and not re.match(r'^\d+\.\s', lines[i].strip()):
|
||||
para_lines.append(lines[i])
|
||||
i += 1
|
||||
elements.append(('para', ' '.join(para_lines)))
|
||||
|
||||
return elements
|
||||
|
||||
def clean_text(text):
|
||||
"""Remove markdown formatting from text."""
|
||||
# Bold
|
||||
text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text)
|
||||
# Italic
|
||||
text = re.sub(r'\*([^*]+)\*', r'\1', text)
|
||||
# Code
|
||||
text = re.sub(r'`([^`]+)`', r'\1', text)
|
||||
# Links
|
||||
text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text)
|
||||
return text
|
||||
|
||||
def add_formatted_text(paragraph, text):
|
||||
"""Add text with basic formatting to a paragraph."""
|
||||
# Split by formatting markers and add runs
|
||||
parts = re.split(r'(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)]+\))', text)
|
||||
|
||||
for part in parts:
|
||||
if not part:
|
||||
continue
|
||||
if part.startswith('**') and part.endswith('**'):
|
||||
run = paragraph.add_run(part[2:-2])
|
||||
run.bold = True
|
||||
elif part.startswith('`') and part.endswith('`'):
|
||||
run = paragraph.add_run(part[1:-1])
|
||||
run.font.name = 'Consolas'
|
||||
run.font.size = Pt(9)
|
||||
# Add light background for inline code
|
||||
run.font.highlight_color = 15 # Light gray (WD_COLOR_INDEX.GRAY_25)
|
||||
elif part.startswith('[') and '](' in part:
|
||||
match = re.match(r'\[([^\]]+)\]\(([^)]+)\)', part)
|
||||
if match:
|
||||
run = paragraph.add_run(match.group(1))
|
||||
run.font.color.rgb = RGBColor(0, 0, 255)
|
||||
run.underline = True
|
||||
else:
|
||||
paragraph.add_run(part)
|
||||
|
||||
def convert_md_to_docx(md_file, docx_file):
|
||||
"""Convert a markdown file to a Word document."""
|
||||
print(f"Converting {md_file} to {docx_file}...")
|
||||
|
||||
with open(md_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
elements = parse_markdown(content)
|
||||
|
||||
doc = Document()
|
||||
|
||||
# Set default font
|
||||
style = doc.styles['Normal']
|
||||
style.font.name = 'Calibri'
|
||||
style.font.size = Pt(11)
|
||||
|
||||
for elem_type, elem_content in elements:
|
||||
if elem_type == 'h1':
|
||||
p = doc.add_heading(clean_text(elem_content), level=0)
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
elif elem_type == 'h2':
|
||||
doc.add_heading(clean_text(elem_content), level=1)
|
||||
|
||||
elif elem_type == 'h3':
|
||||
doc.add_heading(clean_text(elem_content), level=2)
|
||||
|
||||
elif elem_type == 'h4':
|
||||
doc.add_heading(clean_text(elem_content), level=3)
|
||||
|
||||
elif elem_type == 'hr':
|
||||
p = doc.add_paragraph()
|
||||
p.add_run('─' * 70)
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
elif elem_type == 'para':
|
||||
p = doc.add_paragraph()
|
||||
add_formatted_text(p, elem_content)
|
||||
|
||||
elif elem_type == 'code':
|
||||
code_lang, code_text = elem_content
|
||||
add_code_block(doc, code_text, code_lang)
|
||||
|
||||
elif elem_type == 'bullet':
|
||||
for item in elem_content:
|
||||
p = doc.add_paragraph(style='List Bullet')
|
||||
add_formatted_text(p, item)
|
||||
|
||||
elif elem_type == 'numbered':
|
||||
for item in elem_content:
|
||||
p = doc.add_paragraph(style='List Number')
|
||||
add_formatted_text(p, item)
|
||||
|
||||
elif elem_type == 'table':
|
||||
# Parse table
|
||||
rows = []
|
||||
for line in elem_content:
|
||||
if '---' in line:
|
||||
continue
|
||||
cells = [c.strip() for c in line.split('|')[1:-1]]
|
||||
if cells:
|
||||
rows.append(cells)
|
||||
|
||||
if rows:
|
||||
num_cols = len(rows[0])
|
||||
table = doc.add_table(rows=len(rows), cols=num_cols)
|
||||
table.style = 'Table Grid'
|
||||
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
|
||||
for i, row in enumerate(rows):
|
||||
for j, cell in enumerate(row):
|
||||
if j < num_cols:
|
||||
table.rows[i].cells[j].text = clean_text(cell)
|
||||
# Bold and shade header row
|
||||
if i == 0:
|
||||
set_cell_shading(table.rows[i].cells[j], "E0E0E0")
|
||||
for para in table.rows[i].cells[j].paragraphs:
|
||||
for run in para.runs:
|
||||
run.bold = True
|
||||
|
||||
# Add spacing after table
|
||||
doc.add_paragraph()
|
||||
|
||||
doc.save(docx_file)
|
||||
print(f" Created: {docx_file}")
|
||||
|
||||
def main():
|
||||
docs_dir = '/home/camp/projects/powershell/docs'
|
||||
|
||||
md_files = [
|
||||
'Update-ShopfloorPCs-Remote.md',
|
||||
'Invoke-RemoteMaintenance.md',
|
||||
'Update-PC-CompleteAsset.md',
|
||||
'DATA_COLLECTION_PARITY.md'
|
||||
]
|
||||
|
||||
for md_file in md_files:
|
||||
md_path = os.path.join(docs_dir, md_file)
|
||||
docx_path = os.path.join(docs_dir, md_file.replace('.md', '.docx'))
|
||||
|
||||
if os.path.exists(md_path):
|
||||
convert_md_to_docx(md_path, docx_path)
|
||||
else:
|
||||
print(f"Warning: {md_path} not found")
|
||||
|
||||
print("\nConversion complete!")
|
||||
print(f"Word documents saved to: {docs_dir}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user