-
Technitium DNS Weekly Report → ntfy / Email
Technitium DNS Weekly Report → ntfy / Email
#!/usr/bin/env python3 """ Technitium DNS Server - Weekly Device Report Fetches all visited & blocked sites for a target device IP for the last 7 days and sends via ntfy and/or email. Schedule with cron: 0 8 * * 1 python3 /path/to/dns_report.py """ import requests import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from collections import Counter from datetime import datetime, timedelta import sys import time # ===================== CONFIGURATION ===================== # Technitium DNS Server DNS_SERVER = "http://192.168.1.x:5380" # <-- Your DNS server IP USERNAME = "admin" PASSWORD = "your_password" # <-- Your password # Target Device TARGET_DEVICE_IP = "192.168.1.100" # <-- Device IP to report DEVICE_NAME = "Test's PC" # <-- Friendly name # Pagination (increase MAX_PAGES if you have heavy traffic) ENTRIES_PER_PAGE = 1000 MAX_PAGES = 100 # ---- NTFY Configuration ---- NTFY_ENABLED = True NTFY_URL = "https://ntfy.sh/test-home" NTFY_TITLE = "DNS Report" NTFY_PRIORITY = "default" # min, low, default, high, urgent NTFY_TAGS = "bar_chart,globe_with_meridians" # ---- Email Configuration (optional) ---- EMAIL_ENABLED = False SMTP_SERVER = "smtp.gmail.com" SMTP_PORT = 587 SMTP_USE_TLS = True SMTP_USER = "your_email@gmail.com" SMTP_PASSWORD = "your_app_password" EMAIL_FROM = "your_email@gmail.com" EMAIL_TO = "recipient@gmail.com" EMAIL_SUBJECT = "Weekly DNS Report" # ========================================================== def get_token(): """Authenticate with Technitium and return API token.""" url = f"{DNS_SERVER}/api/user/login" params = {"user": USERNAME, "pass": PASSWORD} try: resp = requests.get(url, params=params, timeout=15) resp.raise_for_status() data = resp.json() if data.get("status") == "ok": print("✅ Authenticated with Technitium DNS") return data["token"] else: raise Exception(f"Login failed: {data.get('errorMessage', data)}") except requests.exceptions.ConnectionError: print(f"❌ Cannot connect to Technitium at {DNS_SERVER}") sys.exit(1) def fetch_logs_for_week(token, client_ip): """Fetch all query logs for the target IP for the last 7 days.""" now = datetime.now() week_ago = now - timedelta(days=7) # Technitium expects: yyyy-MM-dd HH:mm:ss (or ISO 8601 in newer versions) start_str = week_ago.strftime("%Y-%m-%d %H:%M:%S") end_str = now.strftime("%Y-%m-%d %H:%M:%S") print(f"📅 Fetching logs from {start_str} to {end_str}") print(f"🎯 Target device: {client_ip} ({DEVICE_NAME})\n") all_entries = [] for page in range(1, MAX_PAGES + 1): url = f"{DNS_SERVER}/api/queryLogs/list" params = { "token": token, "pageNumber": page, "entriesPerPage": ENTRIES_PER_PAGE, "clientIpAddress": client_ip, "start": start_str, "end": end_str, } try: resp = requests.get(url, params=params, timeout=30) resp.raise_for_status() data = resp.json() except Exception as e: print(f"⚠️ Error fetching page {page}: {e}") break if data.get("status") != "ok": print(f"⚠️ API error: {data.get('errorMessage', data)}") break entries = data.get("response", {}).get("entries", []) if not entries: break all_entries.extend(entries) print(f" Page {page}: {len(entries)} entries (total: {len(all_entries)})") # If we got fewer entries than requested, we've reached the end if len(entries) < ENTRIES_PER_PAGE: break time.sleep(0.1) # Be gentle on the server print(f"\n📊 Total entries fetched: {len(all_entries)}") return all_entries def parse_logs(entries): """Parse log entries into allowed/blocked domain lists.""" allowed_domains = [] blocked_domains = [] all_domains = [] for entry in entries: # Extract domain name (field name varies by Technitium version) domain = ( entry.get("qName") or entry.get("questionName") or entry.get("qname") or "unknown" ) domain = domain.rstrip(".") # Remove trailing dot # Skip internal/infrastructure queries if domain in ("unknown", "", "localhost"): continue # Detect if the query was blocked response_type = str(entry.get("responseType", "")).lower() rcode = str(entry.get("responseCode", entry.get("rcode", ""))).lower() blocked_keywords = [ "blocked", "upstreamblocked", "cacheblocked", "customblocked", "blocklist", "adblocked" ] is_blocked = ( any(kw in response_type for kw in blocked_keywords) or rcode in ("refused", "nxdomain") # depends on your block config or entry.get("isBlocked", False) ) all_domains.append(domain) if is_blocked: blocked_domains.append(domain) else: allowed_domains.append(domain) return allowed_domains, blocked_domains, all_domains def build_report(allowed_domains, blocked_domains, all_domains): """Build a formatted text report.""" now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d") today = datetime.now().strftime("%Y-%m-%d") allowed_counter = Counter(allowed_domains) blocked_counter = Counter(blocked_domains) all_counter = Counter(all_domains) # ---- Build the report string ---- lines = [] lines.append("=" * 50) lines.append(f"📊 WEEKLY DNS REPORT") lines.append(f"📱 Device: {DEVICE_NAME} ({TARGET_DEVICE_IP})") lines.append(f"📅 Period: {week_ago} → {today}") lines.append(f"🕐 Generated: {now}") lines.append("=" * 50) # Summary lines.append("") lines.append("📈 SUMMARY") lines.append(f" Total Queries : {len(all_domains)}") lines.append(f" Allowed Queries : {len(allowed_domains)}") lines.append(f" Blocked Queries : {len(blocked_domains)}") if all_domains: block_pct = (len(blocked_domains) / len(all_domains)) * 100 lines.append(f" Block Rate : {block_pct:.1f}%") lines.append(f" Unique Sites : {len(all_counter)}") lines.append(f" Unique Allowed : {len(allowed_counter)}") lines.append(f" Unique Blocked : {len(blocked_counter)}") # ---- ALL VISITED SITES (sorted by query count) ---- lines.append("") lines.append("🌐 ALL VISITED SITES (by query count)") lines.append("-" * 50) for i, (domain, count) in enumerate(allowed_counter.most_common(), 1): lines.append(f" {i:>4}. {domain} ({count})") # ---- ALL BLOCKED SITES ---- lines.append("") lines.append("🚫 ALL BLOCKED SITES (by query count)") lines.append("-" * 50) if blocked_counter: for i, (domain, count) in enumerate(blocked_counter.most_common(), 1): lines.append(f" {i:>4}. {domain} ({count})") else: lines.append(" (No blocked queries)") lines.append("") lines.append("=" * 50) return "\n".join(lines) def build_short_summary(allowed_domains, blocked_domains, all_domains, top_n=15): """Build a shorter summary for ntfy (fits in message limit).""" allowed_counter = Counter(allowed_domains) blocked_counter = Counter(blocked_domains) week_ago = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d") today = datetime.now().strftime("%Y-%m-%d") lines = [] lines.append(f"📱 {DEVICE_NAME} ({TARGET_DEVICE_IP})") lines.append(f"📅 {week_ago} → {today}") lines.append(f"📊 Queries: {len(all_domains)} | Blocked: {len(blocked_domains)}") if all_domains: lines.append(f"🛡️ Block rate: {(len(blocked_domains)/len(all_domains))*100:.1f}%") lines.append("") lines.append(f"🌐 Top {top_n} Visited:") for i, (domain, count) in enumerate(allowed_counter.most_common(top_n), 1): lines.append(f" {i}. {domain} ({count})") lines.append("") lines.append(f"🚫 Top {min(top_n, len(blocked_counter))} Blocked:") if blocked_counter: for i, (domain, count) in enumerate(blocked_counter.most_common(top_n), 1): lines.append(f" {i}. {domain} ({count})") else: lines.append(" (none)") return "\n".join(lines) def send_ntfy(full_report, short_summary): """Send notification via ntfy.sh with full report as attachment.""" if not NTFY_ENABLED: return print("\n📤 Sending ntfy notification...") # ---- Send the full report as a file attachment ---- try: timestamp = datetime.now().strftime("%Y%m%d") filename = f"dns_report_{timestamp}.txt" resp = requests.put( NTFY_URL, data=full_report.encode("utf-8"), headers={ "Title": NTFY_TITLE, "Tags": NTFY_TAGS, "Priority": NTFY_PRIORITY, "Filename": filename, }, timeout=15, ) resp.raise_for_status() print(f" ✅ Full report sent as attachment: {filename}") except Exception as e: print(f" ⚠️ Attachment send failed: {e}") # ---- Send a short summary as a readable notification ---- try: resp = requests.post( NTFY_URL, data=short_summary.encode("utf-8"), headers={ "Title": f"{NTFY_TITLE} - {DEVICE_NAME}", "Tags": NTFY_TAGS, "Priority": NTFY_PRIORITY, }, timeout=15, ) resp.raise_for_status() print(" ✅ Summary notification sent!") except Exception as e: print(f" ❌ Notification failed: {e}") def send_email(full_report): """Send the full report via email.""" if not EMAIL_ENABLED: return print("\n📧 Sending email report...") try: msg = MIMEMultipart("alternative") msg["Subject"] = f"{EMAIL_SUBJECT} - {DEVICE_NAME}" msg["From"] = EMAIL_FROM msg["To"] = EMAIL_TO # Plain text version text_part = MIMEText(full_report, "plain", "utf-8") msg.attach(text_part) # HTML version (optional - makes it look nicer) html_report = "<pre style='font-family: monospace; font-size: 13px;'>\n" html_report += full_report.replace("\n", "<br>") html_report += "\n</pre>" html_part = MIMEText(html_report, "html", "utf-8") msg.attach(html_part) with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server: if SMTP_USE_TLS: server.starttls() server.login(SMTP_USER, SMTP_PASSWORD) server.sendmail(EMAIL_FROM, EMAIL_TO, msg.as_string()) print(" ✅ Email sent successfully!") except Exception as e: print(f" ❌ Email failed: {e}") def save_report_local(full_report): """Save the report to a local file as backup.""" timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"dns_report_{TARGET_DEVICE_IP}_{timestamp}.txt" try: with open(filename, "w", encoding="utf-8") as f: f.write(full_report) print(f"\n💾 Report saved locally: {filename}") except Exception as e: print(f"\n⚠️ Could not save locally: {e}") # ===================== MAIN ===================== def main(): print("🚀 Technitium DNS Weekly Report Generator\n") # Step 1: Authenticate token = get_token() # Step 2: Fetch logs for the last 7 days entries = fetch_logs_for_week(token, TARGET_DEVICE_IP) if not entries: no_data_msg = f"No DNS queries found for {DEVICE_NAME} ({TARGET_DEVICE_IP}) in the last 7 days." print(f"\n⚠️ {no_data_msg}") # Still notify that there's no data if NTFY_ENABLED: requests.post( NTFY_URL, data=no_data_msg.encode("utf-8"), headers={"Title": NTFY_TITLE, "Tags": "warning"}, timeout=15, ) return # Step 3: Parse logs allowed, blocked, all_domains = parse_logs(entries) print(f"\n Allowed: {len(allowed)} | Blocked: {len(blocked)} | Total: {len(all_domains)}") # Step 4: Build reports full_report = build_report(allowed, blocked, all_domains) short_summary = build_short_summary(allowed, blocked, all_domains, top_n=15) # Print to console print("\n" + full_report) # Step 5: Send notifications send_ntfy(full_report, short_summary) send_email(full_report) save_report_local(full_report) print("\n🎉 Done!") if __name__ == "__main__": main()
What This Does
┌─────────────────────────────────────────────┐ │ Technitium DNS Server │ │ (last 7 days of logs) │ └──────────────┬──────────────────────────────┘ │ API query filtered by IP ▼ ┌─────────────────────────────────────────────┐ │ Python Script │ │ • Fetches all logs for TARGET_DEVICE_IP │ │ • Separates allowed vs blocked │ │ • Counts & ranks domains │ │ • Builds full report + short summary │ └──────┬──────────────┬──────────────┬────────┘ │ │ │ ▼ ▼ ▼ 📱 ntfy 📧 Email 💾 Local (2 messages) (full report) (.txt file) • Summary msg • Full .txt attachment
ntfy Notifications You’ll Receive
Notification 1 — Readable Summary:
📱 Test's PC (192.168.1.100) 📅 2025-01-08 → 2025-01-15 📊 Queries: 12847 | Blocked: 2341 🛡️ Block rate: 18.2% 🌐 Top 15 Visited: 1. www.google.com (834) 2. youtube.com (612) 3. reddit.com (445) ... 🚫 Top 15 Blocked: 1. ads.doubleclick.net (312) 2. tracking.facebook.com (198) ...Notification 2 — Full report attached as
dns_report_20250115.txt
Setup Steps
1. Edit the Configuration
DNS_SERVER = "http://192.168.1.5:5380" # Your Technitium IP PASSWORD = "your_actual_password" TARGET_DEVICE_IP = "192.168.1.100" # Device to track DEVICE_NAME = "Test's PC"2. Enable Query Logging in Technitium
Technitium Dashboard → Settings → Logging
- ✅ Enable Query Logging
- ✅ Log to local folder
- Set log retention ≥ 7 days
3. Test It
pip install requests python3 dns_report.py4. Schedule Weekly (Every Monday 8 AM)
Linux (cron):
crontab -e # Add: 0 8 * * 1 /usr/bin/python3 /home/test/dns_report.py >> /home/test/dns_report.log 2>&1Windows (Task Scheduler):
Action: Start a program Program: python Arguments: C:\scripts\dns_report.py Trigger: Weekly, Monday, 8:00 AM
Optional: Enable Email Too
Set
EMAIL_ENABLED = Trueand fill in your SMTP details. For Gmail, use an App Password:EMAIL_ENABLED = True SMTP_SERVER = "smtp.gmail.com" SMTP_PORT = 587 SMTP_USER = "you@gmail.com" SMTP_PASSWORD = "xxxx xxxx xxxx xxxx" # Gmail App Password EMAIL_TO = "you@gmail.com" -
PowerToys Peek on Spacebar Is a UX Trap in Windows 11
Windows 11 Spacebar Opens File Preview? It’s Probably PowerToys — And It’s Bad UX
If pressing Spacebar in Windows 11 File Explorer opens a preview window, it’s easy to assume Windows is broken.
It probably isn’t.
In most cases, the real culprit is PowerToys Peek — and if it’s bound to Spacebar, it creates a genuinely bad user experience.
Why? Because Spacebar is not a “feature key.” It’s a typing key.
That means a preview shortcut can suddenly hijack normal actions like:
- renaming files,
- typing in search,
- or simply using File Explorer the way people expect it to work.
This post explains what’s happening, how to fix it, and why binding Peek to Spacebar is a design choice that can easily backfire.
TL;DR
If Spacebar previews files in Windows 11, the issue is usually:
- PowerToys → Peek
- configured to use Spacebar
Fix:
Open:
PowerToys → Peek
Then either:
- Disable Peek, or
-
Change the activation shortcut to something like:
Ctrl + SpaceCtrl + Shift + Space
First: This Is Not a Native Windows 11 Feature
This part matters.
Windows 11 does not natively use plain Spacebar in File Explorer to open a floating preview popup.
What Windows does include is:
- Preview Pane
- Shortcut:
Alt + P - Opens as a side panel inside File Explorer
That’s the built-in behavior.
If you’re getting a floating popup preview when pressing Spacebar, that’s almost always coming from:
- PowerToys Peek
- QuickLook
- Seer
- or another third-party Explorer enhancement
In my case, it was PowerToys Peek.
Why This Feels Like a Windows Bug
Because PowerToys is made by Microsoft.
That’s what makes this so misleading.
PowerToys integrates well with Windows, so when it changes File Explorer behavior, it can feel “native” enough that users assume the OS itself is responsible.
But PowerToys is not Windows.
It’s an optional utility suite layered on top of Windows — and that distinction matters when troubleshooting weird keyboard behavior.
The Real Problem Isn’t the Preview — It’s the Shortcut
The preview feature itself is not the issue.
The issue is binding it to Spacebar.
That’s where the UX falls apart.
Why this is bad UX
Spacebar is a core input key.
It’s not like
F8,Ctrl + Shift + P, or some obscure combo nobody types accidentally.Users press Spacebar all the time, and not just in documents.
Inside File Explorer alone, it’s part of normal workflow behavior.
That means assigning a global-ish preview action to Spacebar creates a shortcut collision with basic file operations.
The Best Example: Renaming Files
This is where the problem becomes obvious.
Typical workflow
- Select a file
- Press
F2to rename it - Start typing the new file name
- Press Spacebar to separate words
What happens instead
Instead of inserting a space in the file name:
- Peek intercepts the keypress
- a preview window opens
- your rename flow gets interrupted
That’s not just annoying.
That’s a direct conflict with one of the most common actions in File Explorer.
Why this matters
When a utility hijacks a normal text-input key, users experience:
- broken typing behavior
- interrupted workflows
- accidental popups
- confusion about what the system is doing
- loss of trust in File Explorer behavior
That’s why this feels worse than “just a shortcut issue.”
It’s a usability problem.
How I Confirmed It Was PowerToys
The fastest test took about 10 seconds.
Quick diagnostic
- Press
Ctrl + Shift + Escto open Task Manager - Go to Processes
- Find PowerToys
- Right-click it
- Click End task
- Go back to File Explorer
- Press Spacebar on a selected file
Result
If the preview stops:
✅ PowerToys Peek was the cause
That immediately proves the behavior is not coming from native Windows.
How to Fix It
You have two options:
- disable Peek
- or keep Peek, but stop using Spacebar
For most people, the second option is the better fix.
Option 1: Disable Peek Completely
If you don’t use file preview often:
- Open PowerToys
- Click Peek in the left sidebar
- Turn Enable Peek Off
Done.
Option 2: Keep Peek, But Rebind the Shortcut (Recommended)
If you like the feature, keep it — just don’t bind it to a typing key.
Steps
- Open PowerToys
- Go to Peek
- Find Activation shortcut
- Change it from Spacebar to something safer
Better shortcut choices
Ctrl + SpaceCtrl + Shift + Space- another modifier-based combination that doesn’t conflict with typing
This preserves the feature without breaking normal file operations.
My Opinion: Spacebar Should Never Be the Default Here
This is the part where I’ll be blunt:
Spacebar is a poor default for a preview tool inside File Explorer.
Yes, it mirrors the macOS Quick Look idea.
But Windows users don’t all have the same expectations or workflows, and File Explorer is not Finder.
More importantly:
- Spacebar is still a normal input key
- it’s heavily used in naming, searching, and general text entry
- binding it to a preview action creates too much friction unless it’s extremely context-aware
A good shortcut should be:
- intentional,
- hard to trigger accidentally,
- and should not interfere with typing
Spacebar fails that test in this context.
Recommended Best Practice
If you use PowerToys Peek, avoid assigning it to:
- Spacebar
- single letters
- number keys
- any key that doubles as regular text input
Use modifier-based shortcuts instead
Safer examples:
Ctrl + SpaceCtrl + Shift + Space-
Alt + Space(only if it doesn’t conflict with your setup)
The rule is simple:
If a key is part of normal typing, it shouldn’t be the sole trigger for a utility action in File Explorer.
If It’s Not PowerToys
If disabling or rebinding Peek doesn’t fix it, check for similar tools:
- QuickLook
- Seer
- Files
- QTTabBar
But if you already found PowerToys Peek bound to Spacebar, that’s almost certainly the root cause.
Final Take
The weird part about this issue is that it looks like Windows.
The frustrating part is that it feels like a Windows bug.
But the real problem is simpler:
- PowerToys Peek was bound to Spacebar
- Spacebar is a terrible key to hijack for this
- That breaks common workflows like file renaming
Best fix
Open:
PowerToys → Peek
Then:
- disable Peek, or
- rebind it to a modifier-based shortcut
That keeps the feature useful without making File Explorer feel broken.
-
Lenovo Workstation Recommendation for Research
[2] ASSISTANT (claude-opus-4-6-thinking)
💭 Thinking Process
> The user wants to buy a Lenovo workstation for a university researcher who will be using: > 1. **Abaqus** (FEA simulation software) > 2. **MATLAB** (numerical computing) > 3. **CATIA** (3D CAD/CAM/CAE software) > > Let me outline the system requirements for each and then recommend a configuration that meets or exceeds all of them. > > ## Abaqus (Dassault Systèmes) > **Minimum Requirements:** > - CPU: 64-bit Intel or AMD processor (multi-core recommended) > - RAM: 8 GB minimum, 16-64 GB+ recommended for large models > > > - GPU: OpenGL compatible, certified graphics cards (NVIDIA Quadro/RTX or AMD Radeon Pro) > - Storage: SSD recommended, 10+ GB for installation > - OS: Windows 10/11 64-bit or Linux > > **Recommended for serious FEA work:** > - CPU: Multi-core (8+ cores), high clock speed > - RAM: 64-128 GB for large simulations > - GPU: NVIDIA RTX A-series or Quadro > - Storage: NVMe SSD (1 TB+) > > ## MATLAB (MathWorks) > Now I'm looking at MATLAB's requirements, which are fairly flexible—it runs on standard Intel or AMD processors with modest RAM needs, though for heavy numerical work I'd want at least 16 GB, and the software itself needs about 2-3 GB of disk space. > > ## CATIA (Dassault Systèmes) > CATIA is more demanding, requiring high-performance Intel Core i7 or Xeon processors and a minimum of 16 GB RAM, with certified NVIDIA or AMD GPUs being essential for proper performance. > > ## Combined Recommendation > > For serious simulation work at a university, I'm thinking a workstation-class machine makes the most sense. The Lenovo ThinkStation line offers several solid options depending on budget—the P3 Tower for mid-range needs, the P620 with AMD Threadripper Pro for heavy multithreading, or the P520 with Intel Xeon W for maximum single-thread performance. > > Looking at the performance tiers, I'd recommend either a 16+ core processor with 64-128 GB of ECC RAM and an RTX A4000 or A5000 GPU for the serious simulation work, or step down to an 8-core Xeon W with 32 GB RAM and an RTX A2000 if budget is tighter. Storage-wise, a 1 TB NVMe SSD for the OS and applications, plus a 2 TB HDD for data, gives good balance between speed and capacity. For a researcher, the P3 Tower or P520 would work well, though I should consider the current 2024-2025 lineup more carefully—the P5 has replaced the P520, and there's also the high-end PX option with dual Xeons if they need maximum performance. Let me put together a practical recommendation based on their actual needs.Lenovo Workstation Recommendation for Research (Abaqus, MATLAB, CATIA)
Software Minimum & Recommended Requirements Summary
Component Abaqus MATLAB CATIA You Should Target CPU Multi-core 64-bit; high clock + many cores for solvers x86-64 with AVX2 Intel Xeon / Core i7+ Xeon W or Core i9 (12–16+ cores) RAM 8 GB min; 64 GB+ for large models 4 GB min 16 GB min 64 GB ECC (expandable to 128 GB) GPU OpenGL; certified NVIDIA Quadro/RTX NVIDIA CUDA for GPU computing Certified NVIDIA RTX / Quadro (critical for CATIA) NVIDIA RTX A4000 (16 GB) or RTX A2000 (12 GB) Storage SSD strongly recommended; large scratch space ~10–30 GB with toolboxes SSD required 1 TB NVMe SSD + optional 2 TB HDD OS Windows 10/11 Pro 64-bit or Linux Windows/Linux Windows 10/11 Pro 64-bit Windows 11 Pro 64-bit ⚠️ CATIA is the most demanding regarding GPU certification. Always cross-check the chosen GPU against the Dassault Systèmes certified hardware list.
Recommended Lenovo Models & Configurations
✅ Option 1: Lenovo ThinkStation P3 Tower (Best value for most researchers)
Component Suggested Config Processor Intel Core i9-13900K (24 cores/32 threads) or Intel Xeon W3-2435 (8 cores, ECC support) RAM 64 GB DDR5 (2 × 32 GB), expandable to 128 GB GPU NVIDIA RTX A4000 (16 GB GDDR6) Storage 1 TB PCIe Gen4 NVMe SSD + 2 TB 7200 RPM HDD PSU 750W (80+ Platinum) OS Windows 11 Pro 64-bit Approx. Price $3,500–$5,000 USD
🔶 Option 2: Lenovo ThinkStation P620 (Heavy simulation workloads)
Component Suggested Config Processor AMD Threadripper PRO 5955WX (16 cores/32 threads) or 5975WX (32 cores) RAM 128 GB DDR4 ECC (4 × 32 GB), expandable to 512 GB GPU NVIDIA RTX A5000 (24 GB GDDR6) Storage 1 TB PCIe Gen4 NVMe SSD + 4 TB HDD PSU 1000W OS Windows 11 Pro 64-bit Approx. Price $6,000–$10,000 USD
Key Points & Tips
-
Abaqus scaling: Abaqus/Standard benefits from high clock speed; Abaqus/Explicit benefits from more cores. Ask the researcher what type of analyses they run.
-
ECC Memory: For long-running simulations (hours/days), ECC RAM is strongly recommended to prevent silent data corruption. Xeon/Threadripper Pro platforms support this.
-
GPU Certification: CATIA can be unstable on non-certified GPUs. Stick with NVIDIA RTX A-series (A2000, A4000, A5000) — these are on Dassault’s certified list.
-
MATLAB Parallel Computing: If the researcher uses MATLAB’s Parallel Computing Toolbox or GPU computing, ensure the NVIDIA GPU has CUDA Compute Capability 3.5+ (all RTX A-series qualify).
-
Scratch/Temp Storage: Abaqus writes large temporary files during solves. A secondary NVMe SSD dedicated to scratch space can significantly speed up large simulations.
-
-
White Desktop Icon in Windows 11 Caused by OneDrive
How To Fix the Zombie White Desktop Icon in Windows 11 Caused by OneDrive
If you’ve deployed a customized Windows 11 image and are seeing a mysterious blank white desktop icon that reappears every time you delete it — even though it has no name, target, or properties — you’re not alone.
This ghostly icon is one of the most frustrating issues in Windows imaging and deployment. But the good news? It’s not random, and it can be permanently fixed.
In this guide, I’ll show you exactly how to identify, remove, and prevent this issue — especially when it’s caused by OneDrive, which is one of the most common root causes.
🧟♂️ What Is This “Zombie” Icon?
You see:
- A single blank white icon on the desktop
- No right-click menu options (or only “Delete”)
- Properties shows nothing useful (no target path)
- Deleting it → refresh (
F5) → it comes back instantly - Appears for every new user on machines deployed from your image
❌ This is NOT an icon cache issue
✅ This is a namespace object baked into the Default User profile
🔍 Why Does This Happen?
When you run
sysprep /generalize /oobe /unattend:unattend.xmlwith CopyProfile enabled, Windows copies settings from your reference user account to the Default User profile.If OneDrive was ever installed, partially removed, disabled, or unprovisioned during your build process, its desktop namespace registration may have been left behind as an orphaned registry entry.
Even if you:
- Removed OneDrive via PowerShell
- Disabled it via Group Policy
- Deleted the shortcut manually
…if the namespace key remains in the registry, it gets copied to all future users.
And because the app can’t be found at login, Explorer displays it as a white placeholder icon.
✅ Step-by-Step Fix: Remove the Orphaned OneDrive Namespace Entry
🔧 For Already-Deployed Machines (Quick Fix)
- Press
Win + R, typeregedit, and press Enter - Navigate to:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace - Look through each subkey (they are GUIDs like
{A0953C92-50DC-43BF-BE83-575D374FBC1}). - Click each one and check the (Default) value in the right pane.
- Find the key where the Default value says “OneDrive”.
- Right-click that GUID key → Export (to backup) → then Delete
- Also check the same path under:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpaceAnd delete the OneDrive GUID there too, if present.
- Restart Explorer or log off and back in:
- Open Task Manager → find Windows Explorer → click Restart
✅ The white icon should now be gone — and stay gone.
💣 Nuclear Option: Fix It Permanently in Your Reference Image
To ensure this never happens again on any machine you deploy, clean up the Default User profile before capturing your image.
⚠️ Warning:
Never edit
C:\Users\Defaultdirectly. Always use the registry hive method.
Step 1: Load the Default User Registry Hive
Run Command Prompt as Administrator on your reference machine:
reg load HKLM\DefaultUser C:\Users\Default\NTUSER.DATThis loads the Default User’s
NTUSER.DATinto the registry underHKEY_LOCAL_MACHINE\DefaultUser.
Step 2: Delete the OneDrive Namespace Key
- Open
regedit - Go to:
HKEY_LOCAL_MACHINE\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace - Browse each GUID subkey.
- If you find a key where the (Default) value is “OneDrive”, delete it.
- Example GUIDs (may vary):
-
{018D5C66-4533-4307-9B53-224DE2ED1FE6}← Common OneDrive namespace -
{905E63B6-C1BF-494E-B29C-65B732D3D21A}← Older Microsoft 365/OneDrive combo
-
- Example GUIDs (may vary):
🔎 Tip: Sort by the “Data” column to spot “OneDrive” entries faster.
Step 3: Unload the Hive (Critical!)
Back in the admin Command Prompt:
reg unload HKLM\DefaultUser❗ Never skip this step. Failing to unload the hive can corrupt the Default profile and cause bigger issues.
Step 4: Run Sysprep & Capture
Now proceed with:
sysprep /generalize /oobe /shutdown /unattend:unattend.xmlThen capture your WIM/ESD image.
🎉 Any machine deployed from this image will no longer inherit the OneDrive zombie icon.
🛠 Prevent This Issue in Future Builds
Add these steps to your standard image build checklist:
Step Action 1 After removing apps (like OneDrive), always clean the namespace registry 2 Use reg load/reg unloadto audit the Default User profile3 Consider disabling OneDrive via GPO instead of removal, unless required 4 Avoid interactive logins before sysprep — they risk polluting the profile
📜 Bonus: PowerShell Script to Remove OneDrive Namespace (For Deployment Tools)
Use this in MDT, SCCM, or Intune to fix existing machines:
# Remove OneDrive from Machine-wide Desktop Namespace $oneDriveGuids = '{018D5C66-4533-4307-9B53-224DE2ED1FE6}', '{905E63B6-C1BF-494E-B29C-65B732D3D21A}' foreach ($guid in $oneDriveGuids) { $path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\$guid" if (Test-Path $path) { Remove-Item -Path $path -Force -ErrorAction SilentlyContinue } } # Optional: Kill explorer and restart to refresh immediately Get-Process explorer | Stop-ProcessAdd this script to your task sequence just before sysprep.
🔄 Alternative: Disable OneDrive Without Breaking the Desktop
Instead of removing OneDrive entirely, consider suppressing it more cleanly:
Via Group Policy (Recommended)
- Path:
User Configuration → Administrative Templates → Windows Components → OneDrive - Enable: “Remove OneDrive shortcut from This PC”
This hides the icon without leaving broken registry entries.
✅ Final Thoughts
That blank white desktop icon is not magic — it’s a misconfigured namespace object inherited from a dirty Default User profile.
By understanding that OneDrive is a frequent offender, and using the
reg load/reg unloadmethod to clean the Default hive, you can:- Fix existing deployments
- Prevent future ones
- Save hours of troubleshooting across your fleet
-
Building an OEM-Style Factory Reset for Windows 11 — Complete Guide
Building an OEM-Style Factory Reset for Windows 11 — Complete Guide
A Step-by-Step Blog Post
# How to Create an OEM-Style Factory Reset Partition in Windows 11 ### Restore Your PC with All Apps, Drivers & Settings — Like Dell/HP/Lenovo --- ## Table of Contents 1. [Introduction](#introduction) 2. [Architecture Overview](#architecture-overview) 3. [Prerequisites](#prerequisites) 4. [Phase 1: Create the Recovery Partition](#phase-1-create-the-recovery-partition) 5. [Phase 2: The PowerShell Script — Explained](#phase-2-the-powershell-script) 6. [Phase 3: How the Restore Scripts Work](#phase-3-restore-scripts) 7. [Phase 4: Testing & Validation](#phase-4-testing) 8. [Lessons Learned & Pitfalls](#lessons-learned) 9. [Complete Script Download](#complete-script) 10. [FAQ](#faq) --- ## Introduction Ever wondered how Dell, HP, and Lenovo ship PCs with a "Factory Reset" option that restores Windows with **all pre-installed applications and drivers**? Windows 11's built-in "Reset this PC" feature **strips away all Win32 desktop applications** during the reset process. It's designed to return Windows to a clean state — not to restore a fully configured system. This guide shows you how to build a **true OEM-style factory reset** that preserves everything: - ✅ All installed applications (Win32, .NET, etc.) - ✅ All hardware drivers - ✅ System settings and configurations - ✅ Silent OOBE (no setup screens after restore) - ✅ Hidden, protected recovery partition - ✅ Works even when Windows won't boot ### Why Not Just Use "Reset this PC"? | Feature | Reset this PC | Our Solution | |---------|:------------:|:------------:| | Restores base Windows | ✅ | ✅ | | Keeps Win32 apps | ❌ | ✅ | | Keeps drivers | ❌ | ✅ | | Silent OOBE | ❌ | ✅ | | Works from hidden partition | ❌ | ✅ | | OEM-style experience | ❌ | ✅ | --- ## Architecture Overview ### How It Works ```text CREATING THE RECOVERY IMAGE: ┌─────────────────┐ VSS Snapshot ┌──────────────────┐ │ C:\ (live OS) │ ──────────────────→ │ Shadow Copy │ │ Files locked │ │ No locks ✅ │ └─────────────────┘ └────────┬─────────┘ │ DISM /Capture │ ┌────────▼─────────┐ │ Recovery Partition│ │ install.wim │ │ unattend.xml │ │ restore.cmd │ └──────────────────┘ RESTORING THE SYSTEM: ┌─────────────────┐ │ Boot to WinRE │ │ (SHIFT+Restart) │ └────────┬────────┘ │ ▼ ┌───────────────────────────────┐ │ CMD Prompt | | Diskpart │ | list vol | |check for the vol name recovery| |Select that volume | |Assign Letter R | |CD R:\RecoveryImage | │ Run restore.cmd │ └────────┬──────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ restore.cmd: │ │ 1. Scan all drives for install.wim │ │ 2. Format C: │ │ 3. DISM /Apply-Image to C:\ │ │ 4. Rebuild bootloader │ │ 5. Apply unattend.xml │ │ 6. Re-hide recovery partition │ │ 7. Reboot → Windows ready! │ └──────────────────────────────────────────┘File Structure
Recovery Partition (hidden): └── RecoveryImage/ ├── install.wim ← Full system image (15-40 GB) ├── unattend.xml ← Silent OOBE configuration ├── restore.cmd ← Interactive factory reset ├── diagnose.cmd ← Safe pre-restore testing ├── quick_restore.cmd ← Automated (no prompts) ├── recovery_metadata.txt ← Partition info reference └── INSTRUCTIONS.txt ← Human-readable guide Desktop (on C:): ├── Factory Reset.bat ← Double-click launcher ├── FactoryReset_Launcher.ps1 ← PowerShell confirmation └── Factory Reset - Instructions.txt ← Restore guide
Prerequisites
System Requirements
- Windows 11 (any edition)
- Administrator privileges
- All desired applications and drivers installed
- At least 30-50 GB free space for recovery partition
- UEFI boot mode (not Legacy BIOS)
Before You Begin
- Install all applications and drivers
- Configure all settings
- Run Windows Update
- Clean up temp files (Disk Cleanup)
- Restart the PC for a clean state
Phase 1: Create the Recovery Partition
Before running the script, create a partition manually:
Step 1: Open Disk Management
Win + X → Disk ManagementStep 2: Shrink C: Drive
1. Right-click C: → "Shrink Volume" 2. Enter amount: 30000-50000 MB (30-50 GB) (Should be ~70% of your used space on C:) 3. Click "Shrink"Step 3: Create New Volume
1. Right-click the new "Unallocated" space 2. "New Simple Volume" 3. Wizard: - Use all available space - Assign letter: R - Format: NTFS - Volume Label: Recovery 4. Click "Finish"You should now see a
Recovery (R:)drive in File Explorer.
Phase 2: The PowerShell Script
How the Script Works
The PowerShell script automates 4 steps:
Step What It Does How 1 Capture system image VSS Shadow Copy + DISM /Capture 1.5 Generate silent OOBE config Creates unattend.xml 2 Create restore scripts Generates WinRE-compatible .cmd files 3 Create desktop shortcuts Factory Reset.bat + instructions 4 Hide partition (optional) DiskPart removes letter + sets GPT attributes Key Technical Decisions
Why VSS Shadow Copy?
You cannot capture a live Windows drive — files are locked by the OS:
Direct DISM on C:\ → ERROR 0x80070020 (Sharing Violation)VSS creates a frozen point-in-time snapshot that DISM can capture without any file locks.
# Create shadow copy $result = (Get-WmiObject -List Win32_ShadowCopy).Create("C:\", "ClientAccessible") # Mount it cmd /c "mklink /d C:\ShadowCopyMount \\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\" # Capture from snapshot (no locks!) DISM.exe /Capture-Image /ImageFile:R:\RecoveryImage\install.wim ` /CaptureDir:C:\ShadowCopyMount /Name:"Recovery" /Compress:maximumWhy Not “Reset this PC” with reagentc?
reagentc /setosimage → "Reset this PC" → Windows RECONSTRUCTS the OS → Win32 apps are STRIPPED OUT ❌ DISM /Apply-Image → Writes image BYTE-FOR-BYTE back to disk → EVERYTHING preserved ✅Why Hardcoded Paths in restore.cmd?
WinRE has a stripped-down
cmd.exethat doesn’t supportEnableDelayedExpansion::: BROKEN in WinRE: setlocal EnableDelayedExpansion set WIM=R:\RecoveryImage\install.wim DISM.exe /ImageFile:"!WIM!" ← Expands to "" → Error 87 :: WORKS in WinRE: DISM.exe /ImageFile:R:\RecoveryImage\install.wim ← Hardcoded, always worksWhy Scan All Drive Letters?
WinRE reassigns drive letters differently than normal Windows:
Normal Windows: WinRE: C: = Windows C: = Windows (usually) R: = Recovery D: = Recovery (auto-assigned!) R: = nothingThe restore script scans every letter before resorting to diskpart:
if exist D:\RecoveryImage\install.wim set RECOVERY_DRIVE=D:& goto FOUND if exist E:\RecoveryImage\install.wim set RECOVERY_DRIVE=E:& goto FOUND ... :: If none found, mount via diskpart with known disk/partition numbersWhy
pingInstead oftimeout?timeout.exedoesn’t exist in WinRE:timeout /t 3 /nobreak > nul ← ERROR in WinRE ping -n 4 127.0.0.1 > nul ← Works everywhere (3 second delay)Why Separate DiskPart Calls for Re-hide?
Combining all operations in one script causes cascading failures:
:: BROKEN: If "remove letter" fails, set id and gpt also fail select partition 5 remove letter=R ← fails if letter isn't R set id=de94bba4... ← skipped! gpt attributes=0x80... ← skipped! :: FIXED: Each operation is independent :: Call 1: select partition 5 remove ← removes whatever letter is assigned exit :: Call 2: select partition 5 set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override exit :: Call 3: select partition 5 gpt attributes=0x8000000000000001 exitThe Complete Script
Save as
Create-RecoveryPartition.ps1and run as Administrator:#Requires -RunAsAdministrator <# .SYNOPSIS Windows 11 Recovery Partition Creator — Final Version .DESCRIPTION All fixes included: - VSS Shadow Copy for WIM capture - Hardcoded DISM paths (no variables, no delayed expansion) - ping instead of timeout (WinRE compatible) - Separate diskpart calls for re-hide - Full logging to C:\temp\restore_log.txt - Unattend.xml for silent OOBE - Hidden partition support - Desktop shortcuts .NOTES Run as Administrator Requires existing recovery partition (create in Disk Management first) #> # ============================================================ # CONFIGURATION # ============================================================ $RecoveryDriveLetter = "" $RecoveryFolderName = "RecoveryImage" $RecoveryVolumeLabel = "Recovery" $ImageName = "Windows11CustomRecovery" $ImageDescription = "FullSystemWithAppsAndDrivers" $CompressionType = "maximum" $LogFile = "$env:USERPROFILE\Desktop\RecoverySetup_Log.txt" $ShadowMountPoint = "C:\ShadowCopyMount" # ============================================================ # LOGGING # ============================================================ function Write-Log { param([string]$Message, [string]$Level = "INFO") $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $logEntry = "[$timestamp] [$Level] $Message" switch ($Level) { "INFO" { Write-Host $logEntry -ForegroundColor Cyan } "SUCCESS" { Write-Host $logEntry -ForegroundColor Green } "WARNING" { Write-Host $logEntry -ForegroundColor Yellow } "ERROR" { Write-Host $logEntry -ForegroundColor Red } "STEP" { Write-Host "" Write-Host ("=" * 70) -ForegroundColor Magenta Write-Host " $Message" -ForegroundColor Magenta Write-Host ("=" * 70) -ForegroundColor Magenta } } Add-Content -Path $LogFile -Value $logEntry } function Show-Banner { Clear-Host Write-Host "" Write-Host " ╔════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan Write-Host " ║ Windows 11 - Recovery Partition Creator (Final) ║" -ForegroundColor Cyan Write-Host " ║ Full System Restore — Apps + Drivers + Settings ║" -ForegroundColor Cyan Write-Host " ║ WinRE Compatible — Logging — Hidden Partition Support ║" -ForegroundColor Cyan Write-Host " ╚════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan Write-Host "" } # ============================================================ # PARTITION SELECTION & PREREQUISITES # ============================================================ function Show-AvailablePartitions { $volumes = Get-Volume | Where-Object { $_.DriveLetter -and $_.DriveType -eq 'Fixed' -and $_.DriveLetter -ne 'C' } | Sort-Object DriveLetter if ($volumes.Count -eq 0) { Write-Log "No suitable partitions found!" "ERROR" Write-Host "" Write-Host " Create a recovery partition first:" -ForegroundColor Yellow Write-Host " 1. Win+X → Disk Management" -ForegroundColor White Write-Host " 2. Shrink C: by 30-50 GB" -ForegroundColor White Write-Host " 3. Create New Simple Volume (NTFS, label: Recovery)" -ForegroundColor White Write-Host "" return $null } Write-Host " Available Drives:" -ForegroundColor Yellow Write-Host " ────────────────────────────────────────────────────────" -ForegroundColor Gray foreach ($vol in $volumes) { $sizeGB = [math]::Round($vol.Size / 1GB, 2) $freeGB = [math]::Round($vol.SizeRemaining / 1GB, 2) $label = if ($vol.FileSystemLabel) { $vol.FileSystemLabel } else { "No Label" } Write-Host (" [{0}:] {1,-20} Total: {2,8} GB | Free: {3,8} GB" -f $vol.DriveLetter, $label, $sizeGB, $freeGB) -ForegroundColor White } Write-Host " ────────────────────────────────────────────────────────" -ForegroundColor Gray Write-Host "" return $volumes } function Get-RecoveryDrive { $volumes = Show-AvailablePartitions if ($null -eq $volumes) { return $null } do { $inputDrive = Read-Host " Enter Recovery partition drive letter (e.g., R, D, E)" $inputDrive = $inputDrive.Trim().ToUpper().TrimEnd(':') $selectedVol = $volumes | Where-Object { $_.DriveLetter -eq $inputDrive } if (-not $selectedVol) { Write-Log "Invalid drive letter. Try again." "WARNING" } } while (-not $selectedVol) return "${inputDrive}:" } function Test-Prerequisites { param([string]$RecoveryDrive) Write-Log "Running prerequisite checks..." "INFO" Write-Host "" $allPassed = $true # Admin check $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) if ($principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host " [✓] Administrator privileges" -ForegroundColor Green } else { Write-Host " [✗] Administrator privileges" -ForegroundColor Red $allPassed = $false } # DISM check if (Test-Path "$env:SystemRoot\System32\Dism.exe") { Write-Host " [✓] DISM.exe found" -ForegroundColor Green } else { Write-Host " [✗] DISM.exe not found" -ForegroundColor Red $allPassed = $false } # Recovery drive check if (Test-Path "$RecoveryDrive\") { Write-Host " [✓] Recovery drive ($RecoveryDrive) accessible" -ForegroundColor Green } else { Write-Host " [✗] Recovery drive ($RecoveryDrive) not accessible" -ForegroundColor Red $allPassed = $false } # VSS check $vssSvc = Get-Service -Name VSS -ErrorAction SilentlyContinue if ($vssSvc) { Write-Host " [✓] VSS Service available ($($vssSvc.Status))" -ForegroundColor Green } else { Write-Host " [✗] VSS Service not found" -ForegroundColor Red $allPassed = $false } # Free space check $recVol = Get-Volume -DriveLetter ($RecoveryDrive.TrimEnd(':')) -ErrorAction SilentlyContinue $srcVol = Get-Volume -DriveLetter 'C' -ErrorAction SilentlyContinue if ($recVol -and $srcVol) { $usedGB = [math]::Round(($srcVol.Size - $srcVol.SizeRemaining) / 1GB, 2) $requiredGB = [math]::Round($usedGB * 0.7, 2) $freeGB = [math]::Round($recVol.SizeRemaining / 1GB, 2) if ($freeGB -ge $requiredGB) { Write-Host " [✓] Free space OK (Need ~${requiredGB}GB, Have ${freeGB}GB)" -ForegroundColor Green } else { Write-Host " [✗] Insufficient space (Need ~${requiredGB}GB, Have ${freeGB}GB)" -ForegroundColor Red $allPassed = $false } Write-Host " [i] C: used space: ${usedGB} GB" -ForegroundColor Gray } Write-Host "" return $allPassed } # ============================================================ # VSS SHADOW COPY # ============================================================ function New-ShadowCopy { Write-Log "Creating VSS Shadow Copy of C: drive..." "INFO" Start-Service VSS -ErrorAction SilentlyContinue Start-Service swprv -ErrorAction SilentlyContinue Start-Sleep -Seconds 3 try { $result = (Get-WmiObject -List Win32_ShadowCopy).Create("C:\", "ClientAccessible") if ($result.ReturnValue -ne 0) { Write-Log "Shadow copy failed with code: $($result.ReturnValue)" "ERROR" return $null } $shadowID = $result.ShadowID $shadowCopy = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $shadowID } $shadowPath = $shadowCopy.DeviceObject Write-Log "Shadow copy created: $shadowPath" "SUCCESS" return @{ ID = $shadowID DevicePath = $shadowPath } } catch { Write-Log "Shadow copy exception: $_" "ERROR" return $null } } function Mount-ShadowCopy { param([string]$ShadowDevicePath, [string]$MountPoint) if (Test-Path $MountPoint) { cmd /c "rmdir `"$MountPoint`"" 2>$null } $deviceWithSlash = $ShadowDevicePath if (-not $deviceWithSlash.EndsWith('\')) { $deviceWithSlash += '\' } cmd /c "mklink /d `"$MountPoint`" `"$deviceWithSlash`"" 2>&1 | Out-Null if ((Test-Path $MountPoint) -and (Get-ChildItem $MountPoint -ErrorAction SilentlyContinue)) { Write-Log "Shadow copy mounted at $MountPoint" "SUCCESS" return $true } Write-Log "Shadow copy mount failed!" "ERROR" return $false } function Remove-ShadowCopyAndMount { param([string]$ShadowID, [string]$MountPoint) if (Test-Path $MountPoint) { cmd /c "rmdir `"$MountPoint`"" 2>$null } if ($ShadowID) { try { $shadow = Get-WmiObject Win32_ShadowCopy | Where-Object { $_.ID -eq $ShadowID } if ($shadow) { $shadow.Delete() } Write-Log "Shadow copy cleaned up." "SUCCESS" } catch { Write-Log "Shadow cleanup warning: $_" "WARNING" } } } # ============================================================ # STEP 1: CAPTURE WIM IMAGE # ============================================================ function Invoke-Step1_CaptureWIM { param( [string]$RecoveryDrive, [string]$FolderName, [string]$Name, [string]$Description, [string]$Compression ) Write-Log "STEP 1: CAPTURE WIM IMAGE (via VSS Shadow Copy)" "STEP" $recoveryPath = Join-Path $RecoveryDrive $FolderName $wimFile = Join-Path $recoveryPath "install.wim" $shadowInfo = $null try { # Create folder if (-not (Test-Path $recoveryPath)) { New-Item -Path $recoveryPath -ItemType Directory -Force | Out-Null Write-Log "Created folder: $recoveryPath" "INFO" } # Check existing WIM if (Test-Path $wimFile) { $fileSize = [math]::Round((Get-Item $wimFile).Length / 1GB, 2) Write-Host "" Write-Host " ⚠ Existing WIM found: $wimFile ($fileSize GB)" -ForegroundColor Yellow $overwrite = Read-Host " Overwrite? (Y/N)" if ($overwrite -ne 'Y' -and $overwrite -ne 'y') { Write-Log "Using existing WIM file." "WARNING" return @{ Success = $true; WimFile = $wimFile; Skipped = $true } } Remove-Item $wimFile -Force } # Create shadow copy $shadowInfo = New-ShadowCopy if ($null -eq $shadowInfo) { return @{ Success = $false; WimFile = $null; Skipped = $false } } # Mount shadow copy $mounted = Mount-ShadowCopy -ShadowDevicePath $shadowInfo.DevicePath -MountPoint $ShadowMountPoint if (-not $mounted) { return @{ Success = $false; WimFile = $null; Skipped = $false } } # Capture Write-Host "" Write-Host " ⏳ Capturing image... This takes 20-60 minutes." -ForegroundColor Yellow Write-Host " ⚠ Do NOT restart or shut down your PC." -ForegroundColor Yellow Write-Host "" $startTime = Get-Date $batchFile = "$env:TEMP\dism_capture.cmd" @" @echo off DISM.exe /Capture-Image /ImageFile:"$wimFile" /CaptureDir:"$ShadowMountPoint" /Name:"$Name" /Description:"$Description" /Compress:$Compression exit /b %errorlevel% "@ | Out-File -FilePath $batchFile -Encoding ASCII -Force $proc = Start-Process -FilePath "cmd.exe" ` -ArgumentList "/c `"$batchFile`"" ` -NoNewWindow -Wait -PassThru $duration = (Get-Date) - $startTime Remove-Item $batchFile -Force -ErrorAction SilentlyContinue if ($proc.ExitCode -eq 0 -and (Test-Path $wimFile)) { $wimSize = [math]::Round((Get-Item $wimFile).Length / 1GB, 2) Write-Host "" Write-Host " ┌──────────────────────────────────────────────────┐" -ForegroundColor Green Write-Host " │ ✅ CAPTURE SUCCESSFUL! │" -ForegroundColor Green Write-Host " │ File : $wimFile" -ForegroundColor Green Write-Host " │ Size : $wimSize GB" -ForegroundColor Green Write-Host " │ Duration : $($duration.ToString('hh\:mm\:ss'))" -ForegroundColor Green Write-Host " └──────────────────────────────────────────────────┘" -ForegroundColor Green Write-Host "" # Verify Write-Log "Verifying captured image..." "INFO" & DISM.exe /Get-ImageInfo /ImageFile:"$wimFile" return @{ Success = $true; WimFile = $wimFile; Skipped = $false } } else { Write-Log "DISM capture FAILED! Exit code: $($proc.ExitCode)" "ERROR" Write-Host " Check DISM log: notepad C:\Windows\Logs\DISM\dism.log" -ForegroundColor Yellow return @{ Success = $false; WimFile = $null; Skipped = $false } } } catch { Write-Log "Exception during capture: $_" "ERROR" return @{ Success = $false; WimFile = $null; Skipped = $false } } finally { # Always clean up shadow copy Write-Log "Cleaning up shadow copy..." "INFO" if ($shadowInfo) { Remove-ShadowCopyAndMount -ShadowID $shadowInfo.ID -MountPoint $ShadowMountPoint } elseif (Test-Path $ShadowMountPoint) { cmd /c "rmdir `"$ShadowMountPoint`"" 2>$null } } } # ============================================================ # STEP 1.5: UNATTEND.XML GENERATION # ============================================================ function Get-CurrentSystemLocale { $culture = Get-Culture $uiLang = Get-WinSystemLocale $timezone = (Get-TimeZone).Id $keyboard = (Get-WinUserLanguageList)[0].InputMethodTips[0] $kbLayout = if ($keyboard) { $keyboard } else { "0409:00000409" } return @{ SystemLocale = $uiLang.Name UILanguage = $culture.Name UserLocale = $culture.Name InputLocale = $kbLayout TimeZone = $timezone LanguageName = $culture.DisplayName } } function Get-UnattendSettings { Write-Log "STEP 1.5: CONFIGURE SILENT OOBE (unattend.xml)" "STEP" $currentLocale = Get-CurrentSystemLocale Write-Host "" Write-Host " ── Detected System Settings ──" -ForegroundColor Yellow Write-Host " Language : $($currentLocale.LanguageName)" -ForegroundColor Gray Write-Host " Locale : $($currentLocale.SystemLocale)" -ForegroundColor Gray Write-Host " Keyboard : $($currentLocale.InputLocale)" -ForegroundColor Gray Write-Host " Time Zone : $($currentLocale.TimeZone)" -ForegroundColor Gray Write-Host "" $useCurrentLocale = Read-Host " Use current region/keyboard/language? (Y/N) [Y]" if ([string]::IsNullOrEmpty($useCurrentLocale)) { $useCurrentLocale = 'Y' } if ($useCurrentLocale -eq 'Y' -or $useCurrentLocale -eq 'y') { $locale = $currentLocale.SystemLocale $uiLanguage = $currentLocale.UILanguage $userLocale = $currentLocale.UserLocale $inputLocale = $currentLocale.InputLocale $timeZone = $currentLocale.TimeZone } else { Write-Host " Common codes: en-US, fr-FR, de-DE, es-ES, nl-NL, pt-BR, ja-JP" -ForegroundColor Gray $locale = Read-Host " System Locale (e.g., fr-FR)" $uiLanguage = Read-Host " UI Language (e.g., fr-FR)" $userLocale = Read-Host " User Locale (e.g., fr-FR)" $inputLocale = Read-Host " Keyboard (e.g., 040c:0000040c for French)" $timeZone = Read-Host " Time Zone (e.g., Romance Standard Time)" } Write-Host "" Write-Host " ── Local User Account ──" -ForegroundColor Yellow $currentUser = $env:USERNAME $username = Read-Host " Username [$currentUser]" if ([string]::IsNullOrEmpty($username)) { $username = $currentUser } $password = Read-Host " Password (leave blank for no password)" $hasPassword = -not [string]::IsNullOrEmpty($password) $isAdmin = Read-Host " Make Administrator? (Y/N) [Y]" if ([string]::IsNullOrEmpty($isAdmin)) { $isAdmin = 'Y' } $groupName = if ($isAdmin -eq 'Y' -or $isAdmin -eq 'y') { "Administrators" } else { "Users" } $currentPC = $env:COMPUTERNAME $computerName = Read-Host " Computer Name [$currentPC]" if ([string]::IsNullOrEmpty($computerName)) { $computerName = $currentPC } $autoLogon = Read-Host " Auto-login after restore? (Y/N) [Y]" if ([string]::IsNullOrEmpty($autoLogon)) { $autoLogon = 'Y' } # Summary Write-Host "" Write-Host " ┌──────────────────────────────────────────────────┐" -ForegroundColor White Write-Host " │ Locale : $locale" -ForegroundColor White Write-Host " │ Keyboard : $inputLocale" -ForegroundColor White Write-Host " │ Time Zone : $timeZone" -ForegroundColor White Write-Host " │ Username : $username" -ForegroundColor White Write-Host " │ Password : $(if ($hasPassword) {'****'} else {'(none)'})" -ForegroundColor White Write-Host " │ Admin : $groupName" -ForegroundColor White Write-Host " │ PC Name : $computerName" -ForegroundColor White Write-Host " │ Auto-login : $autoLogon" -ForegroundColor White Write-Host " └──────────────────────────────────────────────────┘" -ForegroundColor White Write-Host "" $confirm = Read-Host " Confirm these settings? (Y/N)" if ($confirm -ne 'Y' -and $confirm -ne 'y') { return $null } return @{ Locale = $locale UILanguage = $uiLanguage UserLocale = $userLocale InputLocale = $inputLocale TimeZone = $timeZone Username = $username Password = $password HasPassword = $hasPassword GroupName = $groupName ComputerName = $computerName AutoLogon = ($autoLogon -eq 'Y' -or $autoLogon -eq 'y') } } function New-UnattendXml { param([hashtable]$S) $arch = if ([Environment]::Is64BitOperatingSystem) { "amd64" } else { "x86" } $passwordBlock = if ($S.HasPassword) { $encodedPwd = [Convert]::ToBase64String( [System.Text.Encoding]::Unicode.GetBytes($S.Password + "Password") ) "<Password><Value>$encodedPwd</Value><PlainText>false</PlainText></Password>" } else { "<Password><Value></Value><PlainText>true</PlainText></Password>" } $autoLogonBlock = "" if ($S.AutoLogon) { $autoLogonBlock = @" <AutoLogon> <Enabled>true</Enabled> <Username>$($S.Username)</Username> <Password> <Value>$($S.Password)</Value> <PlainText>true</PlainText> </Password> <LogonCount>3</LogonCount> </AutoLogon> "@ } return @" <?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"> <settings pass="specialize"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="$arch" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <ComputerName>$($S.ComputerName)</ComputerName> </component> <component name="Microsoft-Windows-Deployment" processorArchitecture="$arch" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <RunSynchronous> <RunSynchronousCommand wcm:action="add"> <Order>1</Order> <Path>reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE /v BypassNRO /t REG_DWORD /d 1 /f</Path> </RunSynchronousCommand> </RunSynchronous> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-International-Core" processorArchitecture="$arch" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <InputLocale>$($S.InputLocale)</InputLocale> <SystemLocale>$($S.Locale)</SystemLocale> <UILanguage>$($S.UILanguage)</UILanguage> <UILanguageFallback>en-US</UILanguageFallback> <UserLocale>$($S.UserLocale)</UserLocale> </component> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="$arch" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <OOBE> <HideEULAPage>true</HideEULAPage> <HideLocalAccountScreen>true</HideLocalAccountScreen> <HideOnlineAccountScreens>true</HideOnlineAccountScreens> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> <NetworkLocation>Work</NetworkLocation> <ProtectYourPC>3</ProtectYourPC> <SkipMachineOOBE>true</SkipMachineOOBE> <SkipUserOOBE>true</SkipUserOOBE> </OOBE> <TimeZone>$($S.TimeZone)</TimeZone> <UserAccounts> <LocalAccounts> <LocalAccount wcm:action="add"> <Name>$($S.Username)</Name> <Group>$($S.GroupName)</Group> <DisplayName>$($S.Username)</DisplayName> $passwordBlock </LocalAccount> </LocalAccounts> </UserAccounts> $autoLogonBlock <FirstLogonCommands> <SynchronousCommand wcm:action="add"> <Order>1</Order> <CommandLine>reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</CommandLine> <RequiresUserInput>false</RequiresUserInput> </SynchronousCommand> <SynchronousCommand wcm:action="add"> <Order>2</Order> <CommandLine>reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OOBE" /v DisablePrivacyExperience /t REG_DWORD /d 1 /f</CommandLine> <RequiresUserInput>false</RequiresUserInput> </SynchronousCommand> </FirstLogonCommands> </component> </settings> </unattend> "@ } function Save-UnattendXml { param([string]$RecoveryPath, [hashtable]$Settings) if ($null -eq $Settings) { Write-Log "Unattend configuration skipped." "WARNING" return } $xml = New-UnattendXml -S $Settings $unattendFile = Join-Path $RecoveryPath "unattend.xml" $xml | Out-File -FilePath $unattendFile -Encoding UTF8 -Force Write-Log "Saved: $unattendFile" "SUCCESS" } # ============================================================ # STEP 2: CREATE RECOVERY SCRIPTS # ════════════════════════════════════════════════════════════ # Key design: # - NO setlocal EnableDelayedExpansion # - NO !variables! in any DISM/diskpart commands # - ALL paths hardcoded at generation time # - ping instead of timeout (WinRE compatible) # - Separate diskpart calls for re-hide # - Full logging to C:\temp\restore_log.txt # ============================================================ function Invoke-Step2_CreateRecoveryScripts { param( [string]$RecoveryDrive, [string]$RecoveryPath, [string]$WimFile ) Write-Log "STEP 2: CREATE RECOVERY SCRIPTS" "STEP" $diskNumber = (Get-Partition -DriveLetter C).DiskNumber $cPartNum = (Get-Partition -DriveLetter C).PartitionNumber $partitions = Get-Partition -DiskNumber $diskNumber $efiPart = $partitions | Where-Object { $_.Type -eq 'System' -or $_.GptType -eq '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}' } $efiPartNum = if ($efiPart) { $efiPart.PartitionNumber } else { 1 } $recDriveLetter = $RecoveryDrive.TrimEnd(':') $recPartition = Get-Partition -DriveLetter $recDriveLetter $recPartNum = $recPartition.PartitionNumber $recDiskNum = $recPartition.DiskNumber $osPartSize = [math]::Round((Get-Partition -DriveLetter C).Size / 1GB, 0) Write-Host "" Write-Host " ── Partition Layout ──" -ForegroundColor Yellow Write-Host " OS Disk : $diskNumber, Partition $cPartNum (C:)" -ForegroundColor Gray Write-Host " EFI : $diskNumber, Partition $efiPartNum" -ForegroundColor Gray Write-Host " Recovery : $recDiskNum, Partition $recPartNum" -ForegroundColor Gray Write-Host "" # ════════════════════════════════════════════════════════ # RESTORE.CMD # # Fix: Scans ALL drive letters first to find install.wim # Only uses diskpart if WIM not found on any letter # ════════════════════════════════════════════════════════ $restoreScript = @" @echo off cls color 1F mkdir C:\temp 2>nul echo ================================================== > C:\temp\restore_log.txt echo FACTORY RESET LOG >> C:\temp\restore_log.txt echo Started: %date% %time% >> C:\temp\restore_log.txt echo ================================================== >> C:\temp\restore_log.txt echo. echo ======================================================== echo FACTORY RESET - FULL SYSTEM RESTORE echo All data on C: will be PERMANENTLY DELETED! echo Log: C:\temp\restore_log.txt echo ======================================================== echo. if /i "%1"=="AUTO" goto SKIP_CONFIRM echo Type YES to confirm factory reset: set /p CONFIRM= if /i not "%CONFIRM%"=="YES" ( echo Cancelled. echo [%time%] Cancelled >> C:\temp\restore_log.txt pause exit /b 0 ) :SKIP_CONFIRM echo [%time%] Confirmed >> C:\temp\restore_log.txt REM ────────────────────────────────────────────────────── REM STEP 1/6: FIND the recovery image REM REM Method A: Scan all existing drive letters (D-Z) REM WinRE may have already assigned a letter REM Method B: Use diskpart to mount known partition as R: REM For when partition is truly hidden REM ────────────────────────────────────────────────────── echo. echo [1/6] Searching for recovery image... echo. echo [%time%] STEP 1: Finding recovery image >> C:\temp\restore_log.txt set RECOVERY_DRIVE= set WIM_FOUND=0 REM ── Method A: Scan all existing drive letters ── echo Scanning all drive letters... echo [%time%] Scanning drive letters A-Z >> C:\temp\restore_log.txt if exist C:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=C: set WIM_FOUND=1 echo Found on C: echo [%time%] Found on C: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist D:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=D: set WIM_FOUND=1 echo Found on D: echo [%time%] Found on D: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist E:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=E: set WIM_FOUND=1 echo Found on E: echo [%time%] Found on E: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist F:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=F: set WIM_FOUND=1 echo Found on F: echo [%time%] Found on F: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist G:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=G: set WIM_FOUND=1 echo Found on G: echo [%time%] Found on G: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist H:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=H: set WIM_FOUND=1 echo Found on H: echo [%time%] Found on H: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist I:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=I: set WIM_FOUND=1 echo Found on I: echo [%time%] Found on I: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist J:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=J: set WIM_FOUND=1 echo Found on J: echo [%time%] Found on J: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist K:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=K: set WIM_FOUND=1 echo Found on K: echo [%time%] Found on K: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist L:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=L: set WIM_FOUND=1 echo Found on L: echo [%time%] Found on L: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist M:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=M: set WIM_FOUND=1 echo Found on M: echo [%time%] Found on M: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist N:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=N: set WIM_FOUND=1 echo Found on N: echo [%time%] Found on N: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist O:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=O: set WIM_FOUND=1 echo Found on O: echo [%time%] Found on O: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist P:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=P: set WIM_FOUND=1 echo Found on P: echo [%time%] Found on P: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist Q:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=Q: set WIM_FOUND=1 echo Found on Q: echo [%time%] Found on Q: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist R:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=R: set WIM_FOUND=1 echo Found on R: echo [%time%] Found on R: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist S:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=S: set WIM_FOUND=1 echo Found on S: echo [%time%] Found on S: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist T:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=T: set WIM_FOUND=1 echo Found on T: echo [%time%] Found on T: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist U:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=U: set WIM_FOUND=1 echo Found on U: echo [%time%] Found on U: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist V:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=V: set WIM_FOUND=1 echo Found on V: echo [%time%] Found on V: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist W:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=W: set WIM_FOUND=1 echo Found on W: echo [%time%] Found on W: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist X:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=X: set WIM_FOUND=1 echo Found on X: echo [%time%] Found on X: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist Y:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=Y: set WIM_FOUND=1 echo Found on Y: echo [%time%] Found on Y: >> C:\temp\restore_log.txt goto FOUND_WIM ) if exist Z:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=Z: set WIM_FOUND=1 echo Found on Z: echo [%time%] Found on Z: >> C:\temp\restore_log.txt goto FOUND_WIM ) REM ── Method B: Not found on any letter — mount via diskpart ── echo. echo Not found on any lettered drive. echo Mounting hidden partition (Disk $recDiskNum, Part $recPartNum) as R: ... echo [%time%] Not on any letter. Trying diskpart mount... >> C:\temp\restore_log.txt ( echo select disk $recDiskNum echo select partition $recPartNum echo assign letter=R echo exit ) > %TEMP%\dp_mount.txt diskpart /s %TEMP%\dp_mount.txt >> C:\temp\restore_log.txt 2>&1 echo [%time%] diskpart mount exit: %errorlevel% >> C:\temp\restore_log.txt del %TEMP%\dp_mount.txt 2>nul ping -n 5 127.0.0.1 > nul if exist R:\$RecoveryFolderName\install.wim ( set RECOVERY_DRIVE=R: set WIM_FOUND=1 echo Found after diskpart mount on R: echo [%time%] Found on R: after diskpart >> C:\temp\restore_log.txt goto FOUND_WIM ) REM ── ALL METHODS FAILED ── echo. echo ════════════════════════════════════════════════════ echo ERROR: Recovery image not found anywhere! echo ════════════════════════════════════════════════════ echo. echo [%time%] FATAL: Recovery image not found! >> C:\temp\restore_log.txt echo [%time%] Dumping volume list: >> C:\temp\restore_log.txt (echo list volume echo exit) > %TEMP%\dp_dbg.txt diskpart /s %TEMP%\dp_dbg.txt >> C:\temp\restore_log.txt 2>&1 diskpart /s %TEMP%\dp_dbg.txt del %TEMP%\dp_dbg.txt 2>nul echo. echo Check log: C:\temp\restore_log.txt pause exit /b 1 :FOUND_WIM echo. echo Recovery drive : %RECOVERY_DRIVE% echo Image file : %RECOVERY_DRIVE%\$RecoveryFolderName\install.wim echo. echo [%time%] Using drive: %RECOVERY_DRIVE% >> C:\temp\restore_log.txt REM ────────────────────────────────────────────────────── REM STEP 2/6: Format C: REM ────────────────────────────────────────────────────── echo [2/6] Formatting C: drive... echo. echo [%time%] STEP 2: Formatting C: >> C:\temp\restore_log.txt REM ── Save log before format ── mkdir %RECOVERY_DRIVE%\temp 2>nul copy /y C:\temp\restore_log.txt %RECOVERY_DRIVE%\temp\restore_log.txt > nul 2>nul format C: /FS:NTFS /Q /Y /V:Windows set FMT_ERR=%errorlevel% REM ── Restore log after format ── mkdir C:\temp 2>nul copy /y %RECOVERY_DRIVE%\temp\restore_log.txt C:\temp\restore_log.txt > nul 2>nul echo [%time%] format exit: %FMT_ERR% >> C:\temp\restore_log.txt if %FMT_ERR% neq 0 ( echo format.exe failed, using diskpart... echo [%time%] format failed, trying diskpart >> C:\temp\restore_log.txt ( echo select disk $diskNumber echo select partition $cPartNum echo format fs=ntfs quick label=Windows echo assign letter=C echo exit ) > %TEMP%\dp_fmt.txt diskpart /s %TEMP%\dp_fmt.txt >> C:\temp\restore_log.txt 2>&1 del %TEMP%\dp_fmt.txt 2>nul mkdir C:\temp 2>nul copy /y %RECOVERY_DRIVE%\temp\restore_log.txt C:\temp\restore_log.txt > nul 2>nul ) echo C: formatted. echo [%time%] Format done >> C:\temp\restore_log.txt REM ────────────────────────────────────────────────────── REM STEP 3/6: Apply recovery image REM REM Uses %RECOVERY_DRIVE% which was set during scan. REM This is a SIMPLE %var% — works fine in basic cmd.exe REM (only !var! delayed expansion is broken in WinRE) REM ────────────────────────────────────────────────────── echo. echo [3/6] Applying recovery image (15-30 minutes)... echo DO NOT turn off your computer! echo. echo [%time%] STEP 3: Applying image >> C:\temp\restore_log.txt echo [%time%] Source: %RECOVERY_DRIVE%\$RecoveryFolderName\install.wim >> C:\temp\restore_log.txt DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C:\ set DISM_ERR=%errorlevel% echo [%time%] DISM exit: %DISM_ERR% >> C:\temp\restore_log.txt if %DISM_ERR% neq 0 ( echo. echo Attempt 1 failed (code: %DISM_ERR%). echo Trying without trailing backslash... echo [%time%] Trying /ApplyDir:C: >> C:\temp\restore_log.txt DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C: set DISM_ERR2=%errorlevel% echo [%time%] DISM attempt 2 exit: %DISM_ERR2% >> C:\temp\restore_log.txt if %DISM_ERR2% neq 0 ( echo. echo ALL DISM ATTEMPTS FAILED! echo [%time%] ALL DISM FAILED >> C:\temp\restore_log.txt echo. echo Source: %RECOVERY_DRIVE%\$RecoveryFolderName\install.wim echo Target: C:\ echo Log: C:\temp\restore_log.txt pause exit /b 1 ) ) echo. echo Image applied successfully! echo [%time%] Image applied OK >> C:\temp\restore_log.txt REM ────────────────────────────────────────────────────── REM STEP 4/6: Rebuild boot REM ────────────────────────────────────────────────────── echo. echo [4/6] Rebuilding boot configuration... echo. echo [%time%] STEP 4: Boot config >> C:\temp\restore_log.txt ( echo select disk $diskNumber echo select partition $efiPartNum echo assign letter=S echo exit ) > %TEMP%\dp_efi.txt diskpart /s %TEMP%\dp_efi.txt >> C:\temp\restore_log.txt 2>&1 del %TEMP%\dp_efi.txt 2>nul ping -n 3 127.0.0.1 > nul echo [%time%] bcdboot C:\Windows /s S: /f UEFI >> C:\temp\restore_log.txt bcdboot C:\Windows /s S: /f UEFI >> C:\temp\restore_log.txt 2>&1 set BCD_ERR=%errorlevel% echo [%time%] bcdboot exit: %BCD_ERR% >> C:\temp\restore_log.txt if %BCD_ERR% neq 0 ( echo [%time%] Trying without /s S: >> C:\temp\restore_log.txt bcdboot C:\Windows /f UEFI >> C:\temp\restore_log.txt 2>&1 ) ( echo select disk $diskNumber echo select partition $efiPartNum echo remove letter=S echo exit ) > %TEMP%\dp_efi2.txt diskpart /s %TEMP%\dp_efi2.txt >> C:\temp\restore_log.txt 2>&1 del %TEMP%\dp_efi2.txt 2>nul echo Boot rebuilt. echo [%time%] Boot done >> C:\temp\restore_log.txt REM ────────────────────────────────────────────────────── REM STEP 5/6: Unattend.xml REM ────────────────────────────────────────────────────── echo. echo [5/6] Applying unattend.xml... echo. echo [%time%] STEP 5: Unattend >> C:\temp\restore_log.txt if exist %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml ( echo [%time%] unattend.xml found >> C:\temp\restore_log.txt mkdir C:\Windows\Panther\unattend 2>nul mkdir C:\Windows\System32\Sysprep 2>nul copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend\unattend.xml >> C:\temp\restore_log.txt 2>&1 copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend.xml >> C:\temp\restore_log.txt 2>&1 copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\System32\Sysprep\unattend.xml >> C:\temp\restore_log.txt 2>&1 reg load HKLM\OFFLINE_SW C:\Windows\System32\config\SOFTWARE >> C:\temp\restore_log.txt 2>&1 reg add "HKLM\OFFLINE_SW\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f >> C:\temp\restore_log.txt 2>&1 reg add "HKLM\OFFLINE_SW\Policies\Microsoft\Windows\OOBE" /v DisablePrivacyExperience /t REG_DWORD /d 1 /f >> C:\temp\restore_log.txt 2>&1 reg unload HKLM\OFFLINE_SW >> C:\temp\restore_log.txt 2>&1 echo Unattend.xml applied. echo [%time%] Unattend OK >> C:\temp\restore_log.txt ) else ( echo No unattend.xml found. echo [%time%] No unattend.xml >> C:\temp\restore_log.txt ) REM ────────────────────────────────────────────────────── REM STEP 6/6: Re-hide recovery partition REM Separate diskpart calls for reliability REM ────────────────────────────────────────────────────── echo. echo [6/6] Re-hiding recovery partition... echo. echo [%time%] STEP 6: Re-hide >> C:\temp\restore_log.txt REM ── 6a: Remove whatever letter recovery has ── echo [%time%] 6a: Removing recovery drive letter >> C:\temp\restore_log.txt ( echo select disk $recDiskNum echo select partition $recPartNum echo remove echo exit ) > %TEMP%\dp_rh1.txt diskpart /s %TEMP%\dp_rh1.txt >> C:\temp\restore_log.txt 2>&1 echo [%time%] Remove letter exit: %errorlevel% >> C:\temp\restore_log.txt del %TEMP%\dp_rh1.txt 2>nul ping -n 3 127.0.0.1 > nul REM ── 6b: Set partition type ── echo [%time%] 6b: Setting partition type >> C:\temp\restore_log.txt ( echo select disk $recDiskNum echo select partition $recPartNum echo set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override echo exit ) > %TEMP%\dp_rh2.txt diskpart /s %TEMP%\dp_rh2.txt >> C:\temp\restore_log.txt 2>&1 echo [%time%] Set type exit: %errorlevel% >> C:\temp\restore_log.txt del %TEMP%\dp_rh2.txt 2>nul ping -n 3 127.0.0.1 > nul REM ── 6c: Set GPT attributes ── echo [%time%] 6c: Setting GPT attributes >> C:\temp\restore_log.txt ( echo select disk $recDiskNum echo select partition $recPartNum echo gpt attributes=0x8000000000000001 echo exit ) > %TEMP%\dp_rh3.txt diskpart /s %TEMP%\dp_rh3.txt >> C:\temp\restore_log.txt 2>&1 echo [%time%] Set GPT exit: %errorlevel% >> C:\temp\restore_log.txt del %TEMP%\dp_rh3.txt 2>nul echo Re-hide complete. echo [%time%] Re-hide done >> C:\temp\restore_log.txt REM ── Verify ── echo [%time%] Final volume list: >> C:\temp\restore_log.txt (echo list volume echo exit) > %TEMP%\dp_vfy.txt diskpart /s %TEMP%\dp_vfy.txt >> C:\temp\restore_log.txt 2>&1 del %TEMP%\dp_vfy.txt 2>nul REM ────────────────────────────────────────────────────── REM DONE REM ────────────────────────────────────────────────────── echo. >> C:\temp\restore_log.txt echo ================================================== >> C:\temp\restore_log.txt echo COMPLETED: %date% %time% >> C:\temp\restore_log.txt echo ================================================== >> C:\temp\restore_log.txt echo. echo ======================================================== echo. echo FACTORY RESET COMPLETE! echo All applications, drivers, and settings restored. echo Log: C:\temp\restore_log.txt echo. echo Restarting in 15 seconds... echo. echo ======================================================== echo. echo 15... ping -n 4 127.0.0.1 > nul echo 12... ping -n 4 127.0.0.1 > nul echo 8... ping -n 5 127.0.0.1 > nul echo 3... ping -n 4 127.0.0.1 > nul wpeutil reboot "@ # ════════════════════════════════════════════════════════ # DIAGNOSE.CMD # ════════════════════════════════════════════════════════ $diagnoseScript = @" @echo off cls color 1F mkdir C:\temp 2>nul echo RECOVERY DIAGNOSTICS LOG > C:\temp\diagnose_log.txt echo %date% %time% >> C:\temp\diagnose_log.txt echo. echo ======================================================== echo RECOVERY DIAGNOSTICS (safe - no changes) echo Log: C:\temp\diagnose_log.txt echo ======================================================== echo. echo ── Current volumes ── echo. (echo list volume echo exit) > %TEMP%\dd1.txt diskpart /s %TEMP%\dd1.txt diskpart /s %TEMP%\dd1.txt >> C:\temp\diagnose_log.txt 2>&1 del %TEMP%\dd1.txt 2>nul echo. echo ── Partitions on Disk $diskNumber ── echo. (echo select disk $diskNumber echo list partition echo exit) > %TEMP%\dd2.txt diskpart /s %TEMP%\dd2.txt diskpart /s %TEMP%\dd2.txt >> C:\temp\diagnose_log.txt 2>&1 del %TEMP%\dd2.txt 2>nul echo. echo ── Expected layout ── echo Windows : Disk $diskNumber, Partition $cPartNum (C:) echo EFI : Disk $diskNumber, Partition $efiPartNum echo Recovery : Disk $recDiskNum, Partition $recPartNum echo. echo ── Scanning ALL drives for install.wim ── echo. echo Scanning... >> C:\temp\diagnose_log.txt if exist C:\$RecoveryFolderName\install.wim echo FOUND on C: if exist D:\$RecoveryFolderName\install.wim echo FOUND on D: if exist E:\$RecoveryFolderName\install.wim echo FOUND on E: if exist F:\$RecoveryFolderName\install.wim echo FOUND on F: if exist G:\$RecoveryFolderName\install.wim echo FOUND on G: if exist H:\$RecoveryFolderName\install.wim echo FOUND on H: if exist I:\$RecoveryFolderName\install.wim echo FOUND on I: if exist J:\$RecoveryFolderName\install.wim echo FOUND on J: if exist K:\$RecoveryFolderName\install.wim echo FOUND on K: if exist R:\$RecoveryFolderName\install.wim echo FOUND on R: echo. echo ── Trying diskpart mount (Disk $recDiskNum, Part $recPartNum as R:) ── echo. (echo select disk $recDiskNum echo select partition $recPartNum echo assign letter=R echo exit) > %TEMP%\dd3.txt diskpart /s %TEMP%\dd3.txt >> C:\temp\diagnose_log.txt 2>&1 del %TEMP%\dd3.txt 2>nul ping -n 5 127.0.0.1 > nul if exist R:\$RecoveryFolderName\install.wim ( echo FOUND on R: after diskpart mount DISM.exe /Get-ImageInfo /ImageFile:R:\$RecoveryFolderName\install.wim ) else ( echo NOT FOUND on R: after mount echo R: contents: dir R:\ 2>nul ) echo. echo ── Cleanup ── (echo select disk $recDiskNum echo select partition $recPartNum echo remove echo exit) > %TEMP%\dd4.txt diskpart /s %TEMP%\dd4.txt >> C:\temp\diagnose_log.txt 2>&1 del %TEMP%\dd4.txt 2>nul echo. echo ======================================================== echo DONE. Log: C:\temp\diagnose_log.txt echo. echo restore.cmd will auto-find the WIM on any drive letter. echo Just run: [drive]:\$RecoveryFolderName\restore.cmd echo ======================================================== echo. pause "@ # ════════════════════════════════════════════════════════ # QUICK_RESTORE.CMD # ════════════════════════════════════════════════════════ $quickRestoreScript = @" @echo off cls mkdir C:\temp 2>nul echo QUICK RESTORE %date% %time% > C:\temp\quick_restore_log.txt echo AUTOMATED FACTORY RESET... set RECOVERY_DRIVE= set WIM_FOUND=0 REM ── Scan all letters ── if exist C:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=C:& set WIM_FOUND=1& goto QF if exist D:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=D:& set WIM_FOUND=1& goto QF if exist E:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=E:& set WIM_FOUND=1& goto QF if exist F:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=F:& set WIM_FOUND=1& goto QF if exist G:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=G:& set WIM_FOUND=1& goto QF if exist H:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=H:& set WIM_FOUND=1& goto QF if exist R:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=R:& set WIM_FOUND=1& goto QF REM ── Diskpart mount ── (echo select disk $recDiskNum echo select partition $recPartNum echo assign letter=R echo exit) > %TEMP%\qm.txt diskpart /s %TEMP%\qm.txt >> C:\temp\quick_restore_log.txt 2>&1 del %TEMP%\qm.txt 2>nul ping -n 5 127.0.0.1 > nul if exist R:\$RecoveryFolderName\install.wim set RECOVERY_DRIVE=R:& set WIM_FOUND=1& goto QF echo FATAL: Not found! >> C:\temp\quick_restore_log.txt echo FATAL: Recovery image not found! pause exit /b 1 :QF echo Found on %RECOVERY_DRIVE% >> C:\temp\quick_restore_log.txt mkdir %RECOVERY_DRIVE%\temp 2>nul copy /y C:\temp\quick_restore_log.txt %RECOVERY_DRIVE%\temp\quick_restore_log.txt > nul 2>nul format C: /FS:NTFS /Q /Y /V:Windows if errorlevel 1 ( (echo select disk $diskNumber echo select partition $cPartNum echo format fs=ntfs quick label=Windows echo assign letter=C echo exit) > %TEMP%\qf.txt diskpart /s %TEMP%\qf.txt > nul 2>&1 del %TEMP%\qf.txt 2>nul ) mkdir C:\temp 2>nul copy /y %RECOVERY_DRIVE%\temp\quick_restore_log.txt C:\temp\quick_restore_log.txt > nul 2>nul DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C:\ if errorlevel 1 ( DISM.exe /Apply-Image /ImageFile:%RECOVERY_DRIVE%\$RecoveryFolderName\install.wim /Index:1 /ApplyDir:C: if errorlevel 1 ( echo DISM FAILED! >> C:\temp\quick_restore_log.txt echo CRITICAL: DISM failed! pause exit /b 1 ) ) (echo select disk $diskNumber echo select partition $efiPartNum echo assign letter=S echo exit) > %TEMP%\qe.txt diskpart /s %TEMP%\qe.txt > nul 2>&1 del %TEMP%\qe.txt 2>nul ping -n 3 127.0.0.1 > nul bcdboot C:\Windows /s S: /f UEFI > nul 2>&1 if exist %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml ( mkdir C:\Windows\Panther\unattend 2>nul copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend\unattend.xml copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\Panther\unattend.xml copy /y %RECOVERY_DRIVE%\$RecoveryFolderName\unattend.xml C:\Windows\System32\Sysprep\unattend.xml reg load HKLM\OFFLINE_SW C:\Windows\System32\config\SOFTWARE 2>nul reg add "HKLM\OFFLINE_SW\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f 2>nul reg add "HKLM\OFFLINE_SW\Policies\Microsoft\Windows\OOBE" /v DisablePrivacyExperience /t REG_DWORD /d 1 /f 2>nul reg unload HKLM\OFFLINE_SW 2>nul ) (echo select disk $diskNumber echo select partition $efiPartNum echo remove letter=S echo exit) > %TEMP%\qe2.txt diskpart /s %TEMP%\qe2.txt > nul 2>&1 del %TEMP%\qe2.txt 2>nul ping -n 3 127.0.0.1 > nul (echo select disk $recDiskNum echo select partition $recPartNum echo remove echo exit) > %TEMP%\qr1.txt diskpart /s %TEMP%\qr1.txt > nul 2>&1 del %TEMP%\qr1.txt 2>nul ping -n 3 127.0.0.1 > nul (echo select disk $recDiskNum echo select partition $recPartNum echo set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override echo exit) > %TEMP%\qr2.txt diskpart /s %TEMP%\qr2.txt > nul 2>&1 del %TEMP%\qr2.txt 2>nul ping -n 3 127.0.0.1 > nul (echo select disk $recDiskNum echo select partition $recPartNum echo gpt attributes=0x8000000000000001 echo exit) > %TEMP%\qr3.txt diskpart /s %TEMP%\qr3.txt > nul 2>&1 del %TEMP%\qr3.txt 2>nul echo COMPLETE >> C:\temp\quick_restore_log.txt wpeutil reboot "@ # ── Save ── $restoreScript | Out-File (Join-Path $RecoveryPath "restore.cmd") -Encoding ASCII -Force $diagnoseScript | Out-File (Join-Path $RecoveryPath "diagnose.cmd") -Encoding ASCII -Force $quickRestoreScript | Out-File (Join-Path $RecoveryPath "quick_restore.cmd") -Encoding ASCII -Force @" Recovery Partition Metadata =========================== Created : $(Get-Date) OS Disk : $diskNumber OS Partition : $cPartNum (C:, ~${osPartSize} GB) EFI Partition : $efiPartNum Recovery Disk : $recDiskNum Recovery Part : $recPartNum Folder : $RecoveryFolderName How restore.cmd finds the image: 1. Scans ALL drive letters C: through Z: 2. If not found, mounts Disk $recDiskNum Part $recPartNum as R: 3. Uses whichever letter has the WIM Re-hide uses separate diskpart calls: Step 1: remove (letter) Step 2: set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac Step 3: gpt attributes=0x8000000000000001 "@ | Out-File (Join-Path $RecoveryPath "recovery_metadata.txt") -Encoding ASCII -Force Write-Log "Created: restore.cmd" "SUCCESS" Write-Log "Created: diagnose.cmd" "SUCCESS" Write-Log "Created: quick_restore.cmd" "SUCCESS" Write-Host "" Write-Host " ┌──────────────────────────────────────────────────────────┐" -ForegroundColor Green Write-Host " │ ✅ Recovery Scripts Created │" -ForegroundColor Green Write-Host " │ │" -ForegroundColor Green Write-Host " │ Image search order: │" -ForegroundColor Green Write-Host " │ 1. Scan ALL drive letters (C: through Z:) │" -ForegroundColor Green Write-Host " │ 2. Diskpart mount Disk $recDiskNum Part $recPartNum as R:" -ForegroundColor Green Write-Host " │ 3. Use whichever letter has install.wim │" -ForegroundColor Green Write-Host " │ │" -ForegroundColor Green Write-Host " │ Works whether partition is D:, R:, or hidden! │" -ForegroundColor Green Write-Host " └──────────────────────────────────────────────────────────┘" -ForegroundColor Green return $true } # ============================================================ # STEP 3: CREATE DESKTOP SHORTCUTS # ============================================================ function Invoke-Step3_CreateShortcuts { param( [string]$RecoveryDrive, [string]$RecoveryPath ) Write-Log "STEP 3: CREATE DESKTOP SHORTCUTS" "STEP" $recPartition = Get-Partition -DriveLetter ($RecoveryDrive.TrimEnd(':')) $recPartNum = $recPartition.PartitionNumber $recDiskNum = $recPartition.DiskNumber $createShortcut = Read-Host " Create desktop shortcuts? (Y/N) [Y]" if ([string]::IsNullOrEmpty($createShortcut)) { $createShortcut = 'Y' } if ($createShortcut -ne 'Y' -and $createShortcut -ne 'y') { Write-Log "Desktop shortcuts skipped." "INFO" return $true } # ── PowerShell Launcher ── $launcherPS = @" #Requires -RunAsAdministrator Add-Type -AssemblyName System.Windows.Forms `$msg = "WARNING: This will ERASE ALL DATA on C: and restore the factory image with all original apps and drivers.`n`nALL personal files will be DELETED.`n`nProceed?" `$r = [System.Windows.Forms.MessageBox]::Show(`$msg, "FACTORY RESET", "YesNo", "Warning") if (`$r -eq "Yes") { `$r2 = [System.Windows.Forms.MessageBox]::Show( "FINAL CONFIRMATION`n`nComputer will restart into recovery.`nEstimated time: 20-40 minutes.`n`nContinue?", "CONFIRM FACTORY RESET", "YesNo", "Stop") if (`$r2 -eq "Yes") { Write-Host "Preparing recovery environment..." reagentc /enable 2>`$null reagentc /boottore shutdown /r /t 5 /c "Restarting for Factory Reset..." } } "@ # ── Batch Wrapper (auto-elevates) ── $batchWrapper = @" @echo off net session >nul 2>&1 if %errorlevel% neq 0 ( echo Requesting administrator privileges... powershell -Command "Start-Process cmd -ArgumentList '/c cd /d \"%~dp0\" && powershell -ExecutionPolicy Bypass -File \"%~dp0FactoryReset_Launcher.ps1\"' -Verb RunAs" exit /b ) powershell -ExecutionPolicy Bypass -File "%~dp0FactoryReset_Launcher.ps1" "@ # ── Instructions ── $instructions = @" FACTORY RESET INSTRUCTIONS =========================== AUTOMATIC (Windows boots normally): 1. Double-click "Factory Reset.bat" on Desktop 2. Confirm twice 3. PC restarts into recovery mode 4. In recovery CMD, mount R: and run restore: diskpart select disk $recDiskNum select partition $recPartNum assign letter=R exit R:\$RecoveryFolderName\restore.cmd MANUAL (SHIFT + Restart): 1. Hold SHIFT + click Restart 2. Troubleshoot → Command Prompt 3. Run the diskpart + restore commands above USB BOOT (Windows won't start): 1. Boot from Windows 11 USB 2. Repair → Command Prompt 3. Run the diskpart + restore commands above DIAGNOSTICS (safe test first): Mount R: then run: R:\$RecoveryFolderName\diagnose.cmd LOG FILES: C:\temp\restore_log.txt (after restore) C:\temp\diagnose_log.txt (after diagnostics) PARTITION INFO: Recovery: Disk $recDiskNum, Partition $recPartNum "@ # ── Save files ── $desktop = "$env:USERPROFILE\Desktop" $launcherPS | Out-File "$desktop\FactoryReset_Launcher.ps1" -Encoding UTF8 -Force $batchWrapper | Out-File "$desktop\Factory Reset.bat" -Encoding ASCII -Force $instructions | Out-File "$desktop\Factory Reset - Instructions.txt" -Encoding UTF8 -Force $instructions | Out-File (Join-Path $RecoveryPath "INSTRUCTIONS.txt") -Encoding UTF8 -Force Write-Host " ✓ Factory Reset.bat" -ForegroundColor Green Write-Host " ✓ FactoryReset_Launcher.ps1" -ForegroundColor Green Write-Host " ✓ Factory Reset - Instructions.txt" -ForegroundColor Green Write-Host " ✓ INSTRUCTIONS.txt (on recovery partition)" -ForegroundColor Green Write-Log "Desktop shortcuts created." "SUCCESS" return $true } # ============================================================ # STEP 4: HIDE PARTITION (Optional) # ============================================================ function Invoke-Step4_HidePartition { param([string]$RecoveryDrive) Write-Log "STEP 4: HIDE & PROTECT RECOVERY PARTITION" "STEP" $driveLetter = $RecoveryDrive.TrimEnd(':') # Find volume number $dpListFile = "$env:TEMP\dp_list.txt" "list volume" | Out-File -FilePath $dpListFile -Encoding ASCII $dpOutput = & diskpart /s $dpListFile 2>&1 Remove-Item $dpListFile -Force -ErrorAction SilentlyContinue $volumeNumber = $null foreach ($line in $dpOutput) { if ($line -match "Volume\s+(\d+)\s+$driveLetter\s") { $volumeNumber = $Matches[1] break } } if ($null -eq $volumeNumber) { Write-Log "Could not find volume for ${driveLetter}:." "ERROR" return $false } Write-Host "" Write-Host " This will:" -ForegroundColor Yellow Write-Host " • Remove drive letter ${driveLetter}:" -ForegroundColor White Write-Host " • Hide partition from File Explorer" -ForegroundColor White Write-Host " • Set GPT recovery attributes" -ForegroundColor White Write-Host " • Restore scripts will auto-mount it as R:" -ForegroundColor White Write-Host "" Write-Host " Volume $volumeNumber = ${driveLetter}:" -ForegroundColor Cyan Write-Host "" $confirm = Read-Host " Type 'HIDE' to proceed (anything else to cancel)" if ($confirm -eq 'HIDE') { # Separate operations for reliability # Remove letter $dp1 = @" select volume $volumeNumber remove letter=$driveLetter exit "@ $dp1 | Out-File "$env:TEMP\dp_h1.txt" -Encoding ASCII & diskpart /s "$env:TEMP\dp_h1.txt" Remove-Item "$env:TEMP\dp_h1.txt" -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 # Set partition type $dp2 = @" select volume $volumeNumber set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac override exit "@ $dp2 | Out-File "$env:TEMP\dp_h2.txt" -Encoding ASCII & diskpart /s "$env:TEMP\dp_h2.txt" Remove-Item "$env:TEMP\dp_h2.txt" -Force -ErrorAction SilentlyContinue Start-Sleep -Seconds 2 # Set GPT attributes $dp3 = @" select volume $volumeNumber gpt attributes=0x8000000000000001 exit "@ $dp3 | Out-File "$env:TEMP\dp_h3.txt" -Encoding ASCII & diskpart /s "$env:TEMP\dp_h3.txt" Remove-Item "$env:TEMP\dp_h3.txt" -Force -ErrorAction SilentlyContinue Write-Host "" Write-Host " ┌──────────────────────────────────────────────────┐" -ForegroundColor Green Write-Host " │ ✅ Partition hidden and protected! │" -ForegroundColor Green Write-Host " │ │" -ForegroundColor Green Write-Host " │ To unhide later: │" -ForegroundColor Green Write-Host " │ diskpart → list volume → select volume X → │" -ForegroundColor Green Write-Host " │ assign letter=R │" -ForegroundColor Green Write-Host " └──────────────────────────────────────────────────┘" -ForegroundColor Green Write-Log "Partition hidden successfully." "SUCCESS" return $true } Write-Log "Hide cancelled by user." "WARNING" return $false } # ============================================================ # MAIN # ============================================================ function Main { "Recovery Setup Log (Final) - $(Get-Date)" | Out-File -FilePath $LogFile -Force Show-Banner # ── Admin check ── $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Log "Must run as Administrator!" "ERROR" Write-Host " Right-click PowerShell → Run as Administrator" -ForegroundColor Yellow Read-Host " Press Enter to exit" exit 1 } Write-Log "Running with Administrator privileges." "SUCCESS" # ── Select recovery drive ── if ([string]::IsNullOrEmpty($RecoveryDriveLetter)) { $RecoveryDriveLetter = Get-RecoveryDrive if ($null -eq $RecoveryDriveLetter) { Write-Log "No recovery partition selected. Exiting." "ERROR" Read-Host " Press Enter to exit" exit 1 } } Write-Log "Selected recovery drive: $RecoveryDriveLetter" "INFO" # ── Prerequisites ── if (-not (Test-Prerequisites -RecoveryDrive $RecoveryDriveLetter)) { Write-Log "Prerequisites FAILED. Fix issues above and re-run." "ERROR" Read-Host " Press Enter to exit" exit 1 } Write-Log "All prerequisites PASSED." "SUCCESS" # ── Confirm ── Write-Host "" Write-Host " ┌──────────────────────────────────────────────────────────┐" -ForegroundColor Yellow Write-Host " │ Process: │" -ForegroundColor Yellow Write-Host " │ │" -ForegroundColor Yellow Write-Host " │ Step 1 : Capture WIM (VSS Shadow Copy) │" -ForegroundColor Yellow Write-Host " │ Step 1.5: Generate unattend.xml (silent OOBE) │" -ForegroundColor Yellow Write-Host " │ Step 2 : Create restore scripts (WinRE-compatible) │" -ForegroundColor Yellow Write-Host " │ Step 3 : Desktop shortcuts │" -ForegroundColor Yellow Write-Host " │ Step 4 : (Optional) Hide partition │" -ForegroundColor Yellow Write-Host " │ │" -ForegroundColor Yellow Write-Host " │ ✅ All apps, drivers, settings preserved on restore │" -ForegroundColor Yellow Write-Host " │ ⏱ Estimated: 30-75 minutes │" -ForegroundColor Yellow Write-Host " └──────────────────────────────────────────────────────────┘" -ForegroundColor Yellow Write-Host "" $proceed = Read-Host " Proceed? (Y/N)" if ($proceed -ne 'Y' -and $proceed -ne 'y') { Write-Log "User cancelled." "WARNING" exit 0 } $recoveryFolderPath = Join-Path $RecoveryDriveLetter $RecoveryFolderName # ═══════════════════════════════════ # STEP 1: Capture WIM # ═══════════════════════════════════ $step1 = Invoke-Step1_CaptureWIM ` -RecoveryDrive $RecoveryDriveLetter ` -FolderName $RecoveryFolderName ` -Name $ImageName ` -Description $ImageDescription ` -Compression $CompressionType if (-not $step1.Success) { Write-Log "Step 1 FAILED. Check DISM log." "ERROR" Write-Host " notepad C:\Windows\Logs\DISM\dism.log" -ForegroundColor Yellow Read-Host " Press Enter to exit" exit 1 } # ═══════════════════════════════════ # STEP 1.5: Unattend.xml # ═══════════════════════════════════ Write-Host "" $doUnattend = Read-Host " Configure silent recovery (skip OOBE screens)? (Y/N) [Y]" if ([string]::IsNullOrEmpty($doUnattend)) { $doUnattend = 'Y' } if ($doUnattend -eq 'Y' -or $doUnattend -eq 'y') { $settings = Get-UnattendSettings Save-UnattendXml -RecoveryPath $recoveryFolderPath -Settings $settings } else { Write-Log "Unattend.xml skipped. OOBE screens will appear on restore." "INFO" } # ═══════════════════════════════════ # STEP 2: Recovery Scripts # ═══════════════════════════════════ Invoke-Step2_CreateRecoveryScripts ` -RecoveryDrive $RecoveryDriveLetter ` -RecoveryPath $recoveryFolderPath ` -WimFile $step1.WimFile # ═══════════════════════════════════ # STEP 3: Desktop Shortcuts # ═══════════════════════════════════ Invoke-Step3_CreateShortcuts ` -RecoveryDrive $RecoveryDriveLetter ` -RecoveryPath $recoveryFolderPath # ═══════════════════════════════════ # STEP 4: Hide Partition # ═══════════════════════════════════ Write-Host "" Write-Host " ┌──────────────────────────────────────────────────────────┐" -ForegroundColor Cyan Write-Host " │ Hide recovery partition? │" -ForegroundColor Cyan Write-Host " │ │" -ForegroundColor Cyan Write-Host " │ [Y] Production — hidden from Explorer, auto-mounted │" -ForegroundColor Cyan Write-Host " │ [N] Testing — keep visible for debugging │" -ForegroundColor Cyan Write-Host " └──────────────────────────────────────────────────────────┘" -ForegroundColor Cyan Write-Host "" $hide = Read-Host " (Y/N)" if ($hide -eq 'Y' -or $hide -eq 'y') { Invoke-Step4_HidePartition -RecoveryDrive $RecoveryDriveLetter } else { Write-Log "Partition not hidden (testing mode)." "INFO" } # ═══════════════════════════════════ # COMPLETE # ═══════════════════════════════════ Write-Host "" Write-Host " ╔════════════════════════════════════════════════════════════╗" -ForegroundColor Green Write-Host " ║ ✅ RECOVERY PARTITION SETUP COMPLETE! ║" -ForegroundColor Green Write-Host " ╠════════════════════════════════════════════════════════════╣" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ║ Recovery Partition Contents: ║" -ForegroundColor Green Write-Host " ║ ├── install.wim (full system image) ║" -ForegroundColor Green Write-Host " ║ ├── unattend.xml (silent OOBE) ║" -ForegroundColor Green Write-Host " ║ ├── restore.cmd (interactive restore + log) ║" -ForegroundColor Green Write-Host " ║ ├── diagnose.cmd (safe testing) ║" -ForegroundColor Green Write-Host " ║ ├── quick_restore.cmd (automated) ║" -ForegroundColor Green Write-Host " ║ ├── recovery_metadata.txt (partition info) ║" -ForegroundColor Green Write-Host " ║ └── INSTRUCTIONS.txt (how to restore) ║" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ║ Desktop Files: ║" -ForegroundColor Green Write-Host " ║ ├── Factory Reset.bat ║" -ForegroundColor Green Write-Host " ║ ├── FactoryReset_Launcher.ps1 ║" -ForegroundColor Green Write-Host " ║ └── Factory Reset - Instructions.txt ║" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ║ HOW TO RESTORE: ║" -ForegroundColor Green Write-Host " ║ 1. SHIFT+Restart → Troubleshoot → CMD ║" -ForegroundColor Green Write-Host " ║ 2. Mount R: (diskpart commands in Instructions.txt) ║" -ForegroundColor Green Write-Host " ║ 3. R:\$RecoveryFolderName\diagnose.cmd (test first)" -ForegroundColor Green Write-Host " ║ 4. R:\$RecoveryFolderName\restore.cmd (restore)" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ║ LOG FILES (after restore): ║" -ForegroundColor Green Write-Host " ║ C:\temp\restore_log.txt ║" -ForegroundColor Green Write-Host " ║ C:\temp\diagnose_log.txt ║" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ║ Setup log: $LogFile" -ForegroundColor Green Write-Host " ║ ║" -ForegroundColor Green Write-Host " ╚════════════════════════════════════════════════════════════╝" -ForegroundColor Green Write-Host "" Write-Log "Setup completed successfully." "SUCCESS" Read-Host " Press Enter to exit" } # ── Run ── Main
Phase 3: How the Restore Scripts Work
How to Perform a Factory Reset
Method 1: From Windows (System Boots Normally)
1. Hold SHIFT + click "Restart" 2. Troubleshoot → Command Prompt 3. Mount the recovery partition: diskpart select disk 0 select partition 5 ← your recovery partition number assign letter=R exit 4. Run: R:\RecoveryImage\restore.cmdMethod 2: From USB (Windows Won’t Boot)
1. Boot from Windows 11 installation USB 2. Click "Repair your computer" 3. Troubleshoot → Command Prompt 4. Same diskpart + restore commands as aboveWhat restore.cmd Does
Step 1: FIND the recovery image ├── Scans C: through Z: for RecoveryImage\install.wim ├── If found (e.g., on D:) → uses that drive └── If not found → diskpart mounts known partition as R: Step 2: FORMAT C: drive ├── format C: /FS:NTFS /Q /Y └── Fallback: diskpart format Step 3: APPLY image └── DISM.exe /Apply-Image /ImageFile:[drive]:\RecoveryImage\install.wim /Index:1 /ApplyDir:C:\ Step 4: REBUILD boot ├── Assign S: to EFI partition ├── bcdboot C:\Windows /s S: /f UEFI └── Remove S: Step 5: APPLY unattend.xml ├── Copy to C:\Windows\Panther\unattend\ ├── Copy to C:\Windows\System32\Sysprep\ └── Inject BypassNRO into offline registry Step 6: RE-HIDE recovery partition ├── Call 1: remove (drive letter) ├── Call 2: set id=de94bba4... (recovery type) └── Call 3: gpt attributes=0x80... (protected) Reboot → Windows starts → Silent OOBE → Desktop → Done!Log Files
After a restore, check these logs:
Log Location Purpose Restore log C:\temp\restore_log.txtEvery step with timestamps DISM log C:\Windows\Logs\DISM\dism.logDISM operation details Setup log Desktop\RecoverySetup_Log.txtInitial script setup
Phase 4: Testing & Validation
Pre-Restore Test (Safe)
1. Boot into WinRE (SHIFT + Restart) 2. Command Prompt 3. Mount recovery partition 4. Run: R:\RecoveryImage\diagnose.cmd (Makes no changes — just verifies everything)Post-Restore Verification Checklist
After restoring, verify: [ ] Windows boots successfully [ ] All applications are present (Start Menu) [ ] All drivers work (Device Manager — no yellow triangles) [ ] Network connectivity works [ ] User account was auto-created (if unattend configured) [ ] No OOBE setup screens appeared (if unattend configured) [ ] C:\temp\restore_log.txt shows all steps succeeded [ ] Recovery partition is hidden (not visible in Explorer)
Lessons Learned & Pitfalls
Pitfall 1: “ClientAccessible” Typo
❌ "ClientAccesible" (one 's') → VSS returns error 5 ✅ "ClientAccessible" (two 's') → WorksPitfall 2: DISM Error 0x80070020
❌ DISM /Capture-Image /CaptureDir:C:\ → Files locked by running OS ✅ VSS Shadow Copy → Mount → DISM captures from snapshotPitfall 3: “Reset this PC” Strips Apps
❌ reagentc /setosimage → "Reset this PC" → Apps removed ✅ DISM /Apply-Image → Byte-for-byte restore → Everything preservedPitfall 4: DISM Error 87 in WinRE
❌ setlocal EnableDelayedExpansion + !var! → Empty in WinRE ✅ Hardcoded paths, no delayed expansionPitfall 5: timeout Doesn’t Exist in WinRE
❌ timeout /t 3 /nobreak > nul → Command not found ✅ ping -n 4 127.0.0.1 > nul → 3-second delayPitfall 6: WinRE Reassigns Drive Letters
❌ Assume R: = Recovery → WinRE assigns D: instead ✅ Scan ALL letters C-Z first, then diskpart fallbackPitfall 7: Combined DiskPart Re-hide Fails
❌ remove letter=R (fails if letter is D:) → set id skipped ✅ Separate calls: remove (no letter specified) → set id → gpt attributesPitfall 8: Log Lost When C: Formatted
❌ Log on C: → format C: → Log gone ✅ Copy log to recovery drive before format, restore after
FAQ
Q: How much space does the recovery partition need? A: About 70% of your used space on C:. If you have 40 GB used, you need ~28 GB for the WIM file. Plan for 30-50 GB.
Q: Can I update the recovery image later? A: Yes! Re-run the PowerShell script. When it asks about the existing WIM, choose “Y” to overwrite with a fresh capture.
Q: Does this work with BitLocker? A: You’ll need to suspend BitLocker before capturing and ensure the recovery environment can access the drives. The restore scripts don’t handle BitLocker decryption.
Q: Can I deploy this to multiple identical PCs? A: For multi-PC deployment, add Sysprep generalization before capture:
C:\Windows\System32\Sysprep\sysprep.exe /generalize /oobe /shutdownThen boot from WinPE and capture.Q: What if the recovery partition gets deleted? A: The WIM file is gone. Always keep a backup copy on an external drive. You can recreate the partition and re-run the script.
Q: Why not use WinPE USB for capture instead of VSS? A: You can! Boot from WinPE USB, then run:
DISM /Capture-Image /ImageFile:R:\RecoveryImage\install.wim /CaptureDir:C:\ /Name:Recovery /Compress:maximumNo file locks from WinPE. VSS is the “no reboot needed” alternative.
License
This script is provided as-is for educational purposes. Test thoroughly before deploying in production environments. Always maintain backups before performing system modifications.
How to Use This Blog Post
-
Copy everything between the outer `
markdown ` and `` tags - Paste into your blog editor (WordPress, Ghost, Hugo, Jekyll, etc.)
- The code blocks inside will render correctly since they use indented fences
- The complete PowerShell script is included inline — readers can copy-paste directly
- All diagrams use ASCII art — no external images needed
-
Driver Update script
Driver update script thats called using Autounattended xml file
Driver update from Networkshare
Execution flow
flowchart TB START([🚀 Script Start]) --> INIT[Initialize-PersistentScript<br/>Copy script to C:\Temp\] INIT -->|Failed| FAIL_INIT([❌ Throw Error]) INIT -->|Success| TRANSCRIPT[Start-Transcript<br/>C:\Temp\DriverUpdateScript.log] TRANSCRIPT --> HEADER[Display Header<br/>User, Computer, Model, Config] HEADER --> ORPHAN[Stop Orphan Driver Processes<br/>drvinst, pnputil, DPInst, etc.] ORPHAN --> LOADSTATE[Get-ScriptState<br/>Load or create state JSON] LOADSTATE --> SHOWSTATE[Display Current State<br/>All properties] SHOWSTATE --> CHECKPHASE{state.Phase?} %% ============================================================ %% PHASE 0 %% ============================================================ CHECKPHASE -->|Phase 0| USB_CHECK{USBCompleted?} subgraph PHASE0A ["🔌 PHASE-0A: USB Network Drivers"] USB_CHECK -->|No| USB_FIND[Scan drives D: - Z:<br/>Look for DriversOS / Drivers folder] USB_FIND -->|Not Found| USB_SKIP[No USB folder found<br/>Skip USB phase] USB_FIND -->|Found| USB_INSTALL[Install-DriversFromFolder<br/>SkipDeviceMatching = TRUE<br/>Install ALL drivers from USB] USB_INSTALL --> USB_EACH[For each INF file:<br/>Install-SingleDriver<br/>pnputil /add-driver /install] USB_EACH --> USB_RESULT{NeedsReboot?} USB_RESULT -->|Yes| USB_REBOOT[Schedule-RebootAndContinue<br/>Set RunOnce registry<br/>Reboot in 15 seconds] USB_REBOOT --> REBOOT_USB([🔄 REBOOT]) REBOOT_USB -.->|After reboot| USB_CHECK USB_RESULT -->|No| USB_DONE[USBCompleted = true<br/>USBRebootDone = true] USB_SKIP --> USB_DONE end USB_CHECK -->|Yes, not rebooted| USB_FINALIZE[Post-reboot:<br/>USB drivers finalized] USB_FINALIZE --> NET_CHECK USB_CHECK -->|Yes, rebooted| NET_CHECK USB_DONE --> NET_CHECK subgraph PHASE0B ["📡 PHASE-0B: Network Share Drivers"] NET_CHECK{NetworkShare<br/>Completed?} NET_CHECK -->|No| GUEST_CHECK{GuestAuth<br/>Configured?} subgraph GUESTAUTH ["🔓 SMB Guest Access Fix"] GUEST_CHECK -->|No| GUEST_POLICY[Create policy key if missing<br/>HKLM:\SOFTWARE\Policies\<br/>Microsoft\Windows\LanmanWorkstation] GUEST_POLICY --> GUEST_INSECURE[AllowInsecureGuestAuth = 1<br/>Policy + Service level] GUEST_INSECURE --> GUEST_SIGN1[RequireSecuritySignature = 0<br/>Disable 'always sign'] GUEST_SIGN1 --> GUEST_SIGN2[EnableSecuritySignature = 0<br/>Disable 'sign if server agrees'] GUEST_SIGN2 --> GUEST_RESTART[Restart LanmanWorkstation<br/>+ dependent services] GUEST_RESTART --> GUEST_VERIFY[Verify: Read back all 4 values<br/>Display OK/FAIL table] GUEST_VERIFY --> SYSINFO end GUEST_CHECK -->|Yes| SYSINFO subgraph MODELPATH ["🏷️ Model Detection & Path Building"] SYSINFO[Get-SystemInfo<br/>Manufacturer + Model] SYSINFO --> CLEANMODEL["Clean model name<br/>'Latitude 5400' → 'latitude5400'"] CLEANMODEL --> MFGFOLDER["Map manufacturer → folder<br/>'Dell Inc.' → 'dell'"] MFGFOLDER --> BUILDPATH["Build UNC path<br/>\\networkshare\dell\driver\latitude5400"] end BUILDPATH --> TESTSHARE[Test-NetworkShareAccess<br/>10 retries × 10s delay] TESTSHARE -->|Accessible| DEVSCAN TESTSHARE -->|Failed| TRYALT["Try alternative names:<br/>latitude-5400<br/>latitude_5400<br/>latitude 5400"] TRYALT -->|Found| DEVSCAN TRYALT -->|All failed| NET_NOTFOUND[No share folder found<br/>Skip network share phase] subgraph DEVMATCH ["🔍 Device Matching"] DEVSCAN[Get-SystemDeviceIds<br/>WMI Win32_PnPEntity<br/>Fallback: Get-PnpDevice] DEVSCAN --> MATCHINFS["For each INF:<br/>Parse [Manufacturer] + [Models]<br/>Extract hardware IDs"] MATCHINFS --> COMPARE["Compare INF IDs vs System IDs<br/>HashSet O(1) lookup"] COMPARE --> MATCHRESULT["Display results:<br/>Matched / Skipped / Mode"] end MATCHRESULT --> NETINSTALL[Install-DriversFromFolder<br/>Only MATCHED drivers] NETINSTALL --> NET_EACH["For each matched INF:<br/>Install-SingleDriver<br/>pnputil /add-driver /install<br/>Timeout monitoring<br/>Spinner animation"] NET_EACH --> NET_RESULT{NeedsReboot?} NET_RESULT -->|Yes| NET_REBOOT[Schedule-RebootAndContinue] NET_REBOOT --> REBOOT_NET([🔄 REBOOT]) REBOOT_NET -.->|After reboot| NET_CHECK NET_RESULT -->|No| NET_DONE[NetworkShareCompleted = true<br/>Phase = 1] NET_NOTFOUND --> NET_DONE end NET_CHECK -->|Yes, not rebooted| NET_FINALIZE[Post-reboot:<br/>Share drivers finalized<br/>Phase = 1] NET_CHECK -->|Yes, rebooted| PHASE1_START NET_FINALIZE --> PHASE1_START NET_DONE --> PHASE1_START %% ============================================================ %% PHASE 1 %% ============================================================ CHECKPHASE -->|Phase ≥ 1| PHASE1_START subgraph PHASE1 ["🌐 PHASE-1: Online OEM Updates"] PHASE1_START[PHASE 1: ONLINE OEM UPDATES] PHASE1_START --> INET[Test-InternetConnectivity<br/>15 retries × 10s delay<br/>HEAD https://google.com] INET -->|No Internet| INET_FAIL([❌ Exit 1<br/>No internet]) INET -->|Connected| CHOCO subgraph CHOCO_SETUP ["🍫 Chocolatey Setup"] CHOCO[Install-ChocolateyIfNeeded] CHOCO --> CHOCO_SRC["Set-ChocolateySource<br/>Add: chocosia → choco.local.xyz.com<br/>Disable: default community source"] end CHOCO_SRC --> MFG_DETECT[Get-SystemManufacturer<br/>Win32_ComputerSystem] MFG_DETECT --> MFG_CHECK{Manufacturer?} subgraph DELL ["🖥️ Dell Updates"] MFG_CHECK -->|Dell| DELL_CLI[Locate dcu-cli.exe<br/>or install via Chocolatey] DELL_CLI -->|Not found| DELL_CHOCO["Install-ChocoPackage<br/>'dellcommandupdate'<br/>Invoke-CommandVerbose"] DELL_CLI -->|Found| DELL_PHASE DELL_CHOCO --> DELL_PHASE DELL_PHASE{ManufacturerPhase?} DELL_PHASE -->|0| DELL_ADR["Dell Phase-0: ADR<br/>Configure ADR enable<br/>dcu-cli /driverinstall<br/>Verbose output streaming"] DELL_ADR --> DELL_ADR_EXIT{Exit code?} DELL_ADR_EXIT -->|0, 3| DELL_P2 DELL_ADR_EXIT -->|1, 5| DELL_ADR_REBOOT[Reboot required] DELL_ADR_REBOOT --> REBOOT_DELL([🔄 REBOOT]) REBOOT_DELL -.-> DELL_PHASE DELL_ADR_EXIT -->|2, other| DELL_ADR_FAIL[ADR failed<br/>Skip to Phase-2] DELL_ADR_FAIL --> DELL_P2 DELL_PHASE -->|1| DELL_SCAN["Dell Phase-1: Scan<br/>dcu-cli /scan"] DELL_SCAN --> DELL_P2 DELL_P2["Dell Phase-2: Apply Updates<br/>dcu-cli /applyupdates<br/>-forceUpdate=enable<br/>-autoSuspendBitLocker=enable<br/>Verbose: each driver shown"] DELL_PHASE -->|2| DELL_P2 DELL_P2 --> DELL_P2_EXIT{Exit code?} DELL_P2_EXIT -->|0, 3| DELL_DONE[Dell updates complete] DELL_P2_EXIT -->|1, 5| DELL_P2_REBOOT[Reboot required] DELL_P2_REBOOT --> REBOOT_DELL2([🔄 REBOOT]) REBOOT_DELL2 -.-> DELL_PHASE DELL_P2_EXIT -->|other| DELL_DONE DELL_PHASE -->|3+| DELL_DONE end subgraph HP ["🖨️ HP Updates"] MFG_CHECK -->|HP| HP_CLI[Locate HPImageAssistant.exe<br/>or install via Chocolatey] HP_CLI -->|Not found| HP_CHOCO["Install-ChocoPackage<br/>'hpimageassistant'<br/>Invoke-CommandVerbose"] HP_CLI -->|Found| HP_RUN HP_CHOCO --> HP_RUN HP_RUN["HP Image Assistant<br/>/Operation:Analyze<br/>/Action:Install<br/>/Selection:All<br/>Verbose output streaming"] HP_RUN --> HP_EXIT{Exit code?} HP_EXIT -->|0| HP_DONE[HP updates complete] HP_EXIT -->|256, 3010| HP_REBOOT_WARN[Reboot recommended] HP_EXIT -->|other| HP_CONTINUE[Continue anyway] HP_REBOOT_WARN --> HP_DONE HP_CONTINUE --> HP_DONE end MFG_CHECK -->|Other| MFG_UNSUPPORTED[Unsupported manufacturer<br/>Only Dell & HP handled] end %% ============================================================ %% FINAL %% ============================================================ DELL_DONE --> FINAL HP_DONE --> FINAL MFG_UNSUPPORTED --> FINAL subgraph CLEANUP ["✅ Final Cleanup"] FINAL[Stop remaining driver processes] FINAL --> SUMMARY["Display Final Summary<br/>System, Model, Share path<br/>Reboots, Added, Installed<br/>Skipped, Failed, Timed out<br/>Total time"] SUMMARY --> REMOVESTATE["Remove-ScriptState<br/>Delete state JSON<br/>Delete RunOnce registry<br/>Delete scheduled task"] end REMOVESTATE --> DONE([✅ Script Complete]) %% ============================================================ %% ERROR HANDLING %% ============================================================ FAIL_INIT --> CATCH subgraph ERRORHANDLER ["❌ Error Handler"] CATCH[Catch block<br/>Display error message + line<br/>Display stack trace] CATCH --> EMERGENCY[Emergency cleanup<br/>Stop all driver processes<br/>ErrorAction = SilentlyContinue] EMERGENCY --> KEEPSTATE[State file kept<br/>Re-run script to continue] end KEEPSTATE --> FINALLY DONE --> FINALLY FINALLY[Finally block<br/>Stop-Transcript] %% ============================================================ %% INSTALL-SINGLEDRIVER DETAIL %% ============================================================ subgraph SINGLEDRIVER ["⚙️ Install-SingleDriver (called per INF)"] direction TB SD_START[Write driver header<br/>Progress bar + name] --> SD_CLEAN[Clear-DriverInstallEnvironment<br/>Kill leftover processes] SD_CLEAN --> SD_WRAPPER["Create wrapper .ps1<br/>Runs pnputil in child process"] SD_WRAPPER --> SD_SPAWN["Start-Process powershell<br/>-WindowStyle Hidden"] SD_SPAWN --> SD_MONITOR["Monitor loop:<br/>Spinner animation<br/>Elapsed/Remaining timer<br/>Check timeout"] SD_MONITOR --> SD_TIMEOUT{Timed out?} SD_TIMEOUT -->|Yes| SD_KILL["Kill process<br/>Stop-DriverInstallProcesses<br/>Return TimedOut result"] SD_TIMEOUT -->|No| SD_READ["Read output file<br/>Read exit code file"] SD_READ --> SD_PARSE["Parse pnputil output:<br/>added / installed /<br/>already exists / reboot"] SD_PARSE --> SD_EXIT["Evaluate exit code:<br/>0=OK, 1=warn, 259=staged<br/>3010=reboot, 482=partial"] SD_EXIT --> SD_RETURN[Return result object] end %% ============================================================ %% INVOKE-COMMANDVERBOSE DETAIL %% ============================================================ subgraph VERBOSE ["📺 Invoke-CommandVerbose (used by Dell/HP/Choco)"] direction TB V_START[Start process with<br/>RedirectStdout + Stderr] --> V_LOOP["Async ReadLineAsync loop:<br/>stdout → White text<br/>stderr → Yellow text<br/>Each line timestamped"] V_LOOP --> V_HEARTBEAT["If silent >30s:<br/>Print heartbeat message<br/>'...still running (120s)'"] V_HEARTBEAT --> V_DONE["Process exits<br/>Save full log to C:\Temp\<br/>Display duration + exit code"] end %% ============================================================ %% STYLING %% ============================================================ classDef phase fill:#c9f1f5,stroke:#e94560,color:#fff,stroke-width:2px classDef success fill:#0bd979,stroke:#00ff88,color:#fff,stroke-width:2px classDef reboot fill:#533483,stroke:#ffcc00,color:#fff,stroke-width:2px classDef error fill:#4a0000,stroke:#ff0000,color:#fff,stroke-width:2px classDef decision fill:#26948b,stroke:#0f3460,color:#fff,stroke-width:1px classDef process fill:#c9f1f5,stroke:#e2e2e2,color:#fff,stroke-width:1px class START,DONE success class REBOOT_USB,REBOOT_NET,REBOOT_DELL,REBOOT_DELL2 reboot class FAIL_INIT,INET_FAIL,CATCH error class CHECKPHASE,USB_CHECK,NET_CHECK,GUEST_CHECK,MFG_CHECK,DELL_PHASE,USB_RESULT,NET_RESULT,DELL_ADR_EXIT,DELL_P2_EXIT,HP_EXIT,SD_TIMEOUT decisionReboot Flow
sequenceDiagram participant S as Script participant R as Registry RunOnce participant W as Windows S->>S: Save state to JSON S->>R: Write RunOnce entry (64-bit) alt RunOnce failed S->>W: Create Scheduled Task (fallback) end S->>W: Restart-Computer -Force W->>W: Reboot W->>R: Read RunOnce entry R->>S: Launch script automatically S->>S: Load state from JSON S->>S: Resume from saved PhaseNetwork Share Folder Structure
graph LR SHARE["\\networkshare"] --> DELL[dell/] SHARE --> HP[hp/] DELL --> DDRIVER[driver/] HP --> HDRIVER[driver/] DDRIVER --> LAT5400[latitude5400/] DDRIVER --> PREC3500[precision3500/] DDRIVER --> OPT7080[optiplex7080/] HDRIVER --> ELITE840[elitebook840g7/] HDRIVER --> PRO400[prodesk400g6/] LAT5400 --> AUDIO[Audio/] LAT5400 --> VIDEO[Video/] LAT5400 --> CHIP[Chipset/] AUDIO --> INF1[realtek.inf] VIDEO --> INF2[igdlh64.inf] CHIP --> INF3[SetupChipset.inf] style SHARE fill:#e94560,color:#fff style DELL fill:#0f3460,color:#fff style HP fill:#0f3460,color:#fff style LAT5400 fill:#533483,color:#fffUSB Stick Structure (Minimal)
graph LR USB["USB Drive E:\"] --> DOS[DriversOS/] DOS --> INTEL_LAN["Intel-I219LM/"] DOS --> INTEL_WIFI["Intel-AX211/"] DOS --> REALTEK["Realtek-RTL8111/"] INTEL_LAN --> INF1["e1d68x64.inf"] INTEL_WIFI --> INF2["netwtw10.inf"] REALTEK --> INF3["rt640x64.inf"] style USB fill:#e94560,color:#fff style DOS fill:#533483,color:#fffPowershell Script That searches the network share for drivers
#Requires -RunAsAdministrator Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' #=================================================================== # CONFIGURATION #=================================================================== $Script:TempFolder = "C:\Temp" $Script:PersistentScript = "C:\Temp\DriverUpdateScript.ps1" $Script:StateFile = "C:\Temp\DriverUpdateState.json" $Script:LogFile = "C:\Temp\DriverUpdateScript.log" $Script:RunOnceKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' $Script:RunOnceName = 'DriverUpdateScript_RunOnce' $Script:DriverTimeout = 300 $Script:CooldownSeconds = 5 $Script:DriverProcesses = @('drvinst','pnputil','DPInst','DPInst64','SetupAPI') $Script:SpinnerFrames = @('[ ]','[= ]','[== ]','[=== ]','[====]','[ ===]','[ ==]','[ =]') $Script:ChocoSourceName = 'chocosia' $Script:ChocoSourceUrl = 'http://choco.local.iut-troyes.univ-reims.fr/repository/chocolatey-group' $Script:NetworkShareBase = '\\synosia.local.iut-troyes.univ-reims.fr\batchs' $Script:ShareDriverFolder = 'driver' $Script:ManufacturerShareMap = @{ 'Dell' = 'dell' 'Dell Inc.' = 'dell' 'HP' = 'hp' 'Hewlett-Packard' = 'hp' } $Script:USBDriverFolders = @('DriversOS','Drivers') #=================================================================== # LOGGING HELPERS #=================================================================== function Append-ToLog { param([Parameter(Mandatory=$false)][AllowEmptyString()][string]$Message = "") # No-op: Start-Transcript captures all console output automatically } function Write-Status { param( [Parameter(Mandatory)] [ValidateSet('ERROR','WARN','INFO','STEP','OK','PROGRESS','DEBUG','DRIVER','TIME','SKIP','KILL')] [string]$Level, [Parameter(Mandatory)][string]$Message ) $timestamp = Get-Date -Format "HH:mm:ss" $color = switch ($Level.ToUpper()) { 'ERROR' { 'Red' } 'WARN' { 'Yellow' } 'INFO' { 'Cyan' } 'STEP' { 'Magenta' } 'OK' { 'Green' } 'PROGRESS' { 'White' } 'DEBUG' { 'DarkGray' } 'DRIVER' { 'Blue' } 'TIME' { 'DarkYellow' } 'SKIP' { 'DarkMagenta' } 'KILL' { 'Red' } default { 'White' } } $prefix = switch ($Level.ToUpper()) { 'ERROR' { '[X]' } 'WARN' { '[!]' } 'INFO' { '[i]' } 'STEP' { '[>]' } 'OK' { '[+]' } 'PROGRESS' { '[o]' } 'DEBUG' { '[.]' } 'DRIVER' { '[D]' } 'TIME' { '[T]' } 'SKIP' { '[S]' } 'KILL' { '[K]' } default { '[ ]' } } $line = "[$timestamp] $prefix [$Level] $Message" Write-Host $line -ForegroundColor $color } function Write-Banner { param([string]$Text) $sep = "=" * 70 Write-Host "" Write-Host $sep -ForegroundColor Magenta Write-Host " $Text" -ForegroundColor Magenta Write-Host $sep -ForegroundColor Magenta Write-Host "" } function Write-SubBanner { param([string]$Text) Write-Host "" Write-Host "--- $Text ---" -ForegroundColor Yellow Write-Host "" } function Write-LoggedHost { param( [Parameter(Mandatory=$false)][AllowEmptyString()][string]$Message = "", [string]$ForegroundColor = 'White' ) if ($Message) { Write-Host $Message -ForegroundColor $ForegroundColor } else { Write-Host "" } } function Format-Duration { param([double]$Seconds) if ($Seconds -lt 60) { return "{0:N1}s" -f $Seconds } elseif ($Seconds -lt 3600) { $m = [math]::Floor($Seconds / 60); $s = $Seconds % 60 return "{0}m {1:N0}s" -f $m, $s } else { $h = [math]::Floor($Seconds / 3600); $m = [math]::Floor(($Seconds % 3600) / 60) return "{0}h {1}m" -f $h, $m } } function Get-ConsoleWidth { $w = 100 try { $w = [Console]::WindowWidth - 5; if ($w -lt 50) { $w = 100 } } catch { } return $w } function Write-DriverHeader { param([string]$DriverName, [int]$Current, [int]$Total) if ($Total -le 0) { $Total = 1 } $pct = [math]::Round(($Current / $Total) * 100) $filled = [math]::Floor($pct / 5) $empty = 20 - $filled $bar = "[" + ("#" * $filled) + ("-" * $empty) + "]" $lines = @( " ======================================================================" " $bar $pct% ($Current of $Total drivers)" " Driver: $DriverName" " Timeout: $Script:DriverTimeout seconds" " ======================================================================" ) Write-Host "" foreach ($l in $lines) { Write-Host $l -ForegroundColor DarkCyan } } #=================================================================== # INITIALISATION #=================================================================== function Initialize-PersistentScript { if (-not (Test-Path $Script:TempFolder)) { New-Item -Path $Script:TempFolder -ItemType Directory -Force | Out-Null } $sessionHeader = @( "" "==================================================================" " NEW SESSION: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" " PID: $PID | User: $env:USERNAME | Computer: $env:COMPUTERNAME" "==================================================================" "" ) foreach ($line in $sessionHeader) { Write-Host $line -ForegroundColor DarkGray } $currentScript = $MyInvocation.ScriptName if (-not $currentScript) { $currentScript = $PSCommandPath } if ($currentScript -and $currentScript -ne $Script:PersistentScript) { if (Test-Path $currentScript) { Write-LoggedHost "[INIT] Copying script to $Script:PersistentScript" -ForegroundColor Yellow Copy-Item -Path $currentScript -Destination $Script:PersistentScript -Force Write-LoggedHost "[INIT] Script copied successfully." -ForegroundColor Green } } if (-not (Test-Path $Script:PersistentScript)) { Write-LoggedHost "[ERROR] Persistent script could not be created." -ForegroundColor Red return $false } return $true } #=================================================================== # PROCESS-HANDLING HELPERS #=================================================================== function Get-DriverInstallProcesses { [System.Collections.ArrayList]$collector = @() foreach ($name in $Script:DriverProcesses) { $found = $null try { $found = Get-Process -Name $name -ErrorAction SilentlyContinue } catch { } if ($null -ne $found) { foreach ($p in @($found)) { [void]$collector.Add($p) } } } $arr = [object[]]$collector.ToArray() return ,$arr } function Stop-DriverInstallProcesses { param([switch]$Silent) $killed = 0 foreach ($name in $Script:DriverProcesses) { $found = $null try { $found = Get-Process -Name $name -ErrorAction SilentlyContinue } catch { } if ($null -ne $found) { foreach ($proc in @($found)) { if (-not $Silent) { Write-Status KILL "Terminating: $($proc.ProcessName) (PID $($proc.Id))" } try { $proc.CloseMainWindow() | Out-Null } catch { } Start-Sleep -Milliseconds 300 try { if (-not $proc.HasExited) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } } catch { } $killed++ } } } foreach ($name in $Script:DriverProcesses) { try { $saveEA = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' $null = cmd.exe /c "taskkill /F /IM `"$name.exe`" >nul 2>&1" $ErrorActionPreference = $saveEA } catch { try { $ErrorActionPreference = $saveEA } catch { } } } return $killed } function Wait-ForDriverProcesses { param([int]$TimeoutSeconds = 30) $sw = [System.Diagnostics.Stopwatch]::StartNew() while ($sw.Elapsed.TotalSeconds -lt $TimeoutSeconds) { $procs = Get-DriverInstallProcesses $procCount = ($procs | Measure-Object).Count if ($procCount -eq 0) { return $true } Start-Sleep -Milliseconds 500 } return $false } function Clear-DriverInstallEnvironment { $procs = Get-DriverInstallProcesses $procCount = ($procs | Measure-Object).Count if ($procCount -gt 0) { Write-Status WARN "Found $procCount leftover driver process(es) - cleaning up..." $k = Stop-DriverInstallProcesses Write-Status INFO "Terminated $k process(es)" Start-Sleep -Seconds 2 } } #=================================================================== # EXECUTABLE FINDER (validates file, not directory) #=================================================================== function Find-Executable { param( [Parameter(Mandatory)][string]$FileName, [Parameter(Mandatory)][string[]]$KnownPaths, [string[]]$SearchRoots = @(), [string]$Label = "executable" ) # 1 - Check known paths (MUST be a file, not a directory) foreach ($p in $KnownPaths) { if (Test-Path $p -PathType Leaf) { Write-Status OK "Found $Label at known path: $p" return $p } } Write-Status INFO "$Label not found at known paths." # 2 - Search Chocolatey lib folder $chocoLib = "$env:ProgramData\chocolatey\lib" if (Test-Path $chocoLib) { Write-Status INFO "Searching Chocolatey lib for $FileName..." $found = Get-ChildItem -Path $chocoLib -Filter $FileName -Recurse -File -ErrorAction SilentlyContinue | Select-Object -First 1 if ($found) { Write-Status OK "Found $Label in Chocolatey: $($found.FullName)" return $found.FullName } } # 3 - Search Chocolatey bin for shims $chocoBin = "$env:ProgramData\chocolatey\bin" if (Test-Path $chocoBin) { $shimPath = Join-Path $chocoBin $FileName if (Test-Path $shimPath -PathType Leaf) { Write-Status OK "Found $Label shim: $shimPath" return $shimPath } } # 4 - Search provided root directories $defaultRoots = @( 'C:\Program Files' 'C:\Program Files (x86)' $env:ProgramData 'C:\HP' 'C:\SWSetup' 'C:\Dell' ) $allRoots = @($SearchRoots) + $defaultRoots | Select-Object -Unique foreach ($root in $allRoots) { if (-not (Test-Path $root)) { continue } Write-Status DEBUG "Searching $root for $FileName..." $found = Get-ChildItem -Path $root -Filter $FileName -Recurse -File -ErrorAction SilentlyContinue | Select-Object -First 1 if ($found) { Write-Status OK "Found $Label via search: $($found.FullName)" return $found.FullName } } Write-Status WARN "$Label ($FileName) not found anywhere." return $null } #=================================================================== # VERBOSE COMMAND EXECUTION #=================================================================== function Invoke-CommandVerbose { param( [Parameter(Mandatory)][string]$FilePath, [Parameter(Mandatory)][string]$Arguments, [Parameter(Mandatory)][string]$Label ) # Validate: must exist AND be a file (not a directory) if (-not (Test-Path $FilePath -PathType Leaf)) { Write-Status ERROR "Executable not found or is a directory: $FilePath" return 999 } $exeName = Split-Path $FilePath -Leaf Write-Status INFO "Executing: $exeName $Arguments" Write-Status DEBUG "Full path: $FilePath" $psi = [System.Diagnostics.ProcessStartInfo]::new() $psi.FileName = $FilePath $psi.Arguments = $Arguments $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.CreateNoWindow = $true $process = [System.Diagnostics.Process]::new() $process.StartInfo = $psi try { $null = $process.Start() } catch { Write-Status ERROR "Failed to start $exeName : $($_.Exception.Message)" return 999 } $sw = [System.Diagnostics.Stopwatch]::StartNew() $lastActivity = [System.Diagnostics.Stopwatch]::StartNew() $heartbeatSec = 30 $logLines = [System.Collections.ArrayList]::new() $stdoutTask = $process.StandardOutput.ReadLineAsync() $stderrTask = $process.StandardError.ReadLineAsync() $stdoutDone = $false $stderrDone = $false while ((-not $stdoutDone) -or (-not $stderrDone)) { $activity = $false if ((-not $stdoutDone) -and $stdoutTask.IsCompleted) { $line = $null try { $line = $stdoutTask.Result } catch { $stdoutDone = $true; continue } if ($null -ne $line) { $trimmed = $line.Trim() if ($trimmed) { $ts = Get-Date -Format 'HH:mm:ss' $formatted = " [$ts] [$Label] $trimmed" Write-Host $formatted -ForegroundColor White [void]$logLines.Add("[$ts] $trimmed") } $stdoutTask = $process.StandardOutput.ReadLineAsync() $activity = $true; $lastActivity.Restart() } else { $stdoutDone = $true } } if ((-not $stderrDone) -and $stderrTask.IsCompleted) { $line = $null try { $line = $stderrTask.Result } catch { $stderrDone = $true; continue } if ($null -ne $line) { $trimmed = $line.Trim() if ($trimmed) { $ts = Get-Date -Format 'HH:mm:ss' $formatted = " [$ts] [$Label] [STDERR] $trimmed" Write-Host $formatted -ForegroundColor Yellow [void]$logLines.Add("[$ts] [STDERR] $trimmed") } $stderrTask = $process.StandardError.ReadLineAsync() $activity = $true; $lastActivity.Restart() } else { $stderrDone = $true } } if ((-not $activity) -and ($lastActivity.Elapsed.TotalSeconds -ge $heartbeatSec)) { $ts = Get-Date -Format 'HH:mm:ss' $elapsed = [math]::Round($sw.Elapsed.TotalSeconds) $heartbeatMsg = " [$ts] [$Label] ... still running (${elapsed}s elapsed)" Write-Host $heartbeatMsg -ForegroundColor DarkGray [void]$logLines.Add("[$ts] [heartbeat] ${elapsed}s elapsed") $lastActivity.Restart() } if (-not $activity) { Start-Sleep -Milliseconds 100 } } $process.WaitForExit() $sw.Stop() $exitCode = $process.ExitCode $safeLabel = $Label -replace '[^a-zA-Z0-9]', '_' $cmdLogFile = Join-Path $Script:TempFolder "${safeLabel}_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" try { $header = "Command : $FilePath $Arguments`nExit : $exitCode`nDuration: $(Format-Duration $sw.Elapsed.TotalSeconds)`n$('=' * 60)`n" $body = ($logLines | ForEach-Object { $_ }) -join "`n" "$header$body" | Out-File $cmdLogFile -Encoding UTF8 -Force } catch { } Write-Status TIME "Completed in $(Format-Duration $sw.Elapsed.TotalSeconds)" Write-Status INFO "Exit code: $exitCode" Write-Status DEBUG "Per-command log: $cmdLogFile" try { $process.Dispose() } catch { } return $exitCode } function Get-ChocoExePath { $cmd = Get-Command choco -ErrorAction SilentlyContinue if ($null -ne $cmd) { return $cmd.Source } $default = "$env:ProgramData\chocolatey\bin\choco.exe" if (Test-Path $default) { return $default } return $null } #=================================================================== # SMB GUEST AUTH & SIGNING FIX #=================================================================== function Enable-GuestNetworkAccess { Write-SubBanner "Configuring SMB Guest Access & Signing" Write-Status INFO "Windows 11 blocks guest SMB access by default." Write-Status INFO "Applying registry fixes..." $policyPath = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\LanmanWorkstation' $servicePath = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters' $changes = 0 if (-not (Test-Path $policyPath)) { Write-Status INFO "Creating policy key: $policyPath" try { New-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows' -Name 'LanmanWorkstation' -Force | Out-Null Write-Status OK "Policy key created." $changes++ } catch { Write-Status ERROR "Failed to create policy key: $($_.Exception.Message)" } } else { Write-Status OK "Policy key exists." } try { $currentVal = $null try { $currentVal = Get-ItemPropertyValue -Path $policyPath -Name 'AllowInsecureGuestAuth' -ErrorAction SilentlyContinue } catch { } if ($currentVal -eq 1) { Write-Status OK "AllowInsecureGuestAuth (policy) already 1." } else { Set-ItemProperty -Path $policyPath -Name 'AllowInsecureGuestAuth' -Value 1 -Type DWord -Force Write-Status OK "AllowInsecureGuestAuth (policy) set to 1." $changes++ } } catch { Write-Status ERROR "Failed: $($_.Exception.Message)" } try { $currentVal = $null try { $currentVal = Get-ItemPropertyValue -Path $servicePath -Name 'AllowInsecureGuestAuth' -ErrorAction SilentlyContinue } catch { } if ($currentVal -eq 1) { Write-Status OK "AllowInsecureGuestAuth (service) already 1." } else { Set-ItemProperty -Path $servicePath -Name 'AllowInsecureGuestAuth' -Value 1 -Type DWord -Force Write-Status OK "AllowInsecureGuestAuth (service) set to 1." $changes++ } } catch { Write-Status ERROR "Failed: $($_.Exception.Message)" } try { $currentVal = $null try { $currentVal = Get-ItemPropertyValue -Path $servicePath -Name 'RequireSecuritySignature' -ErrorAction SilentlyContinue } catch { } if ($currentVal -eq 0) { Write-Status OK "RequireSecuritySignature already 0." } else { Set-ItemProperty -Path $servicePath -Name 'RequireSecuritySignature' -Value 0 -Type DWord -Force Write-Status OK "RequireSecuritySignature set to 0." $changes++ } } catch { Write-Status ERROR "Failed: $($_.Exception.Message)" } try { $currentVal = $null try { $currentVal = Get-ItemPropertyValue -Path $servicePath -Name 'EnableSecuritySignature' -ErrorAction SilentlyContinue } catch { } if ($currentVal -eq 0) { Write-Status OK "EnableSecuritySignature already 0." } else { Set-ItemProperty -Path $servicePath -Name 'EnableSecuritySignature' -Value 0 -Type DWord -Force Write-Status OK "EnableSecuritySignature set to 0." $changes++ } } catch { Write-Status ERROR "Failed: $($_.Exception.Message)" } if ($changes -gt 0) { Write-Status INFO "Restarting LanmanWorkstation service ($changes changes)..." try { $saveEA = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' $null = cmd.exe /c "net stop LanmanWorkstation /y >nul 2>&1" Start-Sleep -Seconds 2 $null = cmd.exe /c "net start LanmanWorkstation >nul 2>&1" $null = cmd.exe /c "net start LanmanServer >nul 2>&1" $null = cmd.exe /c "net start Netlogon >nul 2>&1" $ErrorActionPreference = $saveEA Start-Sleep -Seconds 3 Write-Status OK "LanmanWorkstation restarted." } catch { try { $ErrorActionPreference = $saveEA } catch { } Write-Status WARN "Service restart may need reboot." } } else { Write-Status OK "No changes needed." } $readBack = @( @{ Name = 'AllowInsecureGuestAuth (policy)'; Path = $policyPath; Key = 'AllowInsecureGuestAuth'; Expected = 1 } @{ Name = 'AllowInsecureGuestAuth (service)'; Path = $servicePath; Key = 'AllowInsecureGuestAuth'; Expected = 1 } @{ Name = 'RequireSecuritySignature'; Path = $servicePath; Key = 'RequireSecuritySignature'; Expected = 0 } @{ Name = 'EnableSecuritySignature'; Path = $servicePath; Key = 'EnableSecuritySignature'; Expected = 0 } ) Write-LoggedHost Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost " SMB GUEST ACCESS VERIFICATION" -ForegroundColor Cyan Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan foreach ($item in $readBack) { $val = $null try { $val = Get-ItemPropertyValue -Path $item.Path -Name $item.Key -ErrorAction SilentlyContinue } catch { } $valStr = if ($null -ne $val) { $val.ToString() } else { 'NOT SET' } $match = ($val -eq $item.Expected) $statusColor = if ($match) { 'Green' } else { 'Red' } $statusIcon = if ($match) { 'OK' } else { 'FAIL' } Write-LoggedHost " [$statusIcon] $($item.Name): $valStr (expected: $($item.Expected))" -ForegroundColor $statusColor } Write-LoggedHost " Changes applied: $changes" -ForegroundColor White Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost return ($changes -ge 0) } #=================================================================== # SYSTEM MODEL & NETWORK SHARE PATH HELPERS #=================================================================== function Get-SystemInfo { $cs = Get-CimInstance Win32_ComputerSystem $rawManufacturer = $cs.Manufacturer.Trim() $rawModel = $cs.Model.Trim() $cleanModel = ($rawModel -replace '\s', '').ToLower() return [PSCustomObject]@{ RawManufacturer = $rawManufacturer RawModel = $rawModel CleanModel = $cleanModel } } function Get-ManufacturerShareFolder { param([Parameter(Mandatory)][string]$Manufacturer) foreach ($key in $Script:ManufacturerShareMap.Keys) { if ($Manufacturer -like "*$key*") { return $Script:ManufacturerShareMap[$key] } } return ($Manufacturer -replace '\s', '').ToLower() } function Build-NetworkDriverPath { param([Parameter(Mandatory)][string]$ManufacturerFolder, [Parameter(Mandatory)][string]$CleanModel) $path = Join-Path $Script:NetworkShareBase $ManufacturerFolder $path = Join-Path $path $Script:ShareDriverFolder $path = Join-Path $path $CleanModel return $path } function Test-NetworkShareAccess { param([Parameter(Mandatory)][string]$SharePath, [int]$MaxRetries = 10, [int]$RetryDelaySec = 10) Write-Status INFO "Testing access to: $SharePath" for ($i = 1; $i -le $MaxRetries; $i++) { try { if (Test-Path $SharePath -ErrorAction Stop) { Write-Status OK "Network share accessible: $SharePath" return $true } } catch { } if ($i -lt $MaxRetries) { Write-Status WARN "Attempt $i/$MaxRetries failed - waiting ${RetryDelaySec}s..." Start-Sleep -Seconds $RetryDelaySec } } Write-Status ERROR "Share not accessible after $MaxRetries attempts: $SharePath" return $false } #=================================================================== # DEVICE SCANNING & INF MATCHING #=================================================================== function Get-SystemDeviceIds { $ids = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $deviceCount = 0 $problemDeviceNames = [System.Collections.ArrayList]::new() try { $devices = @(Get-CimInstance Win32_PnPEntity -ErrorAction Stop) $deviceCount = ($devices | Measure-Object).Count foreach ($dev in $devices) { if ($null -ne $dev.HardwareID) { foreach ($hwid in @($dev.HardwareID)) { if ($hwid) { [void]$ids.Add($hwid) } } } if ($null -ne $dev.CompatibleID) { foreach ($cid in @($dev.CompatibleID)) { if ($cid) { [void]$ids.Add($cid) } } } if ($null -ne $dev.ConfigManagerErrorCode -and $dev.ConfigManagerErrorCode -ne 0) { $devName = $dev.Name; if (-not $devName) { $devName = $dev.DeviceID } [void]$problemDeviceNames.Add("$devName (error $($dev.ConfigManagerErrorCode))") } } } catch { Write-Status WARN "WMI scan failed: $($_.Exception.Message)" } if ($ids.Count -eq 0) { try { $pnpDevs = @(Get-PnpDevice -ErrorAction SilentlyContinue) $deviceCount = ($pnpDevs | Measure-Object).Count foreach ($dev in $pnpDevs) { try { $hwProp = $dev | Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_HardwareIds' -ErrorAction SilentlyContinue if ($null -ne $hwProp -and $null -ne $hwProp.Data) { foreach ($id in @($hwProp.Data)) { if ($id) { [void]$ids.Add($id) } } } } catch { } try { $cProp = $dev | Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_CompatibleIds' -ErrorAction SilentlyContinue if ($null -ne $cProp -and $null -ne $cProp.Data) { foreach ($id in @($cProp.Data)) { if ($id) { [void]$ids.Add($id) } } } } catch { } } } catch { } } Write-Status OK "Scanned $deviceCount device(s), $($ids.Count) unique ID(s)." $probCount = ($problemDeviceNames | Measure-Object).Count if ($probCount -gt 0) { Write-Status WARN "$probCount device(s) with problems:" foreach ($pd in $problemDeviceNames) { Write-Status INFO " - $pd" } } return $ids } function Get-InfHardwareIds { param([Parameter(Mandatory)][string]$InfPath) $hardwareIds = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) try { $lines = @(Get-Content $InfPath -ErrorAction Stop) } catch { return @($hardwareIds) } $lineCount = ($lines | Measure-Object).Count if ($lineCount -eq 0) { return @($hardwareIds) } $modelSections = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) $inManufacturer = $false foreach ($line in $lines) { $trimmed = $line.Trim() if ($trimmed.StartsWith(';')) { continue } if ($trimmed -match '^\[Manufacturer\]\s*$') { $inManufacturer = $true; continue } if ($inManufacturer) { if ($trimmed -match '^\[') { $inManufacturer = $false; continue } if (-not $trimmed) { continue } if ($trimmed -match '=') { $rightSide = ($trimmed -split '=', 2)[1] if ($null -eq $rightSide) { continue } $parts = $rightSide -split ',' $baseName = $parts[0].Trim() if (-not $baseName) { continue } [void]$modelSections.Add($baseName) $partCount = ($parts | Measure-Object).Count for ($j = 1; $j -lt $partCount; $j++) { $decoration = $parts[$j].Trim() if ($decoration) { [void]$modelSections.Add("$baseName.$decoration") } } } } } foreach ($section in @($modelSections)) { $sectionHeader = "[$section]" $inSection = $false foreach ($line in $lines) { $trimmed = $line.Trim() if ($trimmed.StartsWith(';')) { continue } if ($trimmed -ieq $sectionHeader) { $inSection = $true; continue } if ($inSection) { if ($trimmed -match '^\[') { $inSection = $false; continue } if (-not $trimmed) { continue } if ($trimmed -match '=') { $rightSide = ($trimmed -split '=', 2)[1] if ($null -eq $rightSide) { continue } $idParts = $rightSide -split ',' $idPartCount = ($idParts | Measure-Object).Count for ($j = 1; $j -lt $idPartCount; $j++) { $hwid = $idParts[$j].Trim().Trim('"').Trim() if ($hwid -and $hwid -match '\\') { [void]$hardwareIds.Add($hwid) } } } } } } return @($hardwareIds) } function Test-InfMatchesSystem { param([Parameter(Mandatory)][string]$InfPath, [Parameter(Mandatory)][System.Collections.Generic.HashSet[string]]$SystemIds) $infIds = Get-InfHardwareIds -InfPath $InfPath $infIdCount = ($infIds | Measure-Object).Count if ($infIdCount -eq 0) { return $true } foreach ($infId in $infIds) { if ($SystemIds.Contains($infId)) { return $true } } return $false } #=================================================================== # STATE-MANAGEMENT #=================================================================== function Get-ScriptState { if (Test-Path $Script:StateFile) { try { $json = Get-Content $Script:StateFile -Raw -ErrorAction Stop | ConvertFrom-Json $defaults = @{ Phase = 0; USBCompleted = $false; USBRebootDone = $false NetworkShareCompleted = $false; NetworkShareRebootDone = $false NetworkSharePath = ""; NetworkShareProcessedDrivers = @() USBProcessedDrivers = @(); USBDriverRoot = "" ManufacturerPhase = 0; DellADRFailed = $false; LastExitCode = 0 RebootCount = 0; TotalDriversAdded = 0; TotalDriversInstalled = 0 TotalDriversFailed = 0; TotalDriversSkipped = 0 TotalDriversTimedOut = 0; TotalTimeSpent = 0; TotalProcessesKilled = 0 SystemManufacturer = ""; SystemModel = ""; CleanModel = "" GuestAuthConfigured = $false } foreach ($k in $defaults.Keys) { if (-not $json.PSObject.Properties[$k]) { $json | Add-Member -NotePropertyName $k -NotePropertyValue $defaults[$k] -Force } } return $json } catch { Write-Status WARN "State file error - fresh start: $($_.Exception.Message)" } } return [PSCustomObject]@{ Phase = 0; USBCompleted = $false; USBRebootDone = $false NetworkShareCompleted = $false; NetworkShareRebootDone = $false NetworkSharePath = ""; NetworkShareProcessedDrivers = @() USBProcessedDrivers = @(); USBDriverRoot = "" ManufacturerPhase = 0; DellADRFailed = $false; LastExitCode = 0 RebootCount = 0; TotalDriversAdded = 0; TotalDriversInstalled = 0 TotalDriversFailed = 0; TotalDriversSkipped = 0 TotalDriversTimedOut = 0; TotalTimeSpent = 0; TotalProcessesKilled = 0 SystemManufacturer = ""; SystemModel = ""; CleanModel = "" GuestAuthConfigured = $false } } function Set-ScriptState { param([pscustomobject]$State) $State | ConvertTo-Json -Depth 10 | Out-File $Script:StateFile -Encoding UTF8 -Force } function Remove-ScriptState { Write-Status INFO "Removing state and RunOnce..." if (Test-Path $Script:StateFile) { Remove-Item $Script:StateFile -Force -ErrorAction SilentlyContinue } try { $rk64 = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64).OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', $true) if ($null -ne $rk64) { $rk64.DeleteValue($Script:RunOnceName, $false); $rk64.Close() } } catch { } $fallback = "$($Script:RunOnceName)_Task" try { $t = Get-ScheduledTask -TaskName $fallback -ErrorAction SilentlyContinue if ($null -ne $t) { Unregister-ScheduledTask -TaskName $fallback -Confirm:$false -ErrorAction SilentlyContinue } } catch { } Write-Status OK "Cleanup finished." } #=================================================================== # RUNONCE & REBOOT #=================================================================== function Set-RunOnceEntry { param([Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$Command) try { $rk64 = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64).OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', $true) $rk64.SetValue($Name, $Command, [Microsoft.Win32.RegistryValueKind]::String); $rk64.Close() Write-Status OK "RunOnce: $Name"; return $true } catch { Write-Status WARN "RunOnce failed: $($_.Exception.Message)"; return $false } } function Set-RunOnceTask { param([Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$ScriptPath) try { $a = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`"" $tr = New-ScheduledTaskTrigger -AtStartup $p = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest -LogonType ServiceAccount Register-ScheduledTask -TaskName $Name -Action $a -Trigger $tr -Principal $p -Force -ErrorAction Stop Write-Status OK "Fallback task: $Name"; return $true } catch { Write-Status ERROR "Task failed: $($_.Exception.Message)"; return $false } } function Schedule-RebootAndContinue { param([pscustomobject]$State, [string]$Reason = "Reboot required") Write-LoggedHost Write-LoggedHost "======================================================================" -ForegroundColor Yellow Write-LoggedHost " REBOOT REQUIRED: $Reason" -ForegroundColor Yellow Write-LoggedHost "======================================================================" -ForegroundColor Yellow Write-LoggedHost Stop-DriverInstallProcesses -Silent | Out-Null $State.RebootCount++; Set-ScriptState -State $State $cmd = "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$Script:PersistentScript`"" $ok = Set-RunOnceEntry -Name $Script:RunOnceName -Command $cmd if (-not $ok) { Set-RunOnceTask -Name "$($Script:RunOnceName)_Task" -ScriptPath $Script:PersistentScript | Out-Null } for ($i = 15; $i -ge 1; $i--) { Write-LoggedHost " Rebooting in $i seconds..." -ForegroundColor Yellow Start-Sleep -Seconds 1 } Write-LoggedHost " Rebooting NOW!" -ForegroundColor Red try { Stop-Transcript } catch { } Restart-Computer -Force; exit 0 } #=================================================================== # SINGLE DRIVER INSTALL #=================================================================== function Install-SingleDriver { param( [Parameter(Mandatory)][string]$InfPath, [Parameter(Mandatory)][string]$DriverName, [Parameter(Mandatory)][int]$CurrentNumber, [Parameter(Mandatory)][int]$TotalCount, [int]$TimeoutSeconds = $Script:DriverTimeout ) $result = [PSCustomObject]@{ InfPath = $InfPath; DriverName = $DriverName; Success = $false ExitCode = -1; Added = $false; Installed = $false; AlreadyExists = $false NeedsReboot = $false; TimedOut = $false; ProcessesKilled = 0 ErrorMessage = ""; Output = ""; Duration = 0 } Write-DriverHeader -DriverName $DriverName -Current $CurrentNumber -Total $TotalCount Write-Status DRIVER "Path: $InfPath" Clear-DriverInstallEnvironment Write-Status INFO "Starting pnputil installation..." $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $consoleWidth = Get-ConsoleWidth $tempScript = [IO.Path]::GetTempFileName() + ".ps1" $outFile = [IO.Path]::GetTempFileName() $exitFile = [IO.Path]::GetTempFileName() $wrapperContent = @" try { `$out = & pnputil.exe /add-driver "$InfPath" /install 2>&1 | Out-String `$out | Out-File -FilePath "$outFile" -Encoding UTF8 `$LASTEXITCODE | Out-File -FilePath "$exitFile" -Encoding UTF8 } catch { `$_.Exception.Message | Out-File -FilePath "$outFile" -Encoding UTF8 "999" | Out-File -FilePath "$exitFile" -Encoding UTF8 } "@ $wrapperContent | Out-File -FilePath $tempScript -Encoding UTF8 $proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$tempScript`"" -PassThru -WindowStyle Hidden $frameIdx = 0; $frameCount = $Script:SpinnerFrames.Length $animLine = 0; try { $animLine = [Console]::CursorTop } catch { } Write-Host "[PNPUTIL] Installing: $DriverName ($CurrentNumber/$TotalCount)" -ForegroundColor Cyan while ((-not $proc.HasExited) -and ($stopwatch.Elapsed.TotalSeconds -lt $TimeoutSeconds)) { $elapsed = $stopwatch.Elapsed.TotalSeconds $remaining = [math]::Max(0, $TimeoutSeconds - $elapsed) $pct = [math]::Round(($elapsed / $TimeoutSeconds) * 100) $spinner = $Script:SpinnerFrames[$frameIdx % $frameCount]; $frameIdx++ $line = " $spinner $pct% | Elapsed: $([math]::Round($elapsed))s | Remaining: $([math]::Round($remaining))s" $padded = $line.PadRight($consoleWidth) try { [Console]::SetCursorPosition(0, $animLine) [Console]::ForegroundColor = [ConsoleColor]::Cyan [Console]::Write($padded); [Console]::ResetColor() } catch { Write-Host "`r$padded" -NoNewline -ForegroundColor Cyan } Start-Sleep -Milliseconds 150 } try { [Console]::SetCursorPosition(0, $animLine); [Console]::Write(' ' * $consoleWidth); [Console]::SetCursorPosition(0, $animLine) } catch { Write-Host "`r$(' ' * $consoleWidth)`r" -NoNewline } $stopwatch.Stop() $result.Duration = $stopwatch.Elapsed.TotalSeconds if (-not $proc.HasExited) { Write-Status SKIP "TIMEOUT after $(Format-Duration $TimeoutSeconds)!" try { $proc.Kill() } catch { } $result.TimedOut = $true; $result.ErrorMessage = "Timeout" $killed = Stop-DriverInstallProcesses; $result.ProcessesKilled = $killed $null = Wait-ForDriverProcesses -TimeoutSeconds 10 Remove-Item $tempScript, $outFile, $exitFile -Force -ErrorAction SilentlyContinue Write-Host "[PNPUTIL] TIMEOUT for $DriverName after $TimeoutSeconds seconds" -ForegroundColor Yellow if ($Script:CooldownSeconds -gt 0) { Start-Sleep -Seconds $Script:CooldownSeconds } return $result } $out = ""; $exit = -1 if (Test-Path $outFile) { $out = Get-Content $outFile -Raw -ErrorAction SilentlyContinue } if (Test-Path $exitFile) { $raw = Get-Content $exitFile -Raw -ErrorAction SilentlyContinue if ($null -ne $raw) { [int]::TryParse($raw.Trim(), [ref]$exit) | Out-Null } } Remove-Item $tempScript, $outFile, $exitFile -Force -ErrorAction SilentlyContinue $result.ExitCode = $exit; $result.Output = $out if ($null -ne $out -and $out.Trim()) { Write-Host "[PNPUTIL] --- Output for $DriverName ---" -ForegroundColor DarkGray foreach ($outLine in ($out -split "`n")) { $trimLine = $outLine.Trim() if ($trimLine) { Write-Host "[PNPUTIL] $trimLine" -ForegroundColor DarkGray } } Write-Host "[PNPUTIL] --- End output ---" -ForegroundColor DarkGray } if ($null -ne $out) { if ($out -match 'added|successfully added|driver package added') { $result.Added = $true; Write-Status OK "Driver added to store" } if ($out -match 'install.*device|installed to device') { $result.Installed = $true; Write-Status OK "Driver installed on device" } if ($out -match 'already exists|up to date') { $result.AlreadyExists = $true; Write-Status INFO "Already up-to-date" } if ($out -match 'reboot|restart') { $result.NeedsReboot = $true; Write-Status WARN "Reboot required" } } switch ($exit) { 0 { $result.Success = $true; Write-Status OK "Exit 0 - Success" } 1 { $result.Success = $true; Write-Status INFO "Exit 1 - Warnings" } 259 { $result.Success = $true; Write-Status INFO "Exit 259 - Staged" } 3010 { $result.Success = $true; $result.NeedsReboot = $true; Write-Status WARN "Exit 3010 - Reboot" } 482 { $result.Success = $true; $result.NeedsReboot = $true; Write-Status WARN "Exit 482 - Partial" } default { if ($exit -ge 0) { if ($result.Added -or $result.Installed -or $result.AlreadyExists) { $result.Success = $true } else { $result.Success = $false; $result.ErrorMessage = "Exit $exit"; Write-Status ERROR "Exit $exit" } } else { $result.Success = $true } } } Write-Status TIME "Time: $(Format-Duration $result.Duration)" if ($Script:CooldownSeconds -gt 0) { Start-Sleep -Seconds $Script:CooldownSeconds } return $result } #=================================================================== # GENERIC FOLDER INSTALLER #=================================================================== function Install-DriversFromFolder { param( [Parameter(Mandatory)][string]$DriverRoot, [Parameter(Mandatory)][string]$Label, [string[]]$ProcessedList = @(), [switch]$SkipDeviceMatching, [System.Collections.Generic.HashSet[string]]$SystemIds = $null ) $result = [PSCustomObject]@{ Success = $false; NeedsReboot = $false; NotFound = $false TotalFound = 0; TotalMatched = 0; TotalSkippedNoMatch = 0 TotalAdded = 0; TotalInstalled = 0; TotalFailed = 0 TotalAlreadyExist = 0; TotalTimedOut = 0; TotalTime = 0; TotalKilled = 0 ProcessedDrivers = @($ProcessedList) } $allInfFiles = @(Get-ChildItem -Path $DriverRoot -Filter "*.inf" -Recurse -File -ErrorAction SilentlyContinue | Sort-Object FullName) $totalFound = ($allInfFiles | Measure-Object).Count if ($totalFound -eq 0) { Write-Status WARN "No .inf files in: $DriverRoot" $result.NotFound = $true; $result.Success = $true; return $result } $result.TotalFound = $totalFound Write-Status OK "Found $totalFound INF(s) in: $DriverRoot" $processed = @($ProcessedList) $processedCount = ($processed | Measure-Object).Count if ($processedCount -gt 0) { Write-Status INFO "$processedCount already processed." } $matchedInfs = [System.Collections.ArrayList]::new() $skippedNoMatch = [System.Collections.ArrayList]::new() if ($SkipDeviceMatching) { Write-Status INFO "Device matching DISABLED - installing ALL." foreach ($inf in $allInfFiles) { [void]$matchedInfs.Add($inf) } } else { if ($null -eq $SystemIds -or $SystemIds.Count -eq 0) { Write-SubBanner "Scanning System Devices" $SystemIds = Get-SystemDeviceIds } if ($SystemIds.Count -eq 0) { Write-Status WARN "No device IDs - installing ALL as fallback." foreach ($inf in $allInfFiles) { [void]$matchedInfs.Add($inf) } } else { Write-SubBanner "Matching $Label Drivers to Devices" foreach ($inf in $allInfFiles) { $rel = $inf.FullName.Substring($DriverRoot.Length + 1) if ($rel -in $processed) { continue } if (Test-InfMatchesSystem -InfPath $inf.FullName -SystemIds $SystemIds) { [void]$matchedInfs.Add($inf) } else { [void]$skippedNoMatch.Add($inf); Write-Status SKIP "No match: $rel" } } } } $matchedCount = ($matchedInfs | Measure-Object).Count $skippedNoMatchCount = ($skippedNoMatch | Measure-Object).Count $result.TotalMatched = $matchedCount; $result.TotalSkippedNoMatch = $skippedNoMatchCount $modeLabel = if ($SkipDeviceMatching) { "ALL (no filtering)" } else { "TARGETED" } $modeColor = if ($SkipDeviceMatching) { 'Yellow' } else { 'Green' } Write-LoggedHost Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost " $Label - MATCHING RESULTS" -ForegroundColor Cyan Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost " Total INFs: $totalFound" -ForegroundColor White Write-LoggedHost " Processed: $processedCount" -ForegroundColor DarkGray Write-LoggedHost " Matched: $matchedCount" -ForegroundColor Green Write-LoggedHost " Skipped: $skippedNoMatchCount" -ForegroundColor Yellow Write-LoggedHost " Mode: $modeLabel" -ForegroundColor $modeColor Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost if ($matchedCount -eq 0) { Write-Status OK "No new drivers to install."; $result.Success = $true; return $result } $overallSw = [System.Diagnostics.Stopwatch]::StartNew() $globalReboot = $false; $i = 0 Write-SubBanner "Installing $matchedCount $Label Driver(s)" foreach ($inf in $matchedInfs) { $i++ $rel = $inf.FullName.Substring($DriverRoot.Length + 1) if ($rel -in $processed) { continue } $drvResult = Install-SingleDriver -InfPath $inf.FullName -DriverName $rel -CurrentNumber $i -TotalCount $matchedCount $result.TotalTime += $drvResult.Duration; $result.TotalKilled += $drvResult.ProcessesKilled if ($drvResult.TimedOut) { $result.TotalTimedOut++ } elseif ($drvResult.Added) { $result.TotalAdded++ } if ($drvResult.Installed) { $result.TotalInstalled++ } if ($drvResult.AlreadyExists) { $result.TotalAlreadyExist++ } if ((-not $drvResult.Success) -and (-not $drvResult.TimedOut)) { $result.TotalFailed++ } if ($drvResult.NeedsReboot) { $globalReboot = $true } $processed += $rel; $result.ProcessedDrivers = @($processed) } $overallSw.Stop() $failColor = if ($result.TotalFailed -gt 0) { 'Red' } else { 'Green' } $timeoutColor = if ($result.TotalTimedOut -gt 0) { 'Yellow' } else { 'Green' } $killColor = if ($result.TotalKilled -gt 0) { 'Yellow' } else { 'Green' } $rebootColor = if ($globalReboot) { 'Yellow' } else { 'Green' } Write-LoggedHost Write-LoggedHost "======================================================================" -ForegroundColor Green Write-LoggedHost " $Label DRIVER SUMMARY" -ForegroundColor Green Write-LoggedHost "======================================================================" -ForegroundColor Green Write-LoggedHost " Found: $($result.TotalFound)" -ForegroundColor White Write-LoggedHost " Matched: $($result.TotalMatched)" -ForegroundColor Green Write-LoggedHost " Skipped: $($result.TotalSkippedNoMatch)" -ForegroundColor Yellow Write-LoggedHost " Added: $($result.TotalAdded)" -ForegroundColor Green Write-LoggedHost " Installed: $($result.TotalInstalled)" -ForegroundColor Green Write-LoggedHost " Up-to-date: $($result.TotalAlreadyExist)" -ForegroundColor Cyan Write-LoggedHost " Failed: $($result.TotalFailed)" -ForegroundColor $failColor Write-LoggedHost " Timed out: $($result.TotalTimedOut)" -ForegroundColor $timeoutColor Write-LoggedHost " Killed: $($result.TotalKilled)" -ForegroundColor $killColor Write-LoggedHost " Time: $(Format-Duration $overallSw.Elapsed.TotalSeconds)" -ForegroundColor Magenta Write-LoggedHost " Reboot: $globalReboot" -ForegroundColor $rebootColor Write-LoggedHost "======================================================================" -ForegroundColor Green Write-LoggedHost $result.Success = $true; $result.NeedsReboot = $globalReboot return $result } #=================================================================== # PHASE-0A: USB DRIVERS (TARGETED) #=================================================================== function Install-USBNetworkDrivers { param([pscustomobject]$State) Write-Banner "PHASE-0A: USB DRIVER INSTALLATION (TARGETED)" Write-Status STEP "Installing ONLY drivers matching this PC's devices from USB." Clear-DriverInstallEnvironment $root = $null if ($State.USBDriverRoot -and (Test-Path $State.USBDriverRoot)) { $root = $State.USBDriverRoot } else { foreach ($drive in @('D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z')) { foreach ($folder in $Script:USBDriverFolders) { $candidate = "${drive}:\$folder" if (Test-Path $candidate) { $root = $candidate; Write-Status OK "Found USB driver folder: $root"; break } } if ($null -ne $root) { break } } } if ($null -eq $root) { Write-Status INFO "No USB driver folder found - skipping USB phase." return [PSCustomObject]@{ Success = $true; NeedsReboot = $false; NotFound = $true } } $State.USBDriverRoot = $root; Set-ScriptState -State $State Write-SubBanner "Scanning System Devices for USB Driver Matching" $systemIds = Get-SystemDeviceIds Write-Status INFO "Will match USB INF files against $($systemIds.Count) hardware ID(s)." $processed = @(); if ($State.USBProcessedDrivers) { $processed = @($State.USBProcessedDrivers) } $usbResult = Install-DriversFromFolder -DriverRoot $root -Label "USB" -ProcessedList $processed -SystemIds $systemIds $State.USBProcessedDrivers = $usbResult.ProcessedDrivers $State.TotalDriversAdded += $usbResult.TotalAdded $State.TotalDriversInstalled += $usbResult.TotalInstalled $State.TotalDriversFailed += $usbResult.TotalFailed $State.TotalDriversSkipped += $usbResult.TotalSkippedNoMatch $State.TotalDriversTimedOut += $usbResult.TotalTimedOut $State.TotalTimeSpent += $usbResult.TotalTime $State.TotalProcessesKilled += $usbResult.TotalKilled Set-ScriptState -State $State return $usbResult } #=================================================================== # PHASE-0B: NETWORK SHARE DRIVERS #=================================================================== function Install-NetworkShareDrivers { param([pscustomobject]$State) Write-Banner "PHASE-0B: NETWORK SHARE DRIVER INSTALLATION" if (-not $State.GuestAuthConfigured) { Enable-GuestNetworkAccess | Out-Null $State.GuestAuthConfigured = $true; Set-ScriptState -State $State } else { Write-Status OK "SMB guest access already configured." } Write-SubBanner "Identifying System Model" $sysInfo = Get-SystemInfo $State.SystemManufacturer = $sysInfo.RawManufacturer $State.SystemModel = $sysInfo.RawModel $State.CleanModel = $sysInfo.CleanModel Set-ScriptState -State $State Write-Status INFO "Manufacturer: $($sysInfo.RawManufacturer)" Write-Status INFO "Model: $($sysInfo.RawModel)" Write-Status INFO "Clean model: $($sysInfo.CleanModel)" $mfgFolder = Get-ManufacturerShareFolder -Manufacturer $sysInfo.RawManufacturer $sharePath = "" if ($State.NetworkSharePath -and (Test-Path $State.NetworkSharePath -ErrorAction SilentlyContinue)) { $sharePath = $State.NetworkSharePath } else { $sharePath = Build-NetworkDriverPath -ManufacturerFolder $mfgFolder -CleanModel $sysInfo.CleanModel } Write-LoggedHost Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost " NETWORK SHARE PATH" -ForegroundColor Cyan Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost " Base: $($Script:NetworkShareBase)" -ForegroundColor White Write-LoggedHost " Mfg: $mfgFolder" -ForegroundColor White Write-LoggedHost " Model: $($sysInfo.CleanModel)" -ForegroundColor White Write-LoggedHost " Path: $sharePath" -ForegroundColor Green Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost Write-SubBanner "Testing Network Share Access" $shareOk = Test-NetworkShareAccess -SharePath $sharePath -MaxRetries 10 -RetryDelaySec 10 if (-not $shareOk) { $alternatives = @(($sysInfo.RawModel -replace '\s', '-').ToLower(), ($sysInfo.RawModel -replace '\s', '_').ToLower(), $sysInfo.RawModel.ToLower()) $altFound = $false foreach ($alt in $alternatives) { $altPath = Build-NetworkDriverPath -ManufacturerFolder $mfgFolder -CleanModel $alt Write-Status INFO "Trying: $altPath" if (Test-Path $altPath -ErrorAction SilentlyContinue) { $sharePath = $altPath; Write-Status OK "Found: $sharePath"; $altFound = $true; break } } if (-not $altFound) { Write-Status ERROR "No share folder found. Expected: $sharePath" return [PSCustomObject]@{ Success = $true; NeedsReboot = $false; NotFound = $true } } } $State.NetworkSharePath = $sharePath; Set-ScriptState -State $State $processed = @(); if ($State.NetworkShareProcessedDrivers) { $processed = @($State.NetworkShareProcessedDrivers) } Write-SubBanner "Scanning Devices" $systemIds = Get-SystemDeviceIds $netResult = Install-DriversFromFolder -DriverRoot $sharePath -Label "NETWORK SHARE" -ProcessedList $processed -SystemIds $systemIds $State.NetworkShareProcessedDrivers = $netResult.ProcessedDrivers $State.TotalDriversAdded += $netResult.TotalAdded $State.TotalDriversInstalled += $netResult.TotalInstalled $State.TotalDriversFailed += $netResult.TotalFailed $State.TotalDriversSkipped += $netResult.TotalSkippedNoMatch $State.TotalDriversTimedOut += $netResult.TotalTimedOut $State.TotalTimeSpent += $netResult.TotalTime $State.TotalProcessesKilled += $netResult.TotalKilled Set-ScriptState -State $State return $netResult } #=================================================================== # INTERNET & MANUFACTURER #=================================================================== function Test-InternetConnectivity { param([int]$MaxRetries = 15, [int]$RetryDelay = 10) Write-Banner "CHECKING INTERNET CONNECTIVITY" $w = Get-ConsoleWidth; $totalFrames = $Script:SpinnerFrames.Length for ($i = 1; $i -le $MaxRetries; $i++) { Write-Status PROGRESS "Attempt $i of $MaxRetries..." try { $r = Invoke-WebRequest -Uri "https://www.google.com" -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop if ($r.StatusCode -eq 200) { Write-Status OK "Internet reachable."; return $true } } catch { Write-Status WARN "Attempt $i failed: $($_.Exception.Message)" if ($i -lt $MaxRetries) { for ($s = $RetryDelay; $s -ge 1; $s--) { $spinner = $Script:SpinnerFrames[($RetryDelay - $s) % $totalFrames] $pad = " $spinner Waiting $s sec...".PadRight($w) try { [Console]::SetCursorPosition(0, [Console]::CursorTop); [Console]::ForegroundColor = [ConsoleColor]::DarkGray; [Console]::Write($pad); [Console]::ResetColor() } catch { Write-Host "`r$pad" -NoNewline -ForegroundColor DarkGray } Start-Sleep -Seconds 1 } } } } Write-Status ERROR "No internet after $MaxRetries attempts." return $false } function Get-SystemManufacturer { Write-SubBanner "Detecting System Manufacturer" $cs = Get-CimInstance Win32_ComputerSystem Write-Status INFO "Manufacturer: $($cs.Manufacturer.Trim())" Write-Status INFO "Model: $($cs.Model.Trim())" return $cs.Manufacturer.Trim() } #=================================================================== # CHOCOLATEY #=================================================================== function Set-ChocolateySource { Write-SubBanner "Configuring Chocolatey Sources" $chocoExe = Get-ChocoExePath; if (-not $chocoExe) { return } $null = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "source add -n `"$($Script:ChocoSourceName)`" -s `"$($Script:ChocoSourceUrl)`" --priority=1 --allow-self-service" -Label "CHOCO" $null = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "source disable -n `"chocolatey`"" -Label "CHOCO" $null = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "source list" -Label "CHOCO" } function Install-ChocolateyIfNeeded { Write-SubBanner "Ensuring Chocolatey" if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { Write-Status INFO "Installing Chocolatey..." Write-Host "[CHOCO] Installing Chocolatey from community.chocolatey.org..." -ForegroundColor Cyan Set-ExecutionPolicy Bypass -Scope Process -Force [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072 Invoke-Expression (Invoke-RestMethod https://community.chocolatey.org/install.ps1) $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User') Write-Status OK "Chocolatey installed." } else { Write-Status OK "Chocolatey present." } Set-ChocolateySource } function Install-ChocoPackage { param([Parameter(Mandatory)][string]$PackageName, [string]$DisplayName) if (-not $DisplayName) { $DisplayName = $PackageName } $chocoExe = Get-ChocoExePath; if (-not $chocoExe) { return $false } Write-SubBanner "Installing $DisplayName via Chocolatey" $exitCode = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "install $PackageName -y --source=`"$($Script:ChocoSourceName)`"" -Label "CHOCO" if ($exitCode -ne 0) { Write-Status WARN "Custom source failed - trying default..." $exitCode = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "install $PackageName -y" -Label "CHOCO" } if ($exitCode -eq 0) { Write-Status OK "$DisplayName installed."; return $true } else { Write-Status ERROR "$DisplayName failed."; return $false } } #=================================================================== # DELL UPDATES #=================================================================== function Find-DellCLI { $knownPaths = @( 'C:\Program Files\Dell\CommandUpdate\dcu-cli.exe' 'C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe' 'C:\Program Files\Dell\Dell Command Update\dcu-cli.exe' 'C:\Program Files (x86)\Dell\Dell Command Update\dcu-cli.exe' ) return Find-Executable -FileName 'dcu-cli.exe' -KnownPaths $knownPaths -SearchRoots @('C:\Dell') -Label "Dell Command Update CLI" } function Invoke-DellUpdates { param([pscustomobject]$State) Write-Banner "DELL SYSTEM UPDATE" $cli = Find-DellCLI if (-not $cli) { Install-ChocoPackage -PackageName "dellcommandupdate" -DisplayName "Dell Command Update" | Out-Null Start-Sleep -Seconds 5 $cli = Find-DellCLI } if (-not $cli) { Write-Status ERROR "Dell CLI not found anywhere."; return $false } Write-Status OK "Dell CLI: $cli" switch ($State.ManufacturerPhase) { 0 { if ($State.DellADRFailed) { $State.ManufacturerPhase = 2; Set-ScriptState -State $State; return (Invoke-DellUpdates -State $State) } Write-SubBanner "Dell Phase-0: Configure ADR" $null = Invoke-CommandVerbose -FilePath $cli -Arguments "/configure -advancedDriverRestore=enable" -Label "DCU-CFG" Write-SubBanner "Dell Phase-0: ADR Driver Install" Write-Status STEP "All driver names and progress will appear below." $dellExit = Invoke-CommandVerbose -FilePath $cli -Arguments "/driverinstall" -Label "DCU" switch ($dellExit) { 0 { $State.ManufacturerPhase = 2; Set-ScriptState -State $State; return (Invoke-DellUpdates -State $State) } 1 { $State.ManufacturerPhase = 1; Schedule-RebootAndContinue -State $State -Reason "Dell ADR (exit 1)" } 5 { $State.ManufacturerPhase = 1; Schedule-RebootAndContinue -State $State -Reason "Dell ADR (exit 5)" } 2 { $State.DellADRFailed = $true; $State.ManufacturerPhase = 2; Set-ScriptState -State $State; return (Invoke-DellUpdates -State $State) } 3 { $State.ManufacturerPhase = 2; Set-ScriptState -State $State; return (Invoke-DellUpdates -State $State) } default { $State.DellADRFailed = $true; $State.ManufacturerPhase = 2; Set-ScriptState -State $State; return (Invoke-DellUpdates -State $State) } } } 1 { Write-SubBanner "Dell Phase-1: Post-Reboot Scan" $null = Invoke-CommandVerbose -FilePath $cli -Arguments "/scan" -Label "DCU" $State.ManufacturerPhase = 2; Set-ScriptState -State $State; return (Invoke-DellUpdates -State $State) } 2 { Write-SubBanner "Dell Phase-2: Apply All Updates" Write-Status STEP "Each update name, download, and install status shown below." $dellExit = Invoke-CommandVerbose -FilePath $cli -Arguments "/applyupdates -forceUpdate=enable -autoSuspendBitLocker=enable" -Label "DCU" switch ($dellExit) { 0 { Write-Status OK "All updates applied."; $State.ManufacturerPhase = 3; Set-ScriptState -State $State; return $true } 1 { $State.ManufacturerPhase = 3; Schedule-RebootAndContinue -State $State -Reason "Dell updates (exit 1)" } 5 { $State.ManufacturerPhase = 3; Schedule-RebootAndContinue -State $State -Reason "Dell updates (exit 5)" } 3 { Write-Status OK "Already up to date."; $State.ManufacturerPhase = 3; Set-ScriptState -State $State; return $true } default { Write-Status WARN "Exit $dellExit - continuing."; $State.ManufacturerPhase = 3; Set-ScriptState -State $State; return $true } } } default { Write-Status OK "Dell updates done."; return $true } } return $true } #=================================================================== # HP UPDATES (FIXED - robust exe discovery) #=================================================================== function Find-HPIA { $knownPaths = @( # Chocolatey install location (priority - most common after choco install) "$env:ProgramData\chocolatey\lib\hpimageassistant\tools\HPImageAssistant.exe" # Standard HP install locations 'C:\HP\HPIA\HPImageAssistant.exe' 'C:\Program Files\HP\HPIA\HPImageAssistant.exe' 'C:\Program Files (x86)\HP\HPIA\HPImageAssistant.exe' 'C:\SWSetup\HPIA\HPImageAssistant.exe' 'C:\Program Files\HPImageAssistant\HPImageAssistant.exe' 'C:\Program Files (x86)\HPImageAssistant\HPImageAssistant.exe' "$env:ProgramData\HP\HPIA\HPImageAssistant.exe" 'C:\HP\HPImageAssistant\HPImageAssistant.exe' ) return Find-Executable -FileName 'HPImageAssistant.exe' -KnownPaths $knownPaths -SearchRoots @('C:\HP', 'C:\SWSetup') -Label "HP Image Assistant" } function Invoke-HPUpdates { param([pscustomobject]$State) Write-Banner "HP SYSTEM UPDATE" # ── Find HPIA before Chocolatey ── Write-SubBanner "Locating HP Image Assistant" $hpia = Find-HPIA # ── Try Chocolatey install if not found ── if (-not $hpia) { Write-Status WARN "HPIA not found - attempting Chocolatey install..." # Try multiple known package names $chocoPackages = @('hpimageassistant', 'hp-hpia', 'hp-image-assistant') $installed = $false foreach ($pkg in $chocoPackages) { Write-Status INFO "Trying Chocolatey package: $pkg" $result = Install-ChocoPackage -PackageName $pkg -DisplayName "HP Image Assistant ($pkg)" if ($result) { $installed = $true; break } } if ($installed) { Start-Sleep -Seconds 5 # Refresh PATH $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User') $hpia = Find-HPIA } } # ── Final validation ── if (-not $hpia) { Write-Status ERROR "HP Image Assistant (HPImageAssistant.exe) could not be found anywhere." Write-Status INFO "Searched: known paths, Chocolatey lib, Program Files, C:\HP, C:\SWSetup" Write-Status INFO "Please install HPIA manually and re-run the script." return $false } # Verify it is a file, not a directory $hpiaItem = Get-Item $hpia -ErrorAction SilentlyContinue if ($null -eq $hpiaItem -or $hpiaItem.PSIsContainer) { Write-Status ERROR "Path is a directory, not an executable: $hpia" return $false } Write-Status OK "HPIA executable: $hpia" Write-Status DEBUG "HPIA size: $([math]::Round($hpiaItem.Length / 1MB, 1)) MB" Write-Status DEBUG "HPIA version: $($hpiaItem.VersionInfo.FileVersion)" # ── Create report folder ── $hpiaReport = 'C:\Temp\HPIA' if (-not (Test-Path $hpiaReport)) { New-Item -Path $hpiaReport -ItemType Directory -Force | Out-Null } # ── Run HPIA ── Write-SubBanner "HP Image Assistant: Analyze & Install" Write-Status STEP "All update names and progress will appear below." $hpArgs = "/Operation:Analyze /Action:Install /Selection:All /Silent /Noninteractive /ReportFolder:`"$hpiaReport`"" $hpExit = Invoke-CommandVerbose -FilePath $hpia -Arguments $hpArgs -Label "HPIA" # ── Report results ── Write-LoggedHost Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost " HP IMAGE ASSISTANT RESULTS" -ForegroundColor Cyan Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost " Executable: $hpia" -ForegroundColor White Write-LoggedHost " Exit code: $hpExit" -ForegroundColor White Write-LoggedHost " Report: $hpiaReport" -ForegroundColor White # Parse report if available $jsonReport = Get-ChildItem -Path $hpiaReport -Filter '*.json' -File -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if ($jsonReport) { Write-LoggedHost " Report file: $($jsonReport.FullName)" -ForegroundColor Green try { $reportData = Get-Content $jsonReport.FullName -Raw -ErrorAction SilentlyContinue | ConvertFrom-Json -ErrorAction SilentlyContinue if ($null -ne $reportData -and $null -ne $reportData.HPIA -and $null -ne $reportData.HPIA.Recommendations) { $recs = @($reportData.HPIA.Recommendations) $recCount = ($recs | Measure-Object).Count Write-LoggedHost " Updates: $recCount recommendation(s)" -ForegroundColor White foreach ($rec in $recs) { $name = if ($rec.Name) { $rec.Name } else { $rec.SoftPaqId } $status = if ($rec.RecommendationValue) { $rec.RecommendationValue } else { 'Unknown' } Write-LoggedHost " - $name ($status)" -ForegroundColor DarkGray } } } catch { Write-Status DEBUG "Could not parse HPIA report: $($_.Exception.Message)" } } else { Write-LoggedHost " Report file: (none found)" -ForegroundColor Yellow } Write-LoggedHost " ======================================================================" -ForegroundColor DarkCyan Write-LoggedHost switch ($hpExit) { 0 { Write-Status OK "HP updates completed successfully."; return $true } 256 { Write-Status WARN "HP: reboot needed for some updates."; return $true } 3010 { Write-Status WARN "HP: reboot needed (exit 3010)."; return $true } 1 { Write-Status WARN "HP: completed with warnings."; return $true } default { Write-Status WARN "HP exit $hpExit - check report at $hpiaReport"; return $true } } } #=================================================================== # MAIN EXECUTION #=================================================================== try { if (-not (Initialize-PersistentScript)) { throw "Failed to initialise." } Start-Transcript -Path $Script:LogFile -Append -Force $sysInfoHeader = Get-SystemInfo $mfg = Get-ManufacturerShareFolder -Manufacturer $sysInfoHeader.RawManufacturer $expectedPath = Build-NetworkDriverPath -ManufacturerFolder $mfg -CleanModel $sysInfoHeader.CleanModel $headerLines = @( "======================================================================" " DRIVER UPDATE SCRIPT - PowerShell $($PSVersionTable.PSVersion)" "======================================================================" " User: $env:USERNAME" " Computer: $env:COMPUTERNAME" " Manufacturer: $($sysInfoHeader.RawManufacturer)" " Model: $($sysInfoHeader.RawModel)" " Clean model: $($sysInfoHeader.CleanModel)" " Driver share: $expectedPath" " Timeout: $Script:DriverTimeout sec/driver" " Choco source: $Script:ChocoSourceName" " Flow: USB(targeted) -> GuestAuth -> Share(targeted) -> OEM(online)" "======================================================================" ) Write-LoggedHost foreach ($hl in $headerLines) { Write-LoggedHost $hl -ForegroundColor Cyan } Write-LoggedHost Write-Status INFO "Checking for orphan processes..." $orphanKilled = Stop-DriverInstallProcesses -Silent if ($orphanKilled -gt 0) { Write-Status WARN "Terminated $orphanKilled."; Start-Sleep -Seconds 2 } $state = Get-ScriptState Write-Host "--- Current State ---" -ForegroundColor DarkGray foreach ($prop in $state.PSObject.Properties) { $stLine = " {0,-35}: {1}" -f $prop.Name, $prop.Value Write-Host $stLine -ForegroundColor DarkGray } Write-Host "--- End State ---" -ForegroundColor DarkGray Write-Host "" # PHASE-0A: USB drivers (TARGETED) if ($state.Phase -eq 0) { if (-not $state.USBCompleted) { $usbResult = Install-USBNetworkDrivers -State $state if ($usbResult.NeedsReboot) { $state.USBCompleted = $true; $state.USBRebootDone = $false; Set-ScriptState -State $state Schedule-RebootAndContinue -State $state -Reason "USB drivers - reboot" } else { $state.USBCompleted = $true; $state.USBRebootDone = $true; Set-ScriptState -State $state } } else { if (-not $state.USBRebootDone) { Write-Status OK "Post-reboot: USB drivers finalized." $state.USBRebootDone = $true; Set-ScriptState -State $state } } # PHASE-0B: Network share drivers (TARGETED) if (-not $state.NetworkShareCompleted) { $netResult = Install-NetworkShareDrivers -State $state if ($netResult.NeedsReboot) { $state.NetworkShareCompleted = $true; $state.NetworkShareRebootDone = $false; Set-ScriptState -State $state Schedule-RebootAndContinue -State $state -Reason "Network share drivers - reboot" } else { $state.NetworkShareCompleted = $true; $state.NetworkShareRebootDone = $true; $state.Phase = 1; Set-ScriptState -State $state } } else { if (-not $state.NetworkShareRebootDone) { Write-Status OK "Post-reboot: Share drivers finalized." $state.NetworkShareRebootDone = $true } $state.Phase = 1; Set-ScriptState -State $state } } # PHASE-1: Online OEM updates if ($state.Phase -ge 1) { Write-Banner "PHASE 1: ONLINE OEM UPDATES" if (-not (Test-InternetConnectivity -MaxRetries 15 -RetryDelay 10)) { Write-Status ERROR "No internet."; try { Stop-Transcript } catch { }; exit 1 } Install-ChocolateyIfNeeded $manufacturer = Get-SystemManufacturer if ($manufacturer -like '*Dell*') { Invoke-DellUpdates -State $state | Out-Null } elseif ($manufacturer -match 'HP|Hewlett[-\s]?Packard') { Invoke-HPUpdates -State $state | Out-Null } else { Write-Status WARN "Unsupported: $manufacturer" } } # FINAL SUMMARY Stop-DriverInstallProcesses -Silent | Out-Null $summaryLines = @( "======================================================================" " DRIVER UPDATE SCRIPT COMPLETED SUCCESSFULLY!" "======================================================================" " System: $($state.SystemManufacturer) $($state.SystemModel)" " Clean model: $($state.CleanModel)" " Share path: $($state.NetworkSharePath)" " Guest auth: $($state.GuestAuthConfigured)" " Reboots: $($state.RebootCount)" " Drivers added: $($state.TotalDriversAdded)" " Installed: $($state.TotalDriversInstalled)" " Skipped: $($state.TotalDriversSkipped)" " Failed: $($state.TotalDriversFailed)" " Timed out: $($state.TotalDriversTimedOut)" " Killed: $($state.TotalProcessesKilled)" " Total time: $(Format-Duration $state.TotalTimeSpent)" "======================================================================" ) Write-LoggedHost foreach ($sl in $summaryLines) { Write-LoggedHost $sl -ForegroundColor Green } Write-LoggedHost Remove-ScriptState } catch { Write-LoggedHost Write-LoggedHost "======================================================================" -ForegroundColor Red Write-LoggedHost " SCRIPT ERROR" -ForegroundColor Red Write-LoggedHost "======================================================================" -ForegroundColor Red Write-Status ERROR "Message: $($_.Exception.Message)" Write-Status ERROR "Line: $($_.InvocationInfo.ScriptLineNumber)" $stackStr = $_.ScriptStackTrace Write-Host $stackStr -ForegroundColor Red Write-LoggedHost Write-Status INFO "Emergency clean-up..." try { $saveEA = $ErrorActionPreference; $ErrorActionPreference = 'SilentlyContinue' Stop-DriverInstallProcesses -Silent | Out-Null $ErrorActionPreference = $saveEA } catch { try { $ErrorActionPreference = $saveEA } catch { } } Write-Status INFO "State file kept - re-run to continue." } finally { try { Stop-Transcript } catch { } }Visual example at runtime
====================================================================== DRIVER UPDATE SCRIPT - PowerShell 5.1.19041.4291 ====================================================================== Manufacturer: Dell Inc. Model: Latitude 5400 Clean model: latitude5400 Driver share: \\networkshare\dell\driver\latitude5400 Mode: USB(NIC) -> NetworkShare(all) -> OEM(online) ====================================================================== ====================================================================== PHASE-0A: USB NETWORK DRIVER INSTALLATION ====================================================================== [>] Installing NIC/WiFi drivers from USB to enable network connectivity. [+] Found USB driver folder: E:\DriversOS [+] Found 3 INF file(s) in: E:\DriversOS [i] Device matching DISABLED for USB NETWORK - installing ALL drivers. ====================================================================== USB NETWORK - DRIVER MATCHING RESULTS ====================================================================== Total INF files: 3 Matched to system devices: 3 Mode: ALL (no filtering) ====================================================================== [####################] 100% (3 of 3 drivers) Driver: Intel-I219LM\e1d68x64.inf [+] Exit code 0 - Success ====================================================================== PHASE-0B: NETWORK SHARE DRIVER INSTALLATION ====================================================================== --- Identifying System Model --- [i] Manufacturer: Dell Inc. [i] Model: Latitude 5400 [i] Clean model: latitude5400 [i] Share folder: dell ====================================================================== NETWORK SHARE PATH RESOLUTION ====================================================================== Base: \\networkshare Manufacturer: dell Driver folder: driver Model: latitude5400 Full path: \\networkshare\dell\driver\latitude5400 ====================================================================== --- Testing Network Share Access --- [i] NIC drivers should be installed from USB - testing network... [+] Network share accessible: \\networkshare\dell\driver\latitude5400 --- Matching NETWORK SHARE Drivers to System Devices --- [+] Scanned 45 device(s), found 312 unique hardware/compatible ID(s). [S] No matching device: Chipset\IntelMEI\heci.inf [S] No matching device: Chipset\AMT\LMS.inf ... (only unmatched shown) ====================================================================== NETWORK SHARE - DRIVER MATCHING RESULTS ====================================================================== Total INF files: 85 Matched to system devices: 22 Skipped (no matching device): 63 Mode: TARGETED (matched only) ====================================================================== --- Installing 22 NETWORK SHARE Driver(s) --- [#---------] 5% (1 of 22 drivers) Driver: Video\Intel-UHD620\igdlh64.inf ...Powershell script that searches for the drivers in the USB
#Requires -RunAsAdministrator Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' #=================================================================== # CONFIGURATION #=================================================================== $Script:TempFolder = "C:\Temp" $Script:PersistentScript = "C:\Temp\DriverUpdateScript.ps1" $Script:StateFile = "C:\Temp\DriverUpdateState.json" $Script:LogFile = "C:\Temp\DriverUpdateScript.log" $Script:RunOnceKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' $Script:RunOnceName = 'DriverUpdateScript_RunOnce' $Script:DriverTimeout = 300 $Script:CooldownSeconds = 5 $Script:DriverProcesses = @('drvinst','pnputil','DPInst','DPInst64','SetupAPI') $Script:SpinnerFrames = @('[ ]','[= ]','[== ]','[=== ]','[====]','[ ===]','[ ==]','[ =]') $Script:ChocoSourceName = 'chocosia' $Script:ChocoSourceUrl = 'http://choco.local.xyz.com/repository/chocolatey-group' #=================================================================== # INITIALISATION #=================================================================== function Initialize-PersistentScript { if (-not (Test-Path $Script:TempFolder)) { New-Item -Path $Script:TempFolder -ItemType Directory -Force | Out-Null } $currentScript = $MyInvocation.ScriptName if (-not $currentScript) { $currentScript = $PSCommandPath } if ($currentScript -and $currentScript -ne $Script:PersistentScript) { if (Test-Path $currentScript) { Write-Host "[INIT] Copying script to $Script:PersistentScript" -ForegroundColor Yellow Copy-Item -Path $currentScript -Destination $Script:PersistentScript -Force Write-Host "[INIT] Script copied successfully." -ForegroundColor Green } } if (-not (Test-Path $Script:PersistentScript)) { Write-Host "[ERROR] Persistent script could not be created." -ForegroundColor Red return $false } return $true } #=================================================================== # LOGGING HELPERS #=================================================================== function Write-Status { param( [Parameter(Mandatory)] [ValidateSet('ERROR','WARN','INFO','STEP','OK','PROGRESS','DEBUG','DRIVER','TIME','SKIP','KILL')] [string]$Level, [Parameter(Mandatory)][string]$Message ) $timestamp = Get-Date -Format "HH:mm:ss" $color = switch ($Level.ToUpper()) { 'ERROR' { 'Red' } 'WARN' { 'Yellow' } 'INFO' { 'Cyan' } 'STEP' { 'Magenta' } 'OK' { 'Green' } 'PROGRESS' { 'White' } 'DEBUG' { 'DarkGray' } 'DRIVER' { 'Blue' } 'TIME' { 'DarkYellow' } 'SKIP' { 'DarkMagenta' } 'KILL' { 'Red' } default { 'White' } } $prefix = switch ($Level.ToUpper()) { 'ERROR' { '[X]' } 'WARN' { '[!]' } 'INFO' { '[i]' } 'STEP' { '[>]' } 'OK' { '[+]' } 'PROGRESS' { '[o]' } 'DEBUG' { '[.]' } 'DRIVER' { '[D]' } 'TIME' { '[T]' } 'SKIP' { '[S]' } 'KILL' { '[K]' } default { '[ ]' } } Write-Host "[$timestamp] $prefix $Message" -ForegroundColor $color } function Write-Banner { param([string]$Text) Write-Host "" Write-Host ("=" * 70) -ForegroundColor Magenta Write-Host " $Text" -ForegroundColor Magenta Write-Host ("=" * 70) -ForegroundColor Magenta Write-Host "" } function Write-SubBanner { param([string]$Text) Write-Host "" Write-Host "--- $Text ---" -ForegroundColor Yellow Write-Host "" } function Format-Duration { param([double]$Seconds) if ($Seconds -lt 60) { return "{0:N1}s" -f $Seconds } elseif ($Seconds -lt 3600) { $m = [math]::Floor($Seconds / 60) $s = $Seconds % 60 return "{0}m {1:N0}s" -f $m, $s } else { $h = [math]::Floor($Seconds / 3600) $m = [math]::Floor(($Seconds % 3600) / 60) return "{0}h {1}m" -f $h, $m } } function Get-ConsoleWidth { $w = 100 try { $w = [Console]::WindowWidth - 5 if ($w -lt 50) { $w = 100 } } catch { } return $w } function Write-DriverHeader { param([string]$DriverName, [int]$Current, [int]$Total) if ($Total -le 0) { $Total = 1 } $pct = [math]::Round(($Current / $Total) * 100) $filled = [math]::Floor($pct / 5) $empty = 20 - $filled $bar = "[" + ("#" * $filled) + ("-" * $empty) + "]" Write-Host "" Write-Host " ======================================================================" -ForegroundColor DarkCyan Write-Host " $bar $pct% ($Current of $Total drivers)" -ForegroundColor Cyan Write-Host " Driver: $DriverName" -ForegroundColor White Write-Host " Timeout: $Script:DriverTimeout seconds | Monitoring: drvinst.exe" -ForegroundColor DarkGray Write-Host " ======================================================================" -ForegroundColor DarkCyan } #=================================================================== # PROCESS-HANDLING HELPERS #=================================================================== function Get-DriverInstallProcesses { [System.Collections.ArrayList]$collector = @() foreach ($name in $Script:DriverProcesses) { $found = $null try { $found = Get-Process -Name $name -ErrorAction SilentlyContinue } catch { } if ($null -ne $found) { foreach ($p in @($found)) { [void]$collector.Add($p) } } } $arr = [object[]]$collector.ToArray() return ,$arr } function Stop-DriverInstallProcesses { param([switch]$Silent) $killed = 0 foreach ($name in $Script:DriverProcesses) { $found = $null try { $found = Get-Process -Name $name -ErrorAction SilentlyContinue } catch { } if ($null -ne $found) { foreach ($proc in @($found)) { if (-not $Silent) { Write-Status KILL "Terminating: $($proc.ProcessName) (PID $($proc.Id))" } try { $proc.CloseMainWindow() | Out-Null } catch { } Start-Sleep -Milliseconds 300 try { if (-not $proc.HasExited) { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } } catch { } $killed++ } } } foreach ($name in $Script:DriverProcesses) { try { $saveEA = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' $null = cmd.exe /c "taskkill /F /IM `"$name.exe`" >nul 2>&1" $ErrorActionPreference = $saveEA } catch { try { $ErrorActionPreference = $saveEA } catch { } } } return $killed } function Wait-ForDriverProcesses { param([int]$TimeoutSeconds = 30) $sw = [System.Diagnostics.Stopwatch]::StartNew() while ($sw.Elapsed.TotalSeconds -lt $TimeoutSeconds) { $procs = Get-DriverInstallProcesses $procCount = ($procs | Measure-Object).Count if ($procCount -eq 0) { return $true } Start-Sleep -Milliseconds 500 } return $false } function Clear-DriverInstallEnvironment { $procs = Get-DriverInstallProcesses $procCount = ($procs | Measure-Object).Count if ($procCount -gt 0) { Write-Status WARN "Found $procCount leftover driver process(es) - cleaning up..." $k = Stop-DriverInstallProcesses Write-Status INFO "Terminated $k process(es)" Start-Sleep -Seconds 2 } } #=================================================================== # VERBOSE COMMAND EXECUTION (real-time stdout/stderr streaming) #=================================================================== function Invoke-CommandVerbose { <# .SYNOPSIS Starts a process, streams stdout/stderr line-by-line with timestamps, shows a heartbeat if silent for 30s, saves a full log, returns exit code. #> param( [Parameter(Mandatory)][string]$FilePath, [Parameter(Mandatory)][string]$Arguments, [Parameter(Mandatory)][string]$Label ) if (-not (Test-Path $FilePath)) { Write-Status ERROR "Executable not found: $FilePath" return 999 } $exeName = Split-Path $FilePath -Leaf Write-Status INFO "Executing: $exeName $Arguments" Write-Host "" $psi = [System.Diagnostics.ProcessStartInfo]::new() $psi.FileName = $FilePath $psi.Arguments = $Arguments $psi.UseShellExecute = $false $psi.RedirectStandardOutput = $true $psi.RedirectStandardError = $true $psi.CreateNoWindow = $true $process = [System.Diagnostics.Process]::new() $process.StartInfo = $psi try { $null = $process.Start() } catch { Write-Status ERROR "Failed to start $exeName : $($_.Exception.Message)" return 999 } $sw = [System.Diagnostics.Stopwatch]::StartNew() $lastActivity = [System.Diagnostics.Stopwatch]::StartNew() $heartbeatSec = 30 $logLines = [System.Collections.ArrayList]::new() # Async line readers for stdout and stderr $stdoutTask = $process.StandardOutput.ReadLineAsync() $stderrTask = $process.StandardError.ReadLineAsync() $stdoutDone = $false $stderrDone = $false while ((-not $stdoutDone) -or (-not $stderrDone)) { $activity = $false # --- stdout --- if ((-not $stdoutDone) -and $stdoutTask.IsCompleted) { $line = $null try { $line = $stdoutTask.Result } catch { $stdoutDone = $true; continue } if ($null -ne $line) { $trimmed = $line.Trim() if ($trimmed) { $ts = Get-Date -Format 'HH:mm:ss' Write-Host " [$ts] [$Label] $trimmed" -ForegroundColor White [void]$logLines.Add("[$ts] $trimmed") } $stdoutTask = $process.StandardOutput.ReadLineAsync() $activity = $true $lastActivity.Restart() } else { $stdoutDone = $true } } # --- stderr --- if ((-not $stderrDone) -and $stderrTask.IsCompleted) { $line = $null try { $line = $stderrTask.Result } catch { $stderrDone = $true; continue } if ($null -ne $line) { $trimmed = $line.Trim() if ($trimmed) { $ts = Get-Date -Format 'HH:mm:ss' Write-Host " [$ts] [$Label] $trimmed" -ForegroundColor Yellow [void]$logLines.Add("[$ts] [WARN] $trimmed") } $stderrTask = $process.StandardError.ReadLineAsync() $activity = $true $lastActivity.Restart() } else { $stderrDone = $true } } # --- heartbeat when process is silent --- if ((-not $activity) -and ($lastActivity.Elapsed.TotalSeconds -ge $heartbeatSec)) { $ts = Get-Date -Format 'HH:mm:ss' $elapsed = [math]::Round($sw.Elapsed.TotalSeconds) Write-Host " [$ts] [$Label] ... still running (${elapsed}s elapsed)" -ForegroundColor DarkGray [void]$logLines.Add("[$ts] [heartbeat] ${elapsed}s elapsed") $lastActivity.Restart() } if (-not $activity) { Start-Sleep -Milliseconds 100 } } $process.WaitForExit() $sw.Stop() $exitCode = $process.ExitCode # Save detailed log $safeLabel = $Label -replace '[^a-zA-Z0-9]', '_' $logFile = Join-Path $Script:TempFolder "${safeLabel}_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" try { $header = "Command : $FilePath $Arguments`nExit : $exitCode`nDuration: $(Format-Duration $sw.Elapsed.TotalSeconds)`n$('=' * 60)`n" $body = ($logLines | ForEach-Object { $_ }) -join "`n" "$header$body" | Out-File $logFile -Encoding UTF8 -Force } catch { } Write-Host "" Write-Status TIME "Completed in $(Format-Duration $sw.Elapsed.TotalSeconds)" Write-Status INFO "Exit code: $exitCode" Write-Status DEBUG "Log: $logFile" try { $process.Dispose() } catch { } return $exitCode } #=================================================================== # CHOCOLATEY PATH HELPER #=================================================================== function Get-ChocoExePath { $cmd = Get-Command choco -ErrorAction SilentlyContinue if ($null -ne $cmd) { return $cmd.Source } $default = "$env:ProgramData\chocolatey\bin\choco.exe" if (Test-Path $default) { return $default } return $null } #=================================================================== # DEVICE SCANNING & INF MATCHING #=================================================================== function Get-SystemDeviceIds { $ids = [System.Collections.Generic.HashSet[string]]::new( [System.StringComparer]::OrdinalIgnoreCase ) $deviceCount = 0 $problemDeviceNames = [System.Collections.ArrayList]::new() try { $devices = @(Get-CimInstance Win32_PnPEntity -ErrorAction Stop) $deviceCount = ($devices | Measure-Object).Count foreach ($dev in $devices) { if ($null -ne $dev.HardwareID) { foreach ($hwid in @($dev.HardwareID)) { if ($hwid) { [void]$ids.Add($hwid) } } } if ($null -ne $dev.CompatibleID) { foreach ($cid in @($dev.CompatibleID)) { if ($cid) { [void]$ids.Add($cid) } } } if ($null -ne $dev.ConfigManagerErrorCode -and $dev.ConfigManagerErrorCode -ne 0) { $devName = $dev.Name if (-not $devName) { $devName = $dev.DeviceID } [void]$problemDeviceNames.Add("$devName (error $($dev.ConfigManagerErrorCode))") } } } catch { Write-Status WARN "WMI device scan failed: $($_.Exception.Message)" } if ($ids.Count -eq 0) { Write-Status INFO "Trying Get-PnpDevice as fallback..." try { $pnpDevs = @(Get-PnpDevice -ErrorAction SilentlyContinue) $deviceCount = ($pnpDevs | Measure-Object).Count foreach ($dev in $pnpDevs) { try { $hwProp = $dev | Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_HardwareIds' -ErrorAction SilentlyContinue if ($null -ne $hwProp -and $null -ne $hwProp.Data) { foreach ($id in @($hwProp.Data)) { if ($id) { [void]$ids.Add($id) } } } } catch { } try { $cProp = $dev | Get-PnpDeviceProperty -KeyName 'DEVPKEY_Device_CompatibleIds' -ErrorAction SilentlyContinue if ($null -ne $cProp -and $null -ne $cProp.Data) { foreach ($id in @($cProp.Data)) { if ($id) { [void]$ids.Add($id) } } } } catch { } } } catch { Write-Status WARN "Get-PnpDevice fallback also failed: $($_.Exception.Message)" } } Write-Status OK "Scanned $deviceCount device(s), found $($ids.Count) unique hardware/compatible ID(s)." $probCount = ($problemDeviceNames | Measure-Object).Count if ($probCount -gt 0) { Write-Status WARN "$probCount device(s) with problems (may need drivers):" foreach ($pd in $problemDeviceNames) { Write-Status INFO " - $pd" } } else { Write-Status OK "No devices with driver problems detected." } return $ids } function Get-InfHardwareIds { param([Parameter(Mandatory)][string]$InfPath) $hardwareIds = [System.Collections.Generic.HashSet[string]]::new( [System.StringComparer]::OrdinalIgnoreCase ) try { $lines = @(Get-Content $InfPath -ErrorAction Stop) } catch { return @($hardwareIds) } $lineCount = ($lines | Measure-Object).Count if ($lineCount -eq 0) { return @($hardwareIds) } $modelSections = [System.Collections.Generic.HashSet[string]]::new( [System.StringComparer]::OrdinalIgnoreCase ) $inManufacturer = $false foreach ($line in $lines) { $trimmed = $line.Trim() if ($trimmed.StartsWith(';')) { continue } if ($trimmed -match '^\[Manufacturer\]\s*$') { $inManufacturer = $true; continue } if ($inManufacturer) { if ($trimmed -match '^\[') { $inManufacturer = $false; continue } if (-not $trimmed) { continue } if ($trimmed -match '=') { $rightSide = ($trimmed -split '=', 2)[1] if ($null -eq $rightSide) { continue } $parts = $rightSide -split ',' $baseName = $parts[0].Trim() if (-not $baseName) { continue } [void]$modelSections.Add($baseName) $partCount = ($parts | Measure-Object).Count for ($j = 1; $j -lt $partCount; $j++) { $decoration = $parts[$j].Trim() if ($decoration) { [void]$modelSections.Add("$baseName.$decoration") } } } } } foreach ($section in @($modelSections)) { $sectionHeader = "[$section]" $inSection = $false foreach ($line in $lines) { $trimmed = $line.Trim() if ($trimmed.StartsWith(';')) { continue } if ($trimmed -ieq $sectionHeader) { $inSection = $true; continue } if ($inSection) { if ($trimmed -match '^\[') { $inSection = $false; continue } if (-not $trimmed) { continue } if ($trimmed -match '=') { $rightSide = ($trimmed -split '=', 2)[1] if ($null -eq $rightSide) { continue } $idParts = $rightSide -split ',' $idPartCount = ($idParts | Measure-Object).Count for ($j = 1; $j -lt $idPartCount; $j++) { $hwid = $idParts[$j].Trim().Trim('"').Trim() if ($hwid -and $hwid -match '\\') { [void]$hardwareIds.Add($hwid) } } } } } } return @($hardwareIds) } function Test-InfMatchesSystem { param( [Parameter(Mandatory)][string]$InfPath, [Parameter(Mandatory)][System.Collections.Generic.HashSet[string]]$SystemIds ) $infIds = Get-InfHardwareIds -InfPath $InfPath $infIdCount = ($infIds | Measure-Object).Count if ($infIdCount -eq 0) { return $true } foreach ($infId in $infIds) { if ($SystemIds.Contains($infId)) { return $true } } return $false } #=================================================================== # STATE-MANAGEMENT HELPERS #=================================================================== function Get-ScriptState { if (Test-Path $Script:StateFile) { try { $json = Get-Content $Script:StateFile -Raw -ErrorAction Stop | ConvertFrom-Json $defaults = @{ Phase = 0; VentoyCompleted = $false; VentoyRebootDone = $false VentoyProcessedDrivers = @(); VentoyDriverRoot = "" ManufacturerPhase = 0; DellADRFailed = $false; LastExitCode = 0 RebootCount = 0; TotalDriversAdded = 0; TotalDriversInstalled = 0 TotalDriversFailed = 0; TotalDriversSkipped = 0 TotalDriversTimedOut = 0; TotalTimeSpent = 0; TotalProcessesKilled = 0 } foreach ($k in $defaults.Keys) { if (-not $json.PSObject.Properties[$k]) { $json | Add-Member -NotePropertyName $k -NotePropertyValue $defaults[$k] -Force } } return $json } catch { Write-Status WARN "Failed reading state file - starting fresh: $($_.Exception.Message)" } } return [PSCustomObject]@{ Phase = 0; VentoyCompleted = $false; VentoyRebootDone = $false VentoyProcessedDrivers = @(); VentoyDriverRoot = "" ManufacturerPhase = 0; DellADRFailed = $false; LastExitCode = 0 RebootCount = 0; TotalDriversAdded = 0; TotalDriversInstalled = 0 TotalDriversFailed = 0; TotalDriversSkipped = 0 TotalDriversTimedOut = 0; TotalTimeSpent = 0; TotalProcessesKilled = 0 } } function Set-ScriptState { param([pscustomobject]$State) $State | ConvertTo-Json -Depth 10 | Out-File $Script:StateFile -Encoding UTF8 -Force } function Remove-ScriptState { Write-Status INFO "Removing state file and RunOnce entry..." if (Test-Path $Script:StateFile) { Remove-Item $Script:StateFile -Force -ErrorAction SilentlyContinue } try { $rk64 = [Microsoft.Win32.RegistryKey]::OpenBaseKey( [Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64 ).OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', $true) if ($null -ne $rk64) { $rk64.DeleteValue($Script:RunOnceName, $false); $rk64.Close() } } catch { } $fallback = "$($Script:RunOnceName)_Task" try { $existingTask = Get-ScheduledTask -TaskName $fallback -ErrorAction SilentlyContinue if ($null -ne $existingTask) { Unregister-ScheduledTask -TaskName $fallback -Confirm:$false -ErrorAction SilentlyContinue Write-Status INFO "Deleted fallback Scheduled-Task: $fallback" } } catch { } Write-Status OK "Cleanup finished." } #=================================================================== # RUNONCE HELPERS #=================================================================== function Set-RunOnceEntry { param([Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$Command) try { $rk64 = [Microsoft.Win32.RegistryKey]::OpenBaseKey( [Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry64 ).OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', $true) $rk64.SetValue($Name, $Command, [Microsoft.Win32.RegistryValueKind]::String) $rk64.Close() Write-Status OK "RunOnce entry created (64-bit): $Name" return $true } catch { Write-Status WARN "Failed to write RunOnce (64-bit): $($_.Exception.Message)" return $false } } function Set-RunOnceTask { param([Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$ScriptPath) $taskAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoLogo -NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`"" $taskTrigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest -LogonType ServiceAccount try { Register-ScheduledTask -TaskName $Name -Action $taskAction -Trigger $taskTrigger -Principal $principal -Force -ErrorAction Stop Write-Status OK "Fallback Scheduled-Task created: $Name" return $true } catch { Write-Status ERROR "Could not create fallback Scheduled-Task: $($_.Exception.Message)" return $false } } function Schedule-RebootAndContinue { param([pscustomobject]$State, [string]$Reason = "Script requires reboot to continue") Write-Host "" Write-Host "======================================================================" -ForegroundColor Yellow Write-Host " REBOOT REQUIRED" -ForegroundColor Yellow Write-Host "======================================================================" -ForegroundColor Yellow Write-Host " Reason: $Reason" -ForegroundColor Yellow Write-Host " The script will automatically resume after reboot." -ForegroundColor Yellow Write-Host "======================================================================" -ForegroundColor Yellow Write-Host "" Write-Status INFO "Cleaning up driver processes before reboot..." Stop-DriverInstallProcesses -Silent | Out-Null $State.RebootCount++ Set-ScriptState -State $State $cmd = "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$Script:PersistentScript`"" $runOnceCreated = Set-RunOnceEntry -Name $Script:RunOnceName -Command $cmd if (-not $runOnceCreated) { $fallbackName = "$($Script:RunOnceName)_Task" $taskCreated = Set-RunOnceTask -Name $fallbackName -ScriptPath $Script:PersistentScript if (-not $taskCreated) { Write-Status ERROR "Unable to schedule script after reboot - manual restart required." } } Write-Status INFO "Reboot scheduled - system will restart in 15 seconds..." for ($i = 15; $i -ge 1; $i--) { Write-Host " Rebooting in $i seconds..." -ForegroundColor Yellow Start-Sleep -Seconds 1 } Write-Host "" Write-Host " Rebooting NOW!" -ForegroundColor Red Write-Host "" try { Stop-Transcript } catch { } Restart-Computer -Force exit 0 } #=================================================================== # DRIVER INSTALLATION (single driver with timeout & monitoring) #=================================================================== function Install-SingleDriver { param( [Parameter(Mandatory)][string]$InfPath, [Parameter(Mandatory)][string]$DriverName, [Parameter(Mandatory)][int]$CurrentNumber, [Parameter(Mandatory)][int]$TotalCount, [int]$TimeoutSeconds = $Script:DriverTimeout ) $result = [PSCustomObject]@{ InfPath = $InfPath; DriverName = $DriverName; Success = $false ExitCode = -1; Added = $false; Installed = $false; AlreadyExists = $false NeedsReboot = $false; TimedOut = $false; ProcessesKilled = 0 ErrorMessage = ""; Output = ""; Duration = 0 } Write-DriverHeader -DriverName $DriverName -Current $CurrentNumber -Total $TotalCount Write-Status DRIVER "Path: $InfPath" Clear-DriverInstallEnvironment Write-Status INFO "Starting installation with process monitoring..." Write-Host "" $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $consoleWidth = Get-ConsoleWidth $tempScript = [IO.Path]::GetTempFileName() + ".ps1" $outFile = [IO.Path]::GetTempFileName() $exitFile = [IO.Path]::GetTempFileName() $wrapperContent = @" try { `$out = & pnputil.exe /add-driver "$InfPath" /install 2>&1 | Out-String `$out | Out-File -FilePath "$outFile" -Encoding UTF8 `$LASTEXITCODE | Out-File -FilePath "$exitFile" -Encoding UTF8 } catch { `$_.Exception.Message | Out-File -FilePath "$outFile" -Encoding UTF8 "999" | Out-File -FilePath "$exitFile" -Encoding UTF8 } "@ $wrapperContent | Out-File -FilePath $tempScript -Encoding UTF8 $proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$tempScript`"" -PassThru -WindowStyle Hidden $frameIdx = 0 $frameCount = $Script:SpinnerFrames.Length $animLine = 0 try { $animLine = [Console]::CursorTop } catch { } while ((-not $proc.HasExited) -and ($stopwatch.Elapsed.TotalSeconds -lt $TimeoutSeconds)) { $elapsed = $stopwatch.Elapsed.TotalSeconds $remaining = [math]::Max(0, $TimeoutSeconds - $elapsed) $pct = [math]::Round(($elapsed / $TimeoutSeconds) * 100) $spinner = $Script:SpinnerFrames[$frameIdx % $frameCount] $frameIdx++ $line = " $spinner $pct% | Elapsed: $([math]::Round($elapsed))s | Remaining: $([math]::Round($remaining))s" $padded = $line.PadRight($consoleWidth) try { [Console]::SetCursorPosition(0, $animLine) [Console]::ForegroundColor = [ConsoleColor]::Cyan [Console]::Write($padded) [Console]::ResetColor() } catch { Write-Host "`r$padded" -NoNewline -ForegroundColor Cyan } Start-Sleep -Milliseconds 150 } try { [Console]::SetCursorPosition(0, $animLine) [Console]::Write(' ' * $consoleWidth) [Console]::SetCursorPosition(0, $animLine) } catch { Write-Host "`r$(' ' * $consoleWidth)`r" -NoNewline } $stopwatch.Stop() $result.Duration = $stopwatch.Elapsed.TotalSeconds if (-not $proc.HasExited) { Write-Host "" Write-Status SKIP "TIMEOUT after $(Format-Duration $TimeoutSeconds)!" try { $proc.Kill() } catch { } $result.TimedOut = $true $result.ErrorMessage = "Timeout after $TimeoutSeconds seconds" $killed = Stop-DriverInstallProcesses $result.ProcessesKilled = $killed Write-Status KILL "Terminated $killed driver-process(es)" $null = Wait-ForDriverProcesses -TimeoutSeconds 10 Remove-Item $tempScript, $outFile, $exitFile -Force -ErrorAction SilentlyContinue Write-Status TIME "Time spent: $(Format-Duration $result.Duration) (TIMEOUT)" if ($Script:CooldownSeconds -gt 0) { Start-Sleep -Seconds $Script:CooldownSeconds } Write-Host "" return $result } $out = "" $exit = -1 if (Test-Path $outFile) { $out = Get-Content $outFile -Raw -ErrorAction SilentlyContinue } if (Test-Path $exitFile) { $raw = Get-Content $exitFile -Raw -ErrorAction SilentlyContinue if ($null -ne $raw) { [int]::TryParse($raw.Trim(), [ref]$exit) | Out-Null } } Remove-Item $tempScript, $outFile, $exitFile -Force -ErrorAction SilentlyContinue $result.ExitCode = $exit $result.Output = $out if ($null -ne $out) { if ($out -match 'added|successfully added|driver package added') { $result.Added = $true; Write-Status OK "Driver added to store" } if ($out -match 'install.*device|installed to device') { $result.Installed = $true; Write-Status OK "Driver installed on device" } if ($out -match 'already exists|up to date') { $result.AlreadyExists = $true; Write-Status INFO "Driver already up-to-date" } if ($out -match 'reboot|restart') { $result.NeedsReboot = $true; Write-Status WARN "Reboot required (output)" } } switch ($exit) { 0 { $result.Success = $true; Write-Status OK "Exit code 0 - Success" } 1 { $result.Success = $true; Write-Status INFO "Exit code 1 - Success with warnings" } 259 { $result.Success = $true; Write-Status INFO "Exit code 259 - staged (no matching device)" } 3010 { $result.Success = $true; $result.NeedsReboot = $true; Write-Status WARN "Exit code 3010 - Reboot required" } 482 { $result.Success = $true; $result.NeedsReboot = $true; Write-Status WARN "Exit code 482 - Partial success, reboot needed" } default { if ($exit -ge 0) { if ($result.Added -or $result.Installed -or $result.AlreadyExists) { $result.Success = $true; Write-Status INFO "Exit code $exit - driver processed" } else { $result.Success = $false; $result.ErrorMessage = "Exit code $exit" Write-Status ERROR "Exit code $exit - failure" } } else { $result.Success = $true; Write-Status WARN "Unable to determine exit code" } } } Write-Status TIME "Time taken: $(Format-Duration $result.Duration)" if ($Script:CooldownSeconds -gt 0) { Start-Sleep -Seconds $Script:CooldownSeconds } return $result } #=================================================================== # PHASE-0: VENTOY OFFLINE DRIVER INSTALLATION #=================================================================== function Install-VentoyDrivers { param([string[]]$FolderCandidates = @('DriversOS','Drivers'), [pscustomobject]$State) Write-Banner "PHASE-0: VENTOY OFFLINE DRIVER INSTALLATION" Write-Status INFO "Performing initial cleanup..." Clear-DriverInstallEnvironment $log = Join-Path $env:WINDIR 'Temp\InstallVentoyDrivers.log' "=== Install-VentoyDrivers start $(Get-Date) ===" | Out-File $log -Encoding UTF8 -Append $result = [PSCustomObject]@{ Success = $false; NeedsReboot = $false; NotFound = $false TotalFound = 0; TotalMatched = 0; TotalSkippedNoMatch = 0 TotalAdded = 0; TotalInstalled = 0; TotalFailed = 0; TotalSkipped = 0 TotalAlreadyExist = 0; TotalTimedOut = 0; TotalTime = 0; TotalKilled = 0 } $root = $null if ($State.VentoyDriverRoot -and (Test-Path $State.VentoyDriverRoot)) { $root = $State.VentoyDriverRoot Write-Status OK "Using saved driver root: $root" } else { $driveLetters = @('D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') foreach ($drive in $driveLetters) { foreach ($folder in $FolderCandidates) { $candidate = "${drive}:\$folder" if (Test-Path $candidate) { $root = $candidate; Write-Status OK "Found driver folder: $root"; break } } if ($null -ne $root) { break } } } if ($null -eq $root) { Write-Status INFO "No Ventoy driver folder found." $result.NotFound = $true; $result.Success = $true; return $result } $State.VentoyDriverRoot = $root; Set-ScriptState -State $State $allInfFiles = @(Get-ChildItem -Path $root -Filter "*.inf" -Recurse -File -ErrorAction SilentlyContinue | Sort-Object FullName) $totalFound = ($allInfFiles | Measure-Object).Count if ($totalFound -eq 0) { Write-Status WARN "No .inf files discovered." $result.NotFound = $true; $result.Success = $true; return $result } $result.TotalFound = $totalFound Write-Status OK "Found $totalFound INF file(s) in driver folder." $processed = @() if ($State.VentoyProcessedDrivers) { $processed = @($State.VentoyProcessedDrivers) } $processedCount = ($processed | Measure-Object).Count if ($processedCount -gt 0) { Write-Status INFO "$processedCount driver(s) already processed." } Write-SubBanner "Scanning System Devices" $scanSw = [System.Diagnostics.Stopwatch]::StartNew() $systemIds = Get-SystemDeviceIds $scanSw.Stop() Write-Status TIME "Device scan took $(Format-Duration $scanSw.Elapsed.TotalSeconds)" Write-SubBanner "Matching Drivers to System Devices" $matchSw = [System.Diagnostics.Stopwatch]::StartNew() $matchedInfs = [System.Collections.ArrayList]::new() $skippedNoMatch = [System.Collections.ArrayList]::new() $fallbackMode = $false if ($systemIds.Count -eq 0) { Write-Status WARN "Could not enumerate any device IDs - installing ALL drivers as fallback." $fallbackMode = $true foreach ($inf in $allInfFiles) { [void]$matchedInfs.Add($inf) } } else { foreach ($inf in $allInfFiles) { $rel = $inf.FullName.Substring($root.Length + 1) if ($rel -in $processed) { continue } $isMatch = Test-InfMatchesSystem -InfPath $inf.FullName -SystemIds $systemIds if ($isMatch) { [void]$matchedInfs.Add($inf) } else { [void]$skippedNoMatch.Add($inf); Write-Status SKIP "No matching device: $rel" } } } $matchSw.Stop() $matchedCount = ($matchedInfs | Measure-Object).Count $skippedNoMatchCount = ($skippedNoMatch | Measure-Object).Count $result.TotalMatched = $matchedCount $result.TotalSkippedNoMatch = $skippedNoMatchCount Write-Host "" Write-Host " ======================================================================" -ForegroundColor DarkCyan Write-Host " DRIVER MATCHING RESULTS" -ForegroundColor Cyan Write-Host " ======================================================================" -ForegroundColor DarkCyan Write-Host " Total INF files found: $totalFound" -ForegroundColor White Write-Host " Already processed: $processedCount" -ForegroundColor DarkGray Write-Host " Matched to system devices: $matchedCount" -ForegroundColor Green Write-Host " Skipped (no matching device): $skippedNoMatchCount" -ForegroundColor Yellow Write-Host " Matching time: $(Format-Duration $matchSw.Elapsed.TotalSeconds)" -ForegroundColor DarkGray $modeLabel = if ($fallbackMode) { "FALLBACK (all drivers)" } else { "TARGETED (matched only)" } $modeColor = if ($fallbackMode) { 'Red' } else { 'Green' } Write-Host " Mode: $modeLabel" -ForegroundColor $modeColor Write-Host " ======================================================================" -ForegroundColor DarkCyan Write-Host "" if ($matchedCount -eq 0) { Write-Status OK "No new drivers to install." $result.Success = $true; return $result } $overallSw = [System.Diagnostics.Stopwatch]::StartNew() $globalReboot = $false $i = 0 Write-SubBanner "Installing $matchedCount Matched Driver(s)" foreach ($inf in $matchedInfs) { $i++ $rel = $inf.FullName.Substring($root.Length + 1) if ($rel -in $processed) { continue } $drvResult = Install-SingleDriver -InfPath $inf.FullName -DriverName $rel -CurrentNumber $i -TotalCount $matchedCount $result.TotalTime += $drvResult.Duration $result.TotalKilled += $drvResult.ProcessesKilled if ($drvResult.TimedOut) { $result.TotalTimedOut++ } elseif ($drvResult.Added) { $result.TotalAdded++ } if ($drvResult.Installed) { $result.TotalInstalled++ } if ($drvResult.AlreadyExists) { $result.TotalAlreadyExist++ } if ((-not $drvResult.Success) -and (-not $drvResult.TimedOut)) { $result.TotalFailed++ } if ($drvResult.NeedsReboot) { $globalReboot = $true } $processed += $rel $State.VentoyProcessedDrivers = $processed $State.TotalDriversAdded = $result.TotalAdded $State.TotalDriversInstalled = $result.TotalInstalled $State.TotalDriversFailed = $result.TotalFailed $State.TotalDriversSkipped = $result.TotalSkippedNoMatch $State.TotalDriversTimedOut = $result.TotalTimedOut $State.TotalTimeSpent = $result.TotalTime $State.TotalProcessesKilled = $result.TotalKilled Set-ScriptState -State $State } $overallSw.Stop() $failColor = if ($result.TotalFailed -gt 0) { 'Red' } else { 'Green' } $timeoutColor = if ($result.TotalTimedOut -gt 0) { 'Yellow' } else { 'Green' } $killColor = if ($result.TotalKilled -gt 0) { 'Yellow' } else { 'Green' } $rebootColor = if ($globalReboot) { 'Yellow' } else { 'Green' } Write-Host "" Write-Host "======================================================================" -ForegroundColor Green Write-Host " VENTOY DRIVER INSTALLATION SUMMARY" -ForegroundColor Green Write-Host "======================================================================" -ForegroundColor Green Write-Host " Total INFs found: $($result.TotalFound)" -ForegroundColor White Write-Host " Matched to devices: $($result.TotalMatched)" -ForegroundColor Green Write-Host " Skipped (no match): $($result.TotalSkippedNoMatch)" -ForegroundColor Yellow Write-Host " Added to store: $($result.TotalAdded)" -ForegroundColor Green Write-Host " Installed on hardware: $($result.TotalInstalled)" -ForegroundColor Green Write-Host " Already up-to-date: $($result.TotalAlreadyExist)" -ForegroundColor Cyan Write-Host " Failed: $($result.TotalFailed)" -ForegroundColor $failColor Write-Host " Timed out: $($result.TotalTimedOut)" -ForegroundColor $timeoutColor Write-Host " Processes killed: $($result.TotalKilled)" -ForegroundColor $killColor Write-Host " Total install time: $(Format-Duration $overallSw.Elapsed.TotalSeconds)" -ForegroundColor Magenta Write-Host " Reboot required: $globalReboot" -ForegroundColor $rebootColor Write-Host "======================================================================" -ForegroundColor Green Write-Host "" $result.Success = $true $result.NeedsReboot = $globalReboot return $result } #=================================================================== # INTERNET CONNECTIVITY CHECK #=================================================================== function Test-InternetConnectivity { param([int]$MaxRetries = 15, [int]$RetryDelay = 10) Write-Banner "CHECKING INTERNET CONNECTIVITY" $w = Get-ConsoleWidth $totalFrames = $Script:SpinnerFrames.Length for ($i = 1; $i -le $MaxRetries; $i++) { Write-Status PROGRESS "Attempt $i of $MaxRetries..." try { $r = Invoke-WebRequest -Uri "https://www.google.com" -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop if ($r.StatusCode -eq 200) { Write-Status OK "Internet reachable."; return $true } } catch { Write-Status WARN "Attempt $i failed: $($_.Exception.Message)" if ($i -lt $MaxRetries) { Write-Status INFO "Retrying in $RetryDelay seconds..." for ($s = $RetryDelay; $s -ge 1; $s--) { $spinner = $Script:SpinnerFrames[($RetryDelay - $s) % $totalFrames] $line = " $spinner Waiting $s sec..." $pad = $line.PadRight($w) try { [Console]::SetCursorPosition(0, [Console]::CursorTop) [Console]::ForegroundColor = [ConsoleColor]::DarkGray [Console]::Write($pad) [Console]::ResetColor() } catch { Write-Host "`r$pad" -NoNewline -ForegroundColor DarkGray } Start-Sleep -Seconds 1 } } } } Write-Status ERROR "No internet after $MaxRetries attempts." return $false } #=================================================================== # MANUFACTURER DETECTION #=================================================================== function Get-SystemManufacturer { Write-SubBanner "Detecting System Manufacturer" $cs = Get-CimInstance Win32_ComputerSystem $man = $cs.Manufacturer.Trim() $mod = $cs.Model.Trim() Write-Status INFO "Manufacturer: $man" Write-Status INFO "Model: $mod" return $man } #=================================================================== # CHOCOLATEY INSTALL + SOURCE CONFIGURATION #=================================================================== function Set-ChocolateySource { Write-SubBanner "Configuring Chocolatey Sources" $chocoExe = Get-ChocoExePath if (-not $chocoExe) { Write-Status ERROR "Chocolatey executable not found - cannot configure sources." return } # Check if source exists Write-Status INFO "Checking existing Chocolatey sources..." $listExit = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "source list" -Label "CHOCO" # Try adding the custom source Write-Status INFO "Adding custom source: $($Script:ChocoSourceName) -> $($Script:ChocoSourceUrl)" $addExit = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "source add -n `"$($Script:ChocoSourceName)`" -s `"$($Script:ChocoSourceUrl)`" --priority=1 --allow-self-service" -Label "CHOCO" if ($addExit -eq 0) { Write-Status OK "Custom source '$($Script:ChocoSourceName)' configured." } else { Write-Status WARN "Source add returned exit code $addExit (may already exist - OK)." } # Disable default community source Write-Status INFO "Disabling default Chocolatey community source..." $disableExit = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "source disable -n `"chocolatey`"" -Label "CHOCO" if ($disableExit -eq 0) { Write-Status OK "Default community source disabled." } else { Write-Status WARN "Could not disable default source (may not exist)." } # Verify final state Write-Status INFO "Final source configuration:" $null = Invoke-CommandVerbose -FilePath $chocoExe -Arguments "source list" -Label "CHOCO" } function Install-ChocolateyIfNeeded { Write-SubBanner "Ensuring Chocolatey" if (-not (Get-Command choco -ErrorAction SilentlyContinue)) { Write-Status INFO "Chocolatey not present - installing..." Write-Host "" Set-ExecutionPolicy Bypass -Scope Process -Force [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor 3072 Invoke-Expression (Invoke-RestMethod https://community.chocolatey.org/install.ps1) $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User') Write-Host "" Write-Status OK "Chocolatey installed." } else { Write-Status OK "Chocolatey already installed." } Set-ChocolateySource } function Install-ChocoPackage { <# .SYNOPSIS Installs a chocolatey package using Invoke-CommandVerbose so every line of choco output (download progress, install steps, etc.) is visible. #> param( [Parameter(Mandatory)][string]$PackageName, [string]$DisplayName ) if (-not $DisplayName) { $DisplayName = $PackageName } $chocoExe = Get-ChocoExePath if (-not $chocoExe) { Write-Status ERROR "Chocolatey executable not found." return $false } Write-SubBanner "Installing $DisplayName via Chocolatey" Write-Status INFO "Source: $($Script:ChocoSourceName) ($($Script:ChocoSourceUrl))" Write-Host "" # Try custom source first $chocoArgs = "install $PackageName -y --source=`"$($Script:ChocoSourceName)`"" $exitCode = Invoke-CommandVerbose -FilePath $chocoExe -Arguments $chocoArgs -Label "CHOCO" if ($exitCode -ne 0) { Write-Status WARN "Install from custom source returned $exitCode - trying default sources..." $chocoArgs = "install $PackageName -y" $exitCode = Invoke-CommandVerbose -FilePath $chocoExe -Arguments $chocoArgs -Label "CHOCO" } if ($exitCode -eq 0) { Write-Status OK "$DisplayName installed successfully." return $true } else { Write-Status ERROR "$DisplayName installation failed (exit $exitCode)." return $false } } #=================================================================== # DELL UPDATE LOGIC (fully verbose - no spinners) #=================================================================== function Invoke-DellUpdates { param([pscustomobject]$State) Write-Banner "DELL SYSTEM UPDATE" # Locate dcu-cli.exe $cli = @( 'C:\Program Files\Dell\CommandUpdate\dcu-cli.exe', 'C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe' ) | Where-Object { Test-Path $_ } | Select-Object -First 1 if (-not $cli) { $installed = Install-ChocoPackage -PackageName "dellcommandupdate" -DisplayName "Dell Command Update" if ($installed) { Start-Sleep -Seconds 5 } $cli = @( 'C:\Program Files\Dell\CommandUpdate\dcu-cli.exe', 'C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe' ) | Where-Object { Test-Path $_ } | Select-Object -First 1 } if (-not $cli) { Write-Status ERROR "Dell Command Update CLI still missing - aborting Dell section." return $false } Write-Status OK "Dell CLI located: $cli" Write-Host "" switch ($State.ManufacturerPhase) { 0 { # --- Phase-0: Advanced Driver Restore --- if ($State.DellADRFailed) { Write-Status WARN "ADR previously failed - skipping to phase-2." $State.ManufacturerPhase = 2 Set-ScriptState -State $State return (Invoke-DellUpdates -State $State) } Write-SubBanner "Dell Phase-0: Configuring ADR" Write-Status INFO "Enabling Advanced Driver Restore..." $configExit = Invoke-CommandVerbose -FilePath $cli -Arguments "/configure -advancedDriverRestore=enable" -Label "DCU-CFG" Write-Host "" Write-SubBanner "Dell Phase-0: ADR Driver Install" Write-Status STEP "This will download and install drivers matching your Dell system." Write-Status STEP "All progress and driver names will be displayed below." Write-Host "" $dellExit = Invoke-CommandVerbose -FilePath $cli -Arguments "/driverinstall" -Label "DCU" switch ($dellExit) { 0 { Write-Status OK "ADR driver install succeeded." $State.ManufacturerPhase = 2 Set-ScriptState -State $State return (Invoke-DellUpdates -State $State) } 1 { Write-Status WARN "Drivers installed - reboot required." $State.ManufacturerPhase = 1 Schedule-RebootAndContinue -State $State -Reason "Dell ADR driver install (exit 1) requires reboot" } 5 { Write-Status WARN "Reboot required to complete operation." $State.ManufacturerPhase = 1 Schedule-RebootAndContinue -State $State -Reason "Dell ADR driver install (exit 5) requires reboot" } 2 { Write-Status ERROR "ADR crashed - marking as failed, skipping to updates." $State.DellADRFailed = $true $State.ManufacturerPhase = 2 Set-ScriptState -State $State return (Invoke-DellUpdates -State $State) } 3 { Write-Status OK "All drivers already up to date." $State.ManufacturerPhase = 2 Set-ScriptState -State $State return (Invoke-DellUpdates -State $State) } default { Write-Status ERROR "ADR returned $dellExit - skipping to phase-2." $State.DellADRFailed = $true $State.ManufacturerPhase = 2 Set-ScriptState -State $State return (Invoke-DellUpdates -State $State) } } } 1 { # --- Phase-1: Post-reboot scan --- Write-SubBanner "Dell Phase-1: Post-Reboot Scan" Write-Status INFO "Scanning for remaining Dell drivers after reboot..." Write-Host "" $null = Invoke-CommandVerbose -FilePath $cli -Arguments "/scan" -Label "DCU" $State.ManufacturerPhase = 2 Set-ScriptState -State $State return (Invoke-DellUpdates -State $State) } 2 { # --- Phase-2: Apply all updates --- Write-SubBanner "Dell Phase-2: Apply BIOS / Firmware / Driver Updates" Write-Status STEP "Downloading and installing all available Dell updates." Write-Status STEP "Each update name, download progress, and install status shown below." Write-Host "" $dellExit = Invoke-CommandVerbose -FilePath $cli -Arguments "/applyupdates -forceUpdate=enable -autoSuspendBitLocker=enable" -Label "DCU" switch ($dellExit) { 0 { Write-Status OK "All Dell updates applied successfully." $State.ManufacturerPhase = 3 Set-ScriptState -State $State return $true } 1 { Write-Status WARN "Updates applied - reboot required." $State.ManufacturerPhase = 3 Schedule-RebootAndContinue -State $State -Reason "Dell updates (exit 1) require reboot" } 5 { Write-Status WARN "Reboot required to finish updates." $State.ManufacturerPhase = 3 Schedule-RebootAndContinue -State $State -Reason "Dell updates (exit 5) require reboot" } 3 { Write-Status OK "System already up to date." $State.ManufacturerPhase = 3 Set-ScriptState -State $State return $true } default { Write-Status WARN "Apply-updates returned $dellExit - continuing." $State.ManufacturerPhase = 3 Set-ScriptState -State $State return $true } } } default { Write-Status OK "Dell updates already completed." return $true } } return $true } #=================================================================== # HP UPDATE LOGIC (fully verbose - no spinners) #=================================================================== function Invoke-HPUpdates { param([pscustomobject]$State) Write-Banner "HP SYSTEM UPDATE" $hpiaPaths = @( 'C:\HP\HPIA\HPImageAssistant.exe', 'C:\Program Files\HP\HPIA\HPImageAssistant.exe', 'C:\Program Files (x86)\HP\HPIA\HPImageAssistant.exe' ) $hpia = $hpiaPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 if (-not $hpia) { $installed = Install-ChocoPackage -PackageName "hpimageassistant" -DisplayName "HP Image Assistant" if ($installed) { Start-Sleep -Seconds 5 } $hpia = $hpiaPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 } if (-not $hpia) { Write-Status ERROR "HP Image Assistant still missing - aborting HP section." return $false } Write-Status OK "HP Image Assistant located: $hpia" Write-Host "" Write-SubBanner "HP Image Assistant: Analyze & Install All Updates" Write-Status STEP "Scanning for BIOS, firmware, driver, and software updates." Write-Status STEP "Each update name and install progress will be displayed below." Write-Host "" $hpiaArgs = "/Operation:Analyze /Action:Install /Selection:All /Silent /Noninteractive /ReportFolder:C:\Temp\HPIA" $hpExit = Invoke-CommandVerbose -FilePath $hpia -Arguments $hpiaArgs -Label "HPIA" switch ($hpExit) { 0 { Write-Status OK "HP updates completed successfully."; return $true } 256 { Write-Status WARN "HP updates require reboot (exit $hpExit)."; return $true } 3010 { Write-Status WARN "HP updates require reboot (exit $hpExit)."; return $true } default { Write-Status WARN "HP updates returned $hpExit - continuing." return $true } } } #=================================================================== # MAIN EXECUTION #=================================================================== try { if (-not (Initialize-PersistentScript)) { throw "Failed to initialise persistent script." } Start-Transcript -Path $Script:LogFile -Append -Force Write-Host "" Write-Host "======================================================================" -ForegroundColor Cyan Write-Host " DRIVER UPDATE SCRIPT - PowerShell $($PSVersionTable.PSVersion)" -ForegroundColor Cyan Write-Host "======================================================================" -ForegroundColor Cyan Write-Host " User: $env:USERNAME" -ForegroundColor Cyan Write-Host " Computer: $env:COMPUTERNAME" -ForegroundColor Cyan Write-Host " PID: $PID" -ForegroundColor Cyan Write-Host " Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan Write-Host " Script: $Script:PersistentScript" -ForegroundColor Cyan Write-Host " Timeout: $Script:DriverTimeout seconds per driver" -ForegroundColor Cyan Write-Host " ChocoSrc: $Script:ChocoSourceName -> $Script:ChocoSourceUrl" -ForegroundColor Cyan Write-Host " Mode: TARGETED (device-matched) + VERBOSE output" -ForegroundColor Cyan Write-Host "======================================================================" -ForegroundColor Cyan Write-Host "" Write-Status INFO "Checking for orphan driver processes..." $orphanKilled = Stop-DriverInstallProcesses -Silent if ($orphanKilled -gt 0) { Write-Status WARN "Terminated $orphanKilled leftover processes." Start-Sleep -Seconds 2 } $state = Get-ScriptState Write-Host "----------------------------------------------------------------------" -ForegroundColor DarkGray Write-Host " CURRENT STATE" -ForegroundColor DarkGray Write-Host "----------------------------------------------------------------------" -ForegroundColor DarkGray foreach ($prop in $state.PSObject.Properties) { Write-Host (" {0,-25}: {1}" -f $prop.Name, $prop.Value) -ForegroundColor DarkGray } Write-Host "----------------------------------------------------------------------" -ForegroundColor DarkGray Write-Host "" # PHASE-0: Ventoy offline drivers if ($state.Phase -eq 0) { if (-not $state.VentoyCompleted) { $ventoyResult = Install-VentoyDrivers -State $state $state.TotalDriversAdded = $ventoyResult.TotalAdded $state.TotalDriversInstalled = $ventoyResult.TotalInstalled $state.TotalDriversSkipped = $ventoyResult.TotalSkippedNoMatch $state.TotalDriversTimedOut = $ventoyResult.TotalTimedOut $state.TotalProcessesKilled = $ventoyResult.TotalKilled $state.TotalTimeSpent = $ventoyResult.TotalTime if ($ventoyResult.NotFound) { Write-Status INFO "No Ventoy drivers - moving to online phase." $state.VentoyCompleted = $true; $state.Phase = 1 Set-ScriptState -State $state } elseif ($ventoyResult.NeedsReboot) { Write-Status STEP "Ventoy driver install requires reboot." $state.VentoyCompleted = $true; $state.VentoyRebootDone = $false Set-ScriptState -State $state Schedule-RebootAndContinue -State $state -Reason "Ventoy driver install - reboot required" } else { Write-Status OK "Ventoy drivers installed - no reboot needed." $state.VentoyCompleted = $true; $state.Phase = 1 Set-ScriptState -State $state } } else { Write-Status OK "Post-reboot: Ventoy driver installation finalized." $state.VentoyRebootDone = $true; $state.Phase = 1 Set-ScriptState -State $state } } # PHASE-1: Online updates if ($state.Phase -ge 1) { Write-Banner "PHASE 1: ONLINE UPDATES" if (-not (Test-InternetConnectivity -MaxRetries 15 -RetryDelay 10)) { Write-Status ERROR "No internet - cannot continue." Write-Status INFO "Connect to a network and re-run the script." try { Stop-Transcript } catch { } exit 1 } Install-ChocolateyIfNeeded $manufacturer = Get-SystemManufacturer if ($manufacturer -like '*Dell*') { Invoke-DellUpdates -State $state | Out-Null } elseif ($manufacturer -match 'HP|Hewlett[-\s]?Packard') { Invoke-HPUpdates -State $state | Out-Null } else { Write-Status WARN "Unsupported manufacturer: $manufacturer - only Dell and HP are handled." } } # FINAL CLEAN-UP Write-Status INFO "Final clean-up - terminating any remaining driver processes." Stop-DriverInstallProcesses -Silent | Out-Null Write-Host "" Write-Host "======================================================================" -ForegroundColor Green Write-Host " DRIVER UPDATE SCRIPT COMPLETED SUCCESSFULLY!" -ForegroundColor Green Write-Host "======================================================================" -ForegroundColor Green Write-Host "" Write-Host " Total reboots: $($state.RebootCount)" -ForegroundColor White Write-Host " Drivers added: $($state.TotalDriversAdded)" -ForegroundColor White Write-Host " Drivers installed: $($state.TotalDriversInstalled)" -ForegroundColor White Write-Host " Drivers skipped: $($state.TotalDriversSkipped)" -ForegroundColor White Write-Host " Drivers failed: $($state.TotalDriversFailed)" -ForegroundColor White Write-Host " Drivers timed out: $($state.TotalDriversTimedOut)" -ForegroundColor White Write-Host " Processes killed: $($state.TotalProcessesKilled)" -ForegroundColor White Write-Host " Total time spent: $(Format-Duration $state.TotalTimeSpent)" -ForegroundColor White Write-Host " Dell ADR failed: $($state.DellADRFailed)" -ForegroundColor White Write-Host " Choco source: $($Script:ChocoSourceName)" -ForegroundColor White Write-Host "" Write-Host "======================================================================" -ForegroundColor Green Write-Host "" Remove-ScriptState } catch { Write-Host "" Write-Host "======================================================================" -ForegroundColor Red Write-Host " SCRIPT ERROR" -ForegroundColor Red Write-Host "======================================================================" -ForegroundColor Red Write-Status ERROR "Message: $($_.Exception.Message)" Write-Status ERROR "Line: $($_.InvocationInfo.ScriptLineNumber)" Write-Host $_.ScriptStackTrace -ForegroundColor Red Write-Host "" Write-Status INFO "Attempting emergency clean-up..." try { $saveEA = $ErrorActionPreference $ErrorActionPreference = 'SilentlyContinue' Stop-DriverInstallProcesses -Silent | Out-Null $ErrorActionPreference = $saveEA } catch { try { $ErrorActionPreference = $saveEA } catch { } } Write-Status INFO "State file kept - re-run script to continue." } finally { try { Stop-Transcript } catch { } } -
Windows 11 25H2 File Explorer Rename Bug
Space key opens the file instead of inserting a space
Environment
- OS: Windows 11 25H2
- Build: 26200.7462
-
Component: File Explorer (
explorer.exe)
🐞 Problem description -
When renaming a file or folder in Windows 11 File Explorer, pressing the Space key may unexpectedly open the file instead of inserting a space in the filename.
This issue typically occurs when:
- Renaming via mouse click (not
F2) - Typing immediately after clicking the filename
- Explorer loses keyboard focus during rename
The behavior is inconsistent and highly disruptive for users who rename files frequently.
🔍 Root cause analysis
This is not a keyboard or permission issue.
It is caused by File Explorer UI focus instability, introduced or exacerbated in recent Windows 11 builds (including 25H2).Two internal Explorer behaviors contribute to the bug:
1️⃣ Modern context menu / shell UI interaction
The Windows 11 modern shell UI aggressively intercepts input events.
If Explorer momentarily loses rename focus, the Space key is interpreted as a command, not text input — resulting in Open file.2️⃣ Folder type auto-discovery (content sniffing)
Explorer continuously analyzes folder contents to decide whether it’s:
- Documents
- Pictures
- Music
- Videos
This background reclassification can trigger UI refreshes, which:
- Cancel rename edit mode
- Move keyboard focus back to the file item
- Cause the next key press (e.g. Space) to act on the file instead of the text box
✅ Solution: Registry-based workaround
While Microsoft has not yet released an official fix, disabling the problematic behaviors stabilizes File Explorer and prevents focus loss during rename.
What the fix does
- Restores the classic context menu behavior
- Disables folder type auto-discovery
- Reduces Explorer UI refresh events
- Keeps rename text box focused correctly
🛠️ Registry fix (.reg file)
Save the following as
Explorer_Rename_Stability_Win11_25H2.reg
and import it (double-click → Yes).Windows Registry Editor Version 5.00 ; ========================================== ; Windows 11 Explorer Rename Stability Tweaks ; Target: Win11 25H2 (26200.x) ; ========================================== ; --- Tweak 1: Restore classic context menu ; Reduces Explorer UI focus glitches [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell\Update\Packages] "UndockingDisabled"=dword:00000001 ; --- Tweak 2: Disable folder type auto-discovery ; Prevents Explorer rescans that break rename focus [HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags\AllFolders\Shell] "FolderType"="NotSpecified"
🔄 Apply the changes
After importing the registry file:
- Restart Windows Explorer, or
- Reboot the system (recommended)
🧪 Result
After applying this fix:
- Pressing Space correctly inserts a space while renaming
- Files no longer open unexpectedly
- Rename behavior is stable and predictable
This workaround has been confirmed effective on:
- Windows 11 25H2 – build 26200.7462
⚠️ Notes
- This is a workaround, not an official Microsoft fix
- The underlying issue is a File Explorer UI bug
- Using
F2to rename remains the most reliable native method - The registry change is safe and reversible
🧹 Reverting the change
To undo the fix, remove the added registry values or use a revert
.regfile.Save this as
Explorer_Rename_Stability_Revert.regWindows Registry Editor Version 5.00 ; --- Revert classic context menu tweak [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell\Update\Packages] "UndockingDisabled"=- ; --- Revert folder type tweak [HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\Bags\AllFolders\Shell] "FolderType"=-
📌 Conclusion
The “Space opens file while renaming” issue in Windows 11 25H2 is caused by Explorer UI focus loss, not user error.
By simplifying Explorer’s UI behavior through targeted registry tweaks, the issue can be effectively eliminated.
Until Microsoft addresses this at the Explorer level, this solution provides a stable and practical fix.
-
Free file sync compare a synology folder to teams folder with exclusion filters
COMPARE-ONLY, safe for Synology ↔ Teams.
✅ Compare-Only
.ffs_gui(Synology ↔ Teams)👉 Replace your file entirely with the content below.
Save as:
Synology_vs_Teams_COMPARE_ONLY.ffs_gui
📄 FreeFileSync config (NO warnings)
<?xml version="1.0" encoding="utf-8"?> <FreeFileSync XmlType="GUI" XmlFormat="18"> <Notes>Compare-only configuration for Synology NAS ↔ Microsoft Teams (OneDrive local sync)</Notes> <Compare> <Variant>TimeAndSize</Variant> <Symlinks>Exclude</Symlinks> <IgnoreTimeShift/> </Compare> <!-- Synchronization deliberately disabled --> <Synchronize> <Variant>Custom</Variant> <CustomDirections> <LeftOnly>None</LeftOnly> <RightOnly>None</RightOnly> <LeftNewer>None</LeftNewer> <RightNewer>None</RightNewer> </CustomDirections> <DetectMovedFiles>false</DetectMovedFiles> <DeletionPolicy>Permanent</DeletionPolicy> <VersioningFolder Style="None"/> </Synchronize> <Filter> <Include> <Item>*</Item> </Include> <Exclude> <!-- Office / Teams temporary files --> <Item>~$*</Item> <Item>*.tmp</Item> <Item>*.temp</Item> <Item>*.bak</Item> <Item>*.wbk</Item> <Item>*.swp</Item> <Item>*.lock</Item> <Item>*~</Item> <Item>~*.tmp</Item> <!-- Synology / NAS metadata --> <Item>@eaDir/*</Item> <Item>#recycle/*</Item> <Item>.recycle/*</Item> <Item>.nfs*</Item> <Item>.fuse_hidden*</Item> <!-- Windows / OneDrive artifacts --> <Item>Thumbs.db</Item> <Item>desktop.ini</Item> <Item>*.lnk</Item> <Item>$RECYCLE.BIN/*</Item> <Item>System Volume Information/*</Item> <!-- macOS clients --> <Item>.DS_Store</Item> <!-- Cache --> <Item>.cache/*</Item> </Exclude> <SizeMin>0</SizeMin> <SizeMax>0</SizeMax> <TimeSpan/> </Filter> <FolderPairs> <Pair> <!-- Synology NAS (SMB/CIFS) --> <Left>\\NAS\SHARE\FOLDER</Left> <!-- Microsoft Teams (OneDrive local folder) --> <Right>C:\Users\USERNAME\OneDrive - ORGANIZATION\Teams\TEAM_NAME</Right> </Pair> </FolderPairs> <Errors IgnoreErrors="false"/> <PostSyncCommand Condition="None"/> <MainConfig> <FailSafe>true</FailSafe> <AutoRetry count="2" delay="5"/> <ProgressDialog>true</ProgressDialog> </MainConfig> <LogFolder/> <EmailNotification/> <Gui> <GridViewType>Difference</GridViewType> </Gui> </FreeFileSync>
🔧 What you MUST edit
Only change these paths:
Synology
\\NAS\SHARE\FOLDERExample:
\\192.168.1.20\ProjectsTeams (local OneDrive path)
C:\Users\USERNAME\OneDrive - ORGANIZATION\Teams\TEAM_NAME
🧪 Expected behavior now
✔ No XML warnings ✔ Compare button works immediately ✔ Synchronize does nothing (all directions =
None) ✔ Safe for audits / verification ✔ Stable with CIFS + OneDrive
-
Provisioning New HP Laptop after clonage
Provisioning New HP Laptop after clonage
- This Script renames the Pc Name
- Feeds the Asset Tag to Bios
- Updates the BIOS
#Requires -RunAsAdministrator <# .SYNOPSIS HP BIOS Update, PC Rename, and Asset Tag Configuration Script .DESCRIPTION Prompts for PC name and Asset Tag, configures settings, and launches BIOS update silently .NOTES Requires HP Client Management Script Library (HPCMSL) Run as Administrator Place the 'bin' folder from extracted Softpaq in the same location as this script #> # ========================== CONFIGURATION ========================== $ScriptDirectory = $PSScriptRoot $BiosExe = Join-Path -Path $ScriptDirectory -ChildPath "bin\HpFirmwareUpdRec.exe" # ========================== MAIN SCRIPT ========================== Clear-Host Write-Host "==========================================" -ForegroundColor Cyan Write-Host " HP BIOS & System Configuration Tool " -ForegroundColor Cyan Write-Host "==========================================" -ForegroundColor Cyan Write-Host "" # Check if BIOS file exists if (-not (Test-Path -Path $BiosExe)) { Write-Host "ERROR: HpFirmwareUpdRec.exe not found!" -ForegroundColor Red Write-Host "Expected path: $BiosExe" -ForegroundColor Yellow Write-Host "" Write-Host "Make sure the 'bin' folder is in: $ScriptDirectory" -ForegroundColor Yellow Read-Host "Press Enter to exit" exit 1 } Write-Host "BIOS Update file found: $BiosExe" -ForegroundColor Green Write-Host "" # Check if HP CMSL module is available if (-not (Get-Module -ListAvailable -Name "HPCMSL")) { Write-Host "WARNING: HP CMSL module not found." -ForegroundColor Yellow $InstallModule = Read-Host "Do you want to install it now? (Y/N)" if ($InstallModule -match '^[Yy]$') { Write-Host "Installing HP CMSL module..." -ForegroundColor Yellow Install-Module -Name HPCMSL -Force -AcceptLicense Import-Module HPCMSL Write-Host "HP CMSL module installed successfully." -ForegroundColor Green } else { Write-Host "Cannot proceed without HP CMSL module." -ForegroundColor Red Read-Host "Press Enter to exit" exit 1 } } # Display current info Write-Host "Current PC Name: $env:COMPUTERNAME" -ForegroundColor Gray Write-Host "" # ========================== USER INPUT ========================== # Get new PC Name do { $NewPCName = Read-Host "Enter the NEW PC Name" if ([string]::IsNullOrWhiteSpace($NewPCName)) { Write-Host "PC Name cannot be empty!" -ForegroundColor Red } elseif ($NewPCName.Length -gt 15) { Write-Host "PC Name cannot exceed 15 characters!" -ForegroundColor Red $NewPCName = $null } } while ([string]::IsNullOrWhiteSpace($NewPCName)) # Get Asset Tracking Number do { $AssetTag = Read-Host "Enter the Asset Tracking Number" if ([string]::IsNullOrWhiteSpace($AssetTag)) { Write-Host "Asset Tracking Number cannot be empty!" -ForegroundColor Red } } while ([string]::IsNullOrWhiteSpace($AssetTag)) # ========================== CONFIRMATION ========================== Write-Host "" Write-Host "==========================================" -ForegroundColor Yellow Write-Host " New PC Name : $NewPCName" -ForegroundColor White Write-Host " Asset Tracking No. : $AssetTag" -ForegroundColor White Write-Host "==========================================" -ForegroundColor Yellow Write-Host "" $Confirm = Read-Host "Proceed with these settings? (Y/N)" if ($Confirm -notmatch '^[Yy]$') { Write-Host "Operation cancelled." -ForegroundColor Yellow exit 0 } Write-Host "" # ========================== SET ASSET TRACKING NUMBER ========================== Write-Host "[1/3] Setting Asset Tracking Number..." -ForegroundColor Yellow try { Set-HPBIOSSettingValue -Name "Asset Tracking Number" -Value $AssetTag Write-Host " SUCCESS: Asset Tracking Number set to '$AssetTag'" -ForegroundColor Green } catch { Write-Host " ERROR: Failed to set Asset Tracking Number" -ForegroundColor Red Write-Host " $($_.Exception.Message)" -ForegroundColor Red } # ========================== RENAME COMPUTER ========================== Write-Host "[2/3] Renaming computer to '$NewPCName'..." -ForegroundColor Yellow try { Rename-Computer -NewName $NewPCName -Force -ErrorAction Stop Write-Host " SUCCESS: Computer will be renamed after restart" -ForegroundColor Green } catch { Write-Host " ERROR: Failed to rename computer" -ForegroundColor Red Write-Host " $($_.Exception.Message)" -ForegroundColor Red } # ========================== LAUNCH BIOS UPDATE (SILENT) ========================== Write-Host "[3/3] Running BIOS Update (Silent Mode)..." -ForegroundColor Yellow Write-Host " Please wait, this may take a few minutes..." -ForegroundColor Gray Write-Host " DO NOT turn off the computer!" -ForegroundColor Red try { # Silent install: -r (reboot) -b (bitlocker suspend) -s (silent) $Process = Start-Process -FilePath $BiosExe -ArgumentList "-r -b -s" -Wait -PassThru switch ($Process.ExitCode) { 0 { Write-Host " SUCCESS: BIOS Update completed" -ForegroundColor Green } 256 { Write-Host " INFO: BIOS is already up to date" -ForegroundColor Cyan } 273 { Write-Host " SUCCESS: BIOS Update staged for reboot" -ForegroundColor Green } 282 { Write-Host " INFO: BIOS downgrade blocked by policy" -ForegroundColor Yellow } 3010 { Write-Host " SUCCESS: BIOS Update completed (restart required)" -ForegroundColor Green } default { Write-Host " Exit code: $($Process.ExitCode)" -ForegroundColor Yellow } } } catch { Write-Host " ERROR: Failed to run BIOS update" -ForegroundColor Red Write-Host " $($_.Exception.Message)" -ForegroundColor Red } # ========================== COMPLETION ========================== Write-Host "" Write-Host "==========================================" -ForegroundColor Cyan Write-Host " SCRIPT COMPLETED " -ForegroundColor Cyan Write-Host "==========================================" -ForegroundColor Cyan Write-Host "" Write-Host "RESTART REQUIRED to apply all changes!" -ForegroundColor Yellow Write-Host "" $Restart = Read-Host "Restart now? (Y/N)" if ($Restart -match '^[Yy]$') { Restart-Computer -Force }Folder Structure
Your folder should look like this: Extract the contents bios.exe (in our case ex: SP169859.exe) and rename it as bin and place it by the side of the powershell script.
📁 YourFolder ├── 📄 Setup.ps1 (this script) └── 📁 bin ├── 📄 HpFirmwareUpdRec.exe └── 📄 (other files from Softpaq)Silent Install Switches Explained
Switch Description -sSilent mode (no user prompts) -bSuspend BitLocker automatically -rReboot automatically after update Other Useful Switches
Switch Description -fForce update even if same version -pPassword (if BIOS is password protected) If BIOS Has Password
$Process = Start-Process -FilePath $BiosExe -ArgumentList "-r -b -s -p YourPassword" -Wait -PassThru -
Add GSD files to Tia-Portal
Copying the GSD files to the corresponding repository will not work :
This is key: TIA Portal V19 does not automatically scan every folder you drop XMLs into, and simply copying files into
xdd\gsddoesn’t make them appear in the Hardware Catalog. There’s an internal registration/indexing step that the GUI import normally does.So, to make the Dataclogic M120 show up without using the GUI, we need to emulate what the GUI does behind the scenes. Here’s the detailed breakdown.
1. How TIA Portal actually “registers” a GSD
When you import via:
Options → Manage GSD files → InstallTIA does three things:
-
Copies files to the system GSD folder Usually:
C:\ProgramData\Siemens\Automation\Portal V19\data\xdd\gsd -
Creates/updates a device registry cache (binary index) under:
C:\Users\<User>\AppData\Roaming\Siemens\Automation\Portal V19\Data\xddor sometimes:
C:\ProgramData\Siemens\Automation\Portal V19\Data\xdd\cache -
Validates the GSD XML against the supported version schema (V2.3/V2.4)
✅ The crucial part: without updating the cache, TIA Portal won’t display the device, even if the XML is physically in the folder.
2. Why your manual copy isn’t working
- You copied the
.xmlfiles intoxdd\gsdmanually. - TIA did not rebuild the cache/index, so it doesn’t “see” the new device.
- TIA Portal only reads new GSDs during GUI import or after the internal indexing process.
3. How to make it work without GUI
There are two practical approaches:
Option A: Force a GUI-less import using TIA scripts (recommended for labs)
TIA Portal supports a command-line import of GSDs via
PortalCmd.exe(hidden Siemens tool):"C:\Program Files\Siemens\Automation\Portal V19\PortalCmd.exe" /GSDInstall:"C:\Path\To\GSDML"- This will copy the GSD and update the internal index.
- Fully silent, scriptable, ideal for multiple lab PCs.
⚠️ Note:
PortalCmd.exeexists in V17+; V19 retains it. It’s undocumented but works reliably.
Option B: Pre-build the
xddfolder and cacheIf you don’t want to rely on PortalCmd:
-
Do a manual import once on a master PC
- Dataclogic M120 appears in Hardware Catalog.
-
Copy All folders from C:\ProgramData\Siemens\Automation\Portal V19\data it includes folder like hwcn,xdd,xddint to all lab PCs:
C:\ProgramData\Siemens\Automation\Portal V19\data\xdd\gsd C:\Users\<User>\AppData\Roaming\Siemens\Automation\Portal V19\Data\xdd- This ensures the XML and the user/device index cache are present.
- When students open TIA, the device appears instantly.
Essentially, you are cloning the “registered” GSD environment.
✅ Recommended Approach for Labs
- Install M120 GSD on a single PC via GUI.
- Locate these folders:
C:\ProgramData\Siemens\Automation\Portal V19\data\* <-- XML + icons # The below path is not required as i hev tested it works well with the folder programdata C:\Users\<AdminUser>\AppData\Roaming\Siemens\Automation\Portal V19\Data\* <-- cache/index- Copy hwcn,xdd,xddint to all lab PCs (maintain structure).
- Make sure TIA is closed during copy.
- Launch TIA — device appears without GUI import.
This is the most reliable method for lab-wide automation.
-
-
ChatGPT Enhanced Markdown Copy with Code Blocks ,Tables and Formula
ChatGPT Enhanced Markdown Copy with Code Blocks ,Tables and Formula: Download link
ChatGPT/Claude/Grok/Arena | Conversation/Chat Markdown Export/Download : Download link
Tampermonkey Script Security Audit Report
Executive Summary
This script is relatively safe to use for its intended purpose. However, there are several medium-risk issues and best practices violations that warrant attention. The script does not attempt to exfiltrate data or access sensitive information, but it has code quality and security concerns that should be addressed.
Risk Level: MEDIUM ⚠️
Detailed Security Analysis
1. External Dependency Risk (MEDIUM RISK)
Issue: The script loads Turndown.js from a CDN:
// @require https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.2/turndown.min.jsVulnerability Details:
-
The script relies on
cdnjs.cloudflare.comto provide the Turndown library -
If this CDN is compromised, malicious code could be injected
-
No integrity checking (Subresource Integrity / SRI hash) is used
-
The minified nature of the library means you cannot easily audit it
Risk Assessment:
-
Cloudflare’s CDN is generally trustworthy, but this is still a theoretical attack vector
-
The library is widely used and monitored by security researchers
-
Likelihood of compromise: Low, but possible
Recommendation:
Add a Subresource Integrity (SRI) hash to verify the library hasn’t been tampered with:// @require https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.2/turndown.min.js#sha384-[hash-here]Alternatively, embed the Turndown library directly in the script (it’s reasonably compact in minified form) to eliminate the CDN dependency.
2. No Permissions Declaration (LOW RISK)
Issue: The script declares
@grant none, which is correct:// @grant noneWhy This Matters:
-
✅ Positive finding: The script doesn’t request clipboard manipulation, storage access, or network capabilities
-
The script properly uses standard browser APIs (
navigator.clipboard) instead -
This significantly limits what the script can do maliciously
Assessment: This is actually a security best practice being followed correctly.
3. XSS Vulnerability in Table Processing (HIGH RISK)
Critical Issue Found:
tempDiv.innerHTML = cell.innerHTML; // ... then processed with turndownService.turndown()Vulnerability Details:
-
When processing table cells, the script uses
innerHTMLdirectly -
If ChatGPT’s HTML response contains malicious content, it could potentially be executed
-
However, the Turndown library’s
turndown()method converts HTML to Markdown text, which mitigates this significantly -
Actual risk is mitigated because the output is text-based, not re-injected into the DOM
Severity: Medium (mitigated by Turndown conversion)
Recommendation:
UsetextContentfor extracting cell data when safety is the priority:const tempDiv = document.createElement('div'); tempDiv.textContent = cell.textContent;However, this might lose some formatting. A safer middle ground:
const tempDiv = document.createElement('div'); // Clone and sanitize const clone = cell.cloneNode(true); tempDiv.appendChild(clone);
4. DOM Cloning Security (LOW RISK)
Code:
const range = selection.getRangeAt(0); tempDiv.appendChild(range.cloneContents());Assessment:
-
✅ The script clones selected content from the user’s selection, not arbitrary DOM
-
Only processes what the user explicitly selects
-
This is safe because users are choosing what to copy
-
No risk of unauthorized content access
5. Clipboard Access (MEDIUM RISK)
Code:
navigator.clipboard.writeText(markdownText) .then(() => { ... }) .catch((err) => { ... });Security Assessment:
-
✅ Uses modern
navigator.clipboardAPI (not legacydocument.execCommand()) -
✅ Only writes to clipboard when user explicitly triggers the shortcut
-
✅ Does not read from clipboard (safer than two-way access)
-
The script correctly asks for no clipboard permissions since it uses standard API
-
No exfiltration risk - clipboard is a user-controlled resource
Assessment: Safe implementation.
6. Scope and Match Directives (LOW RISK)
Code:
// @match *://chatgpt.com/c // @match *://chatgpt.com/c/*Assessment:
-
✅ Excellent scope limitation: Script only runs on ChatGPT conversation pages
-
✅ Prevents the script from running on unintended websites
-
✅ Properly uses wildcard protocol matching (
*://) -
No risk of unintended site access
Assessment: Security best practice followed.
7. Code Quality Issues (LOW RISK)
Issue 1: Inconsistent Error Handling
console.warn("No content selected for copying."); // No graceful fallback, just returns silentlyIssue 2: Regex Pattern
.replace(/\n{2,}/g, '\n\n')-
Safe regex, no ReDoS (Regular Expression Denial of Service) vulnerability
-
However, this could collapse more than 2 newlines, which might be intentional but isn’t documented
Issue 3: Commented-out Code
//alert("Enhanced Markdown copied!v5");-
Should be removed in production
-
Leaving it doesn’t create a security risk, but reduces code cleanliness
Assessment: Code quality issues, but not security vulnerabilities.
8. No Data Exfiltration (✅ SAFE)
Thorough Check:
-
❌ No
fetch()calls to external servers -
❌ No
XMLHttpRequestto unknown domains -
❌ No
GM_xmlhttpRequest()(Tampermonkey API) -
❌ No
WebSocketconnections -
❌ No image beacons or tracking pixels
-
❌ No localStorage or sessionStorage access
-
❌ No
GM_setValue()or persistent storage
Assessment: No data exfiltration mechanisms present. The script only writes to clipboard locally.
9. Input Validation (MEDIUM RISK)
Weak Point:
The script doesn’t validate that the selected content is actually from ChatGPT:const selection = window.getSelection(); if (!selection || selection.isCollapsed) { console.warn("No content selected for copying."); return; } // No origin verification - could be any selected HTMLRisk: Low in practice because:
-
Users are deliberately selecting content
-
The conversion is transparent (generates readable Markdown)
-
Output is saved locally to clipboard
Recommendation: Add a check that selection comes from ChatGPT’s container:
const chatContainer = document.querySelector('[data-testid="conversation"]'); if (!chatContainer || !chatContainer.contains(selection.anchorNode)) { console.warn("Selection must be from ChatGPT conversation"); return; }
Security Findings Summary
Category Finding Severity Status External Dependencies CDN-loaded library without SRI hash Medium ⚠️ Should be fixed Data Exfiltration No unauthorized data transmission None ✅ Safe XSS Vectors HTML processing via innerHTML Medium ✅ Mitigated by Turndown Clipboard Abuse Proper clipboard access only None ✅ Safe Scope Control Limited to ChatGPT domains None ✅ Safe Code Injection No eval() or Function() constructors None ✅ Safe Permissions Minimal, correct declarations None ✅ Safe
Recommendations for Improved Security
Priority 1 (Should Do)
-
Add SRI hash to Turndown library or embed it directly
-
Remove commented-out alert line (line 89)
-
Add input validation to ensure selection is from ChatGPT content area
Priority 2 (Nice to Have)
-
Use
textContentinstead ofinnerHTMLin table cell processing -
Add error notifications to inform user if copy fails (currently silent)
-
Add version fingerprinting or integrity checks to the metadata
Priority 3 (Code Quality)
-
Add JSDoc comments for the
enhancedCopy()function -
Handle edge cases where no content is available more gracefully
-
Test with different table formats from ChatGPT to ensure compatibility
Final Safety Verdict
Is this script safe to use? YES, with caveats.
Risk Assessment:
-
Personal data safety: ✅ No risk (no exfiltration, no credential theft)
-
System compromise: ✅ No risk (no execution exploits, no malware payloads)
-
Account security: ✅ No risk (doesn’t access ChatGPT tokens or session data)
-
Privacy: ✅ Safe (only processes clipboard, which you control)
When to Use This Script:
-
✅ Safe to use for converting ChatGPT responses to Markdown
-
✅ Safe to use on your personal account (not sensitive environments)
-
✅ Safe to use for general productivity
When to Avoid:
-
❌ Don’t use if you handle sensitive information (though the script doesn’t steal it, principle of least risk)
-
❌ Don’t use without verifying the CDN dependency occasionally
-
❌ Don’t use if you’re uncomfortable with Cloudflare serving code to your browser
Action Items:
-
Install and test on a low-risk conversation first
-
Monitor the Tampermonkey dashboard to ensure only expected network calls occur
-
Watch for future updates from the author (check GreasyFork periodically)
-
Consider requesting the author add SRI hash verification
How to Monitor After Installation
After installing, verify the script is behaving as expected:
-
Open Browser DevTools (F12 → Network tab)
-
Select some ChatGPT text and press Alt + Shift + C
-
Check Network tab — you should only see:
-
Initial load of
turndown.min.jsfrom cdnjs.cloudflare.com -
No other external requests
-
-
Check Console — should see:
"Shortcut Alt + Shift + C is ready for enhanced copying." -
Paste the clipboard content somewhere to verify it’s valid Markdown
If you see any unexpected network requests or console errors, disable the script immediately.
-
-
Enable DHCP on S7-1500
To set a Siemens S7-1500 CPU to obtain its IP address from DHCP, follow these steps in TIA Portal:
✅ Enable DHCP on S7-1500
1. Open your project in TIA Portal
- Go to Devices & networks (or double-click the CPU).
2. Select the CPU
- In the project tree, click on CPU → Properties.
3. Go to PROFINET interface settings
- Under Properties, open:
→ PROFINET interface [X1]
→ Ethernet addresses
4. Change IP assignment mode
-
Find IP protocol → Assignment method.
-
Select:
▶ “Obtain IP address automatically (DHCP)”
Other options in that menu:
-
Use static IP address
-
Set IP via BOOTP
-
Set IP via DCP
-
Obtain IP via DHCP ← choose this
5. (Optional) Specify hostname for DHCP
Some DHCP servers assign IP based on hostname.
Under the same settings you can enter:-
Device name (PROFINET name)
-
Hostname
6. Download hardware configuration to the PLC
-
Click Download to device.
-
After reboot, the CPU will request its IP from the DHCP server.
📌 Confirm DHCP operation
You can verify:
-
On the diagnostic buffer of the CPU
-
On your DHCP server (it should list the PLC lease)
-
In TIA Portal online view → Interface details
📝 Notes
-
The CPU must be on a network with a DHCP server (router or Windows DHCP).
-
Avoid mixing PROFINET device name settings with static IP unless needed.
-
Safety CPUs behave the same but require a password to download.
To set DHCP directly on the S7-1500 CPU itself using the front display (the built-in HMI on the CPU), without TIA Portal.
Here is how:
✅ Enable DHCP directly on the S7-1500 front display
1. Press the “OK” button on the CPU to open the local menu.
2. Navigate to:
Settings → Interfaces → PROFINET / Ethernet → IP configuration
Depending on firmware, the menu names may be slightly different:
-
IP configuration
-
Address assignment
-
IP settings
3. Change “IP assignment”
You will see options such as:
-
Static
-
BOOTP
-
DCP
-
DHCP
👉 Select DHCP.
4. Save & Reboot
The CPU usually asks to confirm.
After confirmation, it will restart the interface and request an IP from your DHCP server.
✔ Notes
-
The CPU’s display allows changing IP, subnet, gateway, and assignment method.
-
If the PLC is password-protected for access level, you may need to enter the authorization level.
-
After switching to DHCP, the CPU will lose its old static IP, so make sure you know how to find it in your DHCP server or network scanner.
“IP settings are write-protected”, it means the CPU has a protection level or a configuration setting that prevents changing network parameters from the front display.
Here are the common causes and how to fix them:
✅ 1. Protection level blocks changes
If the CPU is set to “Full access (password protected)” or “Read access only”, you cannot change IP settings on the display.
Fix
Connect with TIA Portal → go to:
Device → Protection & Security → Access level
Set one of these:
-
Full access (no protection)
or -
Full access (password protected) → enter password when asked on device
Then download the hardware configuration again.
✅ 2. “Write protection” enabled in TIA Portal
In TIA Portal, under the PLC properties there is a setting that disables local changes.
Check:
Properties → General → Operating mode → Prevent local changes
(or similar depending on version)If “IP address write-protected” or “Allow local configuration: No” is active → you must disable it.
✅ 3. CPU memory card (SIMATIC Memory Card) contains locked settings
Some projects enforce IP settings through the memory card.
In that case the CPU restores the IP from the card and blocks display changes.Fix
Either:
-
Remove the memory card → reboot → change IP → insert again
or -
Change IP settings in TIA Portal → download to the CPU → card gets updated
✅ 4. The interface is part of a PROFINET system where the IP is assigned by the device name
If the PLC is inside a PROFINET IO system, its IP may be controlled by DCP/PNIO, not manually.
Symptoms:
- You cannot change the IP because it is bound to the PROFINET device name.
Fix
In TIA Portal:
-
Go to Devices & networks → CPU → PROFINET interface
-
Look for “IP assigned by IO controller” or “Use device name assignment”
-
Disable it if you need manual IP control
👉 What you can try immediately on the device
-
Go to Display → Settings → Access level
-
If it asks for a password, enter the CPU protection password
-
Try again to change the IP settings
If you don’t know the password, only TIA Portal download can change it.
-
Disable NVIDIA Quadro 2200 GPU from Windows 11 Command Line
Disable NVIDIA Quadro 2200 GPU from Windows 11 Command Line
✅ Step 1 — Install DevCon
DevCon is included with the Windows Driver Kit (WDK).
After installation, DevCon is typically located at:
C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe✅ Step 2 — Use the Device Instance ID
Your GPU hardware ID:
PCI\VEN_10DE&DEV_1C31&SUBSYS_131B1028&REV_A1✅ Step 3 — Open Command Prompt as Administrator
Search for cmd, right‑click, choose Run as administrator.
✅ Step 4 — Disable the GPU
Run:
devcon disable "PCI\VEN_10DE&DEV_1C31&SUBSYS_131B1028&REV_A1"🔄 Re-enable Later
devcon enable "PCI\VEN_10DE&DEV_1C31&SUBSYS_131B1028&REV_A1"ℹ️ Notes
✔ If the exact ID doesn’t match, try using a wildcard:
devcon disable "PCI\VEN_10DE&DEV_1C31*"✔ List matching NVIDIA devices:
devcon find *VEN_10DE*✔ Wildcard disable:
devcon disable "PCI\VEN_10DE&DEV_1C31*"✔ To get the full instance ID from CMD:
devmgmt.msc → View → Devices by connection → Properties → Details → “Device instance path”. -
AI Prompts
Useful AI Prompts
-
Désactiver Privacy Badger pour un site spécifique
Désactiver Privacy Badger pour un site spécifique
Ce guide explique comment désactiver temporairement Privacy Badger pour un seul site afin qu’il fonctionne correctement, tout en conservant la protection activée partout ailleurs.
1. Ouvrir le site
Accédez à la page Web que vous souhaitez autoriser (le site qui ne fonctionne pas lorsque Privacy Badger est actif).
2. Ouvrir la fenêtre de Privacy Badger
Cliquez sur l’icône de Privacy Badger dans la barre d’outils de Chrome (en haut à droite) pour ouvrir son panneau.
3. Désactiver Privacy Badger pour ce site
Dans la fenêtre, cliquez sur « Disable for this site » (désactiver pour ce site).
Cette action ajoute le site à la liste blanche. Rechargez ensuite la page.Astuce : Le texte peut varier selon les versions, mais l’action reste la même — désactiver Privacy Badger pour le domaine actuel.
4. Recharger & vérifier
- Après avoir autorisé le site, rechargez la page.
- Si le problème persiste, essayez un rechargement forcé (Ctrl + Maj + R) ou F5 ou ouvrez la page en navigation privée.
- Vérifiez que le site fonctionne désormais correctement avec Privacy Badger désactivé pour ce domaine.
-
markdown-cheatsheet
📘 Markdown Shortcuts Cheat-Sheet 📝 Basic Formatting Bold → text or text
-
AI Shortcut Prompt Codes
⭐ Favorite Shortcut Commands
-
Enable Run as Administartor option to a shortcut in desktop
Here is the powershell script to modify the Shortcut parameters: ```powershell param( [Parameter(Mandatory=$true)] [string]$computerListPath,
[Parameter(Mandatory=$true)] [string]$shortcutName, [Parameter(Mandatory=$false)] [string]$logPath ) -
Disable a service and set the recovery to take no action.
Here’s the Powershell script: ```powershell <# .SYNOPSIS Disables a Windows service and resets its recovery options across multiple computers.
-
Turn off Bitlocker and check the status of the decrytion.
Here is the powershell script that turns off Bitlocker and checks the status of the decryptions.
-
Create a Custom Recovery Partition on Windows 11
📦 PowerShell Script: Create a Custom Recovery Partition on Windows 11
This script automates the process of capturing a custom system image and creating a dedicated recovery partition. The recovery partition can then be used with “Reset this PC” > “Keep my files” to restore the system with pre-installed software.
✅ Features
- Captures current system (
C:) into a.wimimage using DISM - Creates a new recovery partition (30% of disk size)
- Copies the
.wimintoRecovery\WindowsRE - Registers the custom image with
reagentc - Hides the recovery partition from normal users
⚠️ Requirements
- Must run as Administrator
- DISM must be available (built into Windows 10/11)
- Sufficient disk space to shrink
C:and store.wim
💻 Usage Instructions
- Save the script below as
Create-RecoveryPartition.ps1 - Run PowerShell as Administrator
- Wait for all steps to complete — it may take several minutes
- Verify recovery status using:
reagentc /info
📝 PowerShell Script
# === Config === $WimName = "CustomRefresh" $ImageName = "Custom Windows Image" $ImageIndex = 1 $CapturePath = "C:\" $TempWim = "C:\Recovery\$WimName.wim" $RecFolder = "Recovery\WindowsRE" $VolumeLabel = "Recovery" $TempLetter = "R" $DiskNumber = 0 # <-- Adjust if needed (0 is usually system disk) # === Check Admin Privileges === if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Administrator")) { Write-Error "Run this script as Administrator." exit 1 } # === Create folder if needed === if (!(Test-Path "C:\Recovery")) { New-Item -ItemType Directory -Path "C:\Recovery" | Out-Null } # === Capture current C: as WIM image === Write-Host "📦 Capturing system image to $TempWim ..." $dismCmd = "dism /Capture-Image /ImageFile:`"$TempWim`" /CaptureDir:C:\ /Name:`"$ImageName`"" Start-Process -Wait -FilePath cmd.exe -ArgumentList "/c $dismCmd" if (!(Test-Path $TempWim)) { Write-Error "DISM capture failed. Exiting." exit 1 } # === Get Disk Size and Calculate Shrink Amount === Write-Host "📊 Calculating 30% shrink from disk 0..." $vol = Get-Volume -DriveLetter C $disk = Get-Disk -Number $DiskNumber $shrinkSizeMB = [math]::Floor($disk.Size / 1024 / 1024 * 0.3) # === Create DiskPart script === $diskpartScript = @" select disk $DiskNumber list volume select volume $($vol.UniqueId) shrink desired=$shrinkSizeMB create partition primary format fs=ntfs quick label=$VolumeLabel assign letter=$TempLetter set id=de94bba4-06d1-4d40-a16a-bfd50179d6ac gpt attributes=0x8000000000000001 exit "@ $scriptPath = "$env:TEMP\diskpart_script.txt" $diskpartScript | Set-Content -Path $scriptPath -Encoding ASCII Start-Process -Wait -FilePath diskpart -ArgumentList "/s `"$scriptPath`"" Remove-Item $scriptPath # === Copy image to recovery partition === $targetFolder = "$($TempLetter):\$RecFolder" Write-Host "📁 Copying image to $targetFolder ..." New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null Copy-Item -Path $TempWim -Destination "$targetFolder\CustomRefresh.wim" # === Register custom image === Write-Host "🛠️ Configuring recovery image with reagentc..." Start-Process -Wait -FilePath cmd.exe -ArgumentList "/c reagentc /disable" Start-Process -Wait -FilePath cmd.exe -ArgumentList "/c reagentc /SetOSImage /Path $TempLetter`:\$RecFolder /Index $ImageIndex" Start-Process -Wait -FilePath cmd.exe -ArgumentList "/c reagentc /enable" # === Show status === Write-Host "`n📋 Recovery configuration:" Start-Process -Wait -FilePath cmd.exe -ArgumentList "/c reagentc /info" # === Hide the recovery partition === Write-Host "🔒 Hiding recovery partition..." $removeScript = @" select volume $TempLetter remove letter=$TempLetter exit "@ $removePath = "$env:TEMP\remove_letter.txt" $removeScript | Set-Content -Path $removePath -Encoding ASCII Start-Process -Wait -FilePath diskpart -ArgumentList "/s `"$removePath`"" Remove-Item $removePath Write-Host "`n✅ Done! Recovery partition created and configured." -ForegroundColor Green
🔁 Result
Once the script is run, your system will be configured to use your captured recovery image when you choose:
Settings > System > Recovery > Reset this PC > Keep my files
📌 Notes
- Only use on stable systems (tested and configured).
- Always keep a backup before modifying partitions.
- You can optionally place the
.wimon a USB or external disk for reuse on other machines.
🔧 Bootable Recovery USB (Optional)
To create a bootable USB containing the recovery image and tools:
🛠️ Steps
-
Insert a USB drive (8 GB or larger)
-
Format the USB as FAT32 or NTFS
-
Mount a Windows ISO (same version as your current OS) or download the Windows ADK to access WinPE tools.
-
Create WinPE boot environment (if not using ISO):
# From Deployment and Imaging Tools Environment (as Admin) copype amd64 C:\WinPE_amd64 MakeWinPEMedia /UFD C:\WinPE_amd64 E:- Copy your custom .wim file to the USB:
# Assuming CustomRefresh.wim exists Copy-Item C:\Recovery\CustomRefresh.wim E:\sources\install.wim-
Make USB bootable (if not already via MakeWinPEMedia). For ISO-based bootable USB, use
Rufusto burn the Windows ISO and then replace theinstall.wim. -
Boot into USB and use DISM or Recovery Environment to apply the image manually.
✅ Apply WIM manually (example):
# In Windows PE command prompt: dism /Apply-Image /ImageFile:E:\sources\install.wim /Index:1 /ApplyDir:C:\
- Captures current system (
-
Inspection sécurisée de liens suspects avec Application Guard
🔐 Inspection sécurisée de liens suspects avec Application Guard et notepad.libreops.cc
🎯 Objectif
Analyser des liens suspects provenant d’e-mails ou de messages en toute sécurité, en procédant ainsi :
- Coller les liens dans un bloc-notes en ligne avant d’entrer dans le bac à sable.
- Ouvrir Microsoft Edge avec Application Guard.
- Accéder à la page du bloc-notes depuis Edge sécurisé pour copier les liens et les inspecter sans risque.
🧰 Prérequis
- Windows 10/11 Professionnel ou Entreprise
-
Microsoft Defender Application Guard activé :
- Ouvrir
optionalfeatures.exe→ cocher Microsoft Defender Application Guard - Redémarrer si demandé
- Ouvrir
🌐 Étapes à suivre
🟢 Étape 1 : Ouvrir notepad.libreops.cc dans votre navigateur principal
- Rendez-vous sur : https://notepad.libreops.cc
- Collez le ou les liens suspects dans la note.
- Cliquez sur « Save » (en haut à droite).
- Copiez l’URL unique de la note (ex. :
https://notepad.libreops.cc/xyz123abc456)
💡 Vous utiliserez cette URL dans le navigateur sécurisé pour accéder aux liens.
🔵 Étape 2 : Ouvrir Microsoft Edge avec Application Guard
- Lancez Microsoft Edge.
- Cliquez sur le menu à trois points → Nouvelle fenêtre Application Guard.
🔐 Cela ouvre une session de navigation hautement isolée.
🔍 Étape 3 : Accéder à votre note depuis Edge sécurisé
-
Dans la fenêtre Application Guard, ouvrez l’URL copiée à l’étape 1
(ex. :https://notepad.libreops.cc/xyz123abc456) - Une fois la note ouverte :
- Copiez le lien suspect depuis la note.
- Collez-le dans la barre d’adresse ou survolez-le pour l’analyser.
- Vous pouvez maintenant :
- Vérifier visuellement le lien.
- L’ouvrir uniquement si nécessaire.
- Utiliser des outils d’analyse depuis Application Guard :
🧽 Après utilisation
- Fermez la fenêtre Application Guard pour détruire la session.
-
How to Configure network printer on HMI
Setting Up a Network Printer on HMI
Source : Link
- Our architecture is as below:
Instructions: Printing via network
• Call up the control Panel. • Open the “Printer Properties” dialog with the “Printer” icon.
– Select the printer type “PCL Laser” under “Printer Language”.
– Select the interface “PrintServer” under “Port”.
– Enter the network address of the print server under “IP:Port:”.
Note: Many printers have an integrated “print server”. This allows you to specify the address of the printer directly.
Note the correct notation -> “IP address:port number” (colon between the IP address and port number (Link)) Example: 172.16.34.30:9100
– Select the paper size in the “Paper Size” drop-down box.
– Specify the orientation of the printout under “Orientation”: “Portrait” for vertical format “Landscape” for horizontal format
– Select the print quality:
To print in draft quality, select “Draft Mode”.
To print in color, select “Color”.
– Confirm the entries with “OK”.
- make sure the network configuration of the HMI is from DHCP
- Note: if IP is set manually , do not forget to fill in the default gateway address.
- Our architecture is as below:
-
Docker Overlay Cleanup Script
Docker Overlay Cleanup Script
Here is a script to help clean up Docker overlay files on an Ubuntu system running in Proxmox.
#!/bin/bash # Define the base directory for Docker overlay files OVERLAY_BASE_DIR="/var/lib/docker/overlay2" # Loop through each overlay directory find "$OVERLAY_BASE_DIR" -type d -name "diff" | while read -r diff_dir; do # Define the tmp directory within each diff directory tmp_dir="$diff_dir/tmp" # Check if the tmp directory exists if [ -d "$tmp_dir" ]; then echo "Cleaning up temporary files in $tmp_dir" # Remove files and directories within tmp that are older than 1 day find "$tmp_dir" -mindepth 1 -mtime +1 -exec rm -rf {} + fi done echo "Cleanup complete."Explanation:
-
Base Directory: The script targets
/var/lib/docker/overlay2, where Docker stores its overlay files. -
Loop through Directories: It searches for directories named
diffwithin the overlay directories. Eachdiffdirectory represents the writable layer of a Docker container. -
Temporary Files: Within each
diffdirectory, it looks for atmpdirectory, which is typically used for temporary files. -
Cleanup: It removes files and directories within
tmpthat are older than 1 day. You can adjust the-mtime +1parameter to change the age threshold for files to be deleted.
Important Considerations:
-
Backup: Always ensure you have backups of important data before running cleanup scripts.
-
Running Containers: Be cautious with running containers. Cleaning up files in use by active containers can cause issues.
-
Permissions: Ensure the script is run with appropriate permissions. You might need to run it with
sudo. -
Testing: Test the script in a safe environment before applying it to a production system.
You can save this script to a file, for example,
clean_docker_overlay.sh, make it executable withchmod +x clean_docker_overlay.sh, and run it withsudo ./clean_docker_overlay.sh. Adjust the script as necessary to fit your specific needs and environment. -
-
Dual boot to VHDX without USB
Automated Windows 11 Tiny Core VHDX Creator Script
Create a lightweight Windows 11 installation in a virtual disk (VHDX) file with minimal user intervention using this comprehensive PowerShell automation script.
Overview
This script automates the process of creating a Windows 11 Tiny Core installation inside a VHDX file, making it perfect for virtualization, testing, or creating portable Windows environments. The process is divided into two clear phases with minimal manual intervention required.
Features
- ✅ Fully Automated VHDX Creation - Creates, formats, and mounts virtual disk
- ✅ Automatic Tiny11 Builder Integration - Downloads and runs Tiny11 Builder automatically
- ✅ Custom Boot Menu Names - Add personalized boot entry names
- ✅ Interactive Image Selection - Choose specific Windows 11 editions
- ✅ Two-Phase Process - Clear separation between ISO creation and disk operations
- ✅ Comprehensive Error Handling - Detailed feedback and error management
- ✅ Flexible Configuration - Customizable paths, sizes, and names
Prerequisites
Before running the script, ensure you have:
- Administrator privileges (required for diskpart and DISM operations)
- Windows 11 ISO file downloaded from Microsoft
- Sufficient disk space (at least 60GB recommended)
- PowerShell 5.1 or later
Quick Start
Basic Usage
# Run with default settings .\Win11TinyVHDX.ps1Advanced Usage
# Custom configuration .\Win11TinyVHDX.ps1 -VHDXPath "D:\VMs\MyTinyWin11.vhdx" -VHDXSizeGB 40 -VHDXLabel "TinyWindows" -BootEntryName "Win11-Custom-Build"Script Parameters
Parameter Default Value Description VHDXPathE:\VHD_Store\Win11tinycore.vhdxFull path for the VHDX file VHDXSizeGB50Size of the virtual disk in GB VHDXLabelWin11tinycoreVolume label for the virtual disk BootEntryNameWin11TinyCore-MasterCustom name in boot menu Process Flow
Phase 1: Tiny11 Core ISO Creation 🔨
-
Mount Windows 11 ISO (Manual)
- Download Windows 11 ISO from Microsoft
- Right-click → Mount or use Windows Explorer
-
Specify ISO Drive Letter (Interactive)
- Script displays available drives
- Enter the drive letter where ISO is mounted
-
Download Tiny11 Builder (Automated)
- Automatically downloads from GitHub repository
- Extracts and prepares the builder tool
-
Create Tiny11 ISO (Automated)
- Runs Tiny11 Builder automatically
- Creates optimized Windows 11 ISO
-
Mount Tiny11 ISO (Manual)
- Mount the newly created Tiny11 ISO file
Phase 2: Virtual Disk Creation & Image Application 💾
-
Create VHDX Virtual Disk (Automated)
- Uses diskpart to create expandable VHDX
- Formats and assigns drive letter
-
Select Windows Edition (Interactive)
- Displays available Windows editions
- Choose the desired edition index
-
Apply Windows Image (Automated)
- Uses DISM to install Windows to VHDX
- Applies selected edition
-
Configure Boot Manager (Automated)
- Sets up boot configuration
- Adds custom boot menu entry
Sample Output
=== Windows 11 Tiny Core VHDX Creator === This script will create a VHDX and install Windows 11 Tiny Core === Phase 1: Creating Tiny11 Core ISO === Step 1: Manual steps required: 1. Download Windows 11 ISO file 2. Mount the Windows 11 ISO (right-click -> Mount) Step 2: Enter the drive letter where Windows 11 ISO is mounted: Available drives: D: - Windows_11_ISO Enter drive letter (e.g., D): D Step 3: Downloading Tiny11 Builder... Tiny11 Builder downloaded successfully! Step 4: Running Tiny11 Builder automatically... Executing Tiny11 Builder... Source ISO: D:\ Tiny11 Builder completed successfully! =============================================== TINY11 CORE ISO CREATION COMPLETED! =============================================== NEXT PHASE INFORMATION: The script will now proceed to: • Create a virtual disk (VHDX) file • Format and prepare the virtual disk • Apply the Tiny11 Core image to the virtual disk • Configure the boot manager for the installation Virtual Disk Details: Path: E:\VHD_Store\Win11tinycore.vhdx Size: 50 GB Label: Win11tinycore Boot Entry: Win11TinyCore-Master Do you want to proceed with virtual disk creation and image application? (y/n): y === Phase 2: Creating Virtual Disk and Applying Image === Step 7: Creating VHDX... VHDX created successfully and mounted as drive X: Step 8: Getting Windows image information... [WIM Edition List Displayed] Enter index number: 6 Step 9: Applying Windows image... Windows image applied successfully! Step 10: Configuring boot manager... Boot manager configured successfully with custom name! === Installation Complete! ===Technical Details
VHDX Creation Process
The script uses diskpart commands to:
create vdisk file="path" maximum=size type=expandable attach vdisk create part primary format quick label="label" assign letter=XImage Application
Uses DISM (Deployment Image Servicing and Management) tool:
dism /apply-image /imagefile:source.wim /index:N /applydir:X:\Boot Configuration
Configures Windows Boot Manager with custom entry:
bcdboot X:\windows /d "Custom Boot Name"Creating Child Vms with differencing disk
New-VHD -ParentPath 'E:\VHD_Store\Win11tinycore.vhdx' -Path 'E:\VHD_Store\Android.vhdx' -DifferencingNote:
- Use the command above to create a differencing VHD. Then, use EasyBCD to add the newly created VHDX to the boot menu. Ensure that the Hyper-V Virtual Machine Management service is running.
- Before creating the differencing disk, ensure that you have installed all necessary drivers and essential software on the parent VM. This will prevent the need to reinstall them on the child VMs.
- Do not update the parent VM under any circumstances, as this could render the child VMs unbootable. Make sure not to boot into the parent VM after creating child vms.
Use Cases
1. Virtual Machine Development
- Create lightweight VMs for testing
- Rapid deployment of clean Windows environments
- Resource-efficient virtualization
2. System Recovery
- Portable Windows environment on external drive
- Emergency boot scenarios
- System repair and maintenance
3. Educational Purposes
- Learning Windows internals
- Understanding deployment processes
- Teaching virtualization concepts
4. Software Testing
- Isolated testing environments
- Clean OS installations for software validation
- Multiple Windows configurations
Benefits of Tiny11 Core
- Reduced Size: ~2-3GB smaller than standard Windows 11
- Faster Boot: Optimized startup performance
- Lower Resource Usage: Minimal background processes
- Essential Features: Keeps core Windows functionality
- Compatibility: Full Windows 11 application support
Troubleshooting
Common Issues
Script requires Administrator privileges
Solution: Right-click PowerShell → "Run as Administrator"VHDX creation fails
Solution: Ensure sufficient disk space and valid path Check: Disk permissions and available storageTiny11 Builder download fails
Solution: Check internet connection and GitHub accessibility Alternative: Download manually from https://github.com/ntdevlabs/tiny11builderImage application takes too long
Solution: Normal behavior - DISM operations can take 15-30 minutes Monitor: Task Manager for disk activityBoot configuration fails
Solution: Ensure Windows image was applied successfully Check: X:\windows directory exists and contains system filesPerformance Tips
- Use SSD storage for faster VHDX operations
- Allocate sufficient RAM during image application
- Close unnecessary applications to free up system resources
- Use wired network connection for downloads
Security Considerations
- Run from trusted sources - Verify script integrity
- Administrator privileges - Understand elevation requirements
- Antivirus exclusions - May need temporary exclusions for VHDX operations
- Network downloads - Ensure secure connection for Tiny11 Builder
Advanced Configuration
Custom VHDX Locations
# Network storage .\Win11TinyVHDX.ps1 -VHDXPath "\\server\vms\tiny11.vhdx" # External drive .\Win11TinyVHDX.ps1 -VHDXPath "F:\VirtualMachines\Win11Tiny.vhdx"Multiple Configurations
# Development environment .\Win11TinyVHDX.ps1 -BootEntryName "Win11-Dev-Environment" -VHDXSizeGB 60 # Testing environment .\Win11TinyVHDX.ps1 -BootEntryName "Win11-Test-Lab" -VHDXSizeGB 40Script Download
The complete PowerShell script is available below. Save it as
Win11TinyVHDX.ps1and run with Administrator privileges.Note: Ensure execution policy allows script execution:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser# Windows 11 Tiny Core VHDX Creator Script # Requires Administrator privileges param( [string]$VHDXPath = "E:\VHD_Store\Win11tinycore.vhdx", [int]$VHDXSizeGB = 50, [string]$VHDXLabel = "Win11tinycore", [string]$BootEntryName = "Win11TinyCore-Master" ) # Check if running as administrator if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Host "This script requires Administrator privileges. Please run as Administrator." -ForegroundColor Red exit 1 } Write-Host "=== Windows 11 Tiny Core VHDX Creator ===" -ForegroundColor Cyan Write-Host "This script will create a VHDX and install Windows 11 Tiny Core" -ForegroundColor Yellow Write-Host "" # Function to create VHDX using diskpart function Create-VHDX { param( [string]$Path, [int]$SizeMB, [string]$Label, [string]$DriveLetter = "X" ) Write-Host "Creating VHDX file at: $Path" -ForegroundColor Green # Ensure directory exists $directory = Split-Path $Path -Parent if (!(Test-Path $directory)) { New-Item -ItemType Directory -Path $directory -Force | Out-Null } # Create diskpart script $diskpartScript = @" create vdisk file="$Path" maximum=$SizeMB type=expandable attach vdisk create part primary format quick label="$Label" assign letter=$DriveLetter exit "@ $scriptPath = "$env:TEMP\create_vhdx.txt" $diskpartScript | Out-File -FilePath $scriptPath -Encoding ASCII # Run diskpart Write-Host "Running diskpart to create and format VHDX..." -ForegroundColor Yellow diskpart /s $scriptPath # Clean up Remove-Item $scriptPath -Force if (Test-Path "${DriveLetter}:\") { Write-Host "VHDX created successfully and mounted as drive ${DriveLetter}:" -ForegroundColor Green return $true } else { Write-Host "Failed to create or mount VHDX" -ForegroundColor Red return $false } } # Function to download Tiny11 Builder function Download-Tiny11Builder { $downloadPath = "$env:TEMP\tiny11builder" $zipPath = "$downloadPath\tiny11builder.zip" Write-Host "Downloading Tiny11 Builder..." -ForegroundColor Yellow # Create download directory if (!(Test-Path $downloadPath)) { New-Item -ItemType Directory -Path $downloadPath -Force | Out-Null } try { # Download the repository $url = "https://github.com/ntdevlabs/tiny11builder/archive/refs/heads/main.zip" Invoke-WebRequest -Uri $url -OutFile $zipPath -UseBasicParsing # Extract the zip Expand-Archive -Path $zipPath -DestinationPath $downloadPath -Force # Find the PowerShell script $scriptPath = Get-ChildItem -Path $downloadPath -Name "tiny11Coremaker.ps1" -Recurse | Select-Object -First 1 if ($scriptPath) { $fullScriptPath = Join-Path $downloadPath $scriptPath Write-Host "Tiny11 Builder downloaded to: $fullScriptPath" -ForegroundColor Green return $fullScriptPath } else { Write-Host "Could not find tiny11Coremaker.ps1 in downloaded files" -ForegroundColor Red return $null } } catch { Write-Host "Failed to download Tiny11 Builder: $($_.Exception.Message)" -ForegroundColor Red return $null } } # Function to get Windows 11 ISO drive letter function Get-ISODriveLetter { param([string]$Prompt) Write-Host $Prompt -ForegroundColor Yellow Write-Host "Available drives:" -ForegroundColor Cyan Get-WmiObject -Class Win32_LogicalDisk | Where-Object { $_.DriveType -eq 5 } | ForEach-Object { Write-Host " $($_.DeviceID) - $($_.VolumeName)" -ForegroundColor White } do { $driveLetter = Read-Host "Enter drive letter (e.g., D)" $driveLetter = $driveLetter.TrimEnd(':').ToUpper() if ($driveLetter -match "^[A-Z]$" -and (Test-Path "${driveLetter}:\")) { return $driveLetter } else { Write-Host "Invalid drive letter or drive not found. Please try again." -ForegroundColor Red } } while ($true) } # Function to get WIM info and let user select index function Get-WIMInfo { param([string]$ISODriveLetter) $wimFile = "${ISODriveLetter}:\sources\install.wim" if (!(Test-Path $wimFile)) { Write-Host "install.wim not found at $wimFile" -ForegroundColor Red return $null } Write-Host "Getting Windows image information..." -ForegroundColor Yellow Write-Host "" try { # Get WIM info $wimInfo = dism /get-wiminfo /wimfile:$wimFile $wimInfo | Write-Host Write-Host "" Write-Host "Please select the Windows edition index from the list above:" -ForegroundColor Yellow do { $index = Read-Host "Enter index number" if ($index -match "^\d+$" -and [int]$index -gt 0) { return [int]$index } else { Write-Host "Please enter a valid index number." -ForegroundColor Red } } while ($true) } catch { Write-Host "Failed to get WIM information: $($_.Exception.Message)" -ForegroundColor Red return $null } } # Function to apply Windows image function Apply-WindowsImage { param( [string]$ISODriveLetter, [int]$Index, [string]$TargetDrive = "X" ) $wimFile = "${ISODriveLetter}:\sources\install.wim" $targetPath = "${TargetDrive}:\" Write-Host "Applying Windows image..." -ForegroundColor Yellow Write-Host "Source: $wimFile" -ForegroundColor Cyan Write-Host "Index: $Index" -ForegroundColor Cyan Write-Host "Target: $targetPath" -ForegroundColor Cyan Write-Host "" try { dism /apply-image /imagefile:$wimFile /index:$Index /applydir:$targetPath if ($LASTEXITCODE -eq 0) { Write-Host "Windows image applied successfully!" -ForegroundColor Green return $true } else { Write-Host "Failed to apply Windows image (Exit code: $LASTEXITCODE)" -ForegroundColor Red return $false } } catch { Write-Host "Error applying Windows image: $($_.Exception.Message)" -ForegroundColor Red return $false } } # Function to configure boot manager function Configure-BootManager { param( [string]$TargetDrive = "X", [string]$BootEntryName = "Win11TinyCore-Master" ) Write-Host "Configuring boot manager..." -ForegroundColor Yellow Write-Host "Boot entry name: $BootEntryName" -ForegroundColor Cyan try { # Use /d parameter to set custom boot menu description bcdboot "${TargetDrive}:\windows" /d "$BootEntryName" if ($LASTEXITCODE -eq 0) { Write-Host "Boot manager configured successfully with custom name!" -ForegroundColor Green return $true } else { Write-Host "Failed to configure boot manager (Exit code: $LASTEXITCODE)" -ForegroundColor Red return $false } } catch { Write-Host "Error configuring boot manager: $($_.Exception.Message)" -ForegroundColor Red return $false } } # Main script execution try { Write-Host "=== Phase 1: Creating Tiny11 Core ISO ===" -ForegroundColor Magenta Write-Host "" Write-Host "Step 1: Manual steps required:" -ForegroundColor Magenta Write-Host "1. Download Windows 11 ISO file" -ForegroundColor Yellow Write-Host "2. Mount the Windows 11 ISO (right-click -> Mount)" -ForegroundColor Yellow Write-Host "" # Step 2: Get Windows 11 ISO drive letter $win11Drive = Get-ISODriveLetter -Prompt "Step 2: Enter the drive letter where Windows 11 ISO is mounted:" # Step 3: Download Tiny11 Builder Write-Host "" Write-Host "Step 3: Downloading Tiny11 Builder..." -ForegroundColor Magenta $tiny11Script = Download-Tiny11Builder if (!$tiny11Script) { throw "Failed to download Tiny11 Builder" } Write-Host "" Write-Host "Step 4: Running Tiny11 Builder automatically..." -ForegroundColor Magenta # Change to the Tiny11 Builder directory $tiny11Dir = Split-Path $tiny11Script -Parent Set-Location $tiny11Dir Write-Host "Executing Tiny11 Builder..." -ForegroundColor Yellow Write-Host "Source ISO: ${win11Drive}:\" -ForegroundColor Cyan Write-Host "" try { # Run the Tiny11 Builder script with the Windows 11 ISO path & $tiny11Script if ($LASTEXITCODE -eq 0) { Write-Host "Tiny11 Builder completed successfully!" -ForegroundColor Green } else { Write-Host "Tiny11 Builder finished with exit code: $LASTEXITCODE" -ForegroundColor Yellow } } catch { Write-Host "Error running Tiny11 Builder: $($_.Exception.Message)" -ForegroundColor Red throw "Failed to run Tiny11 Builder" } Write-Host "" Write-Host "Step 5: Manual step required:" -ForegroundColor Magenta Write-Host "Please mount the created Tiny11 ISO file" -ForegroundColor Yellow Write-Host "The ISO should be located in: $tiny11Dir" -ForegroundColor Cyan Write-Host "" Read-Host "Press Enter when you have mounted the Tiny11 ISO..." # Step 6: Get Tiny11 ISO drive letter $tiny11Drive = Get-ISODriveLetter -Prompt "Step 6: Enter the drive letter where Tiny11 ISO is mounted:" Write-Host "" Write-Host "===============================================" -ForegroundColor Green Write-Host " TINY11 CORE ISO CREATION COMPLETED!" -ForegroundColor Green Write-Host "===============================================" -ForegroundColor Green Write-Host "" Write-Host "NEXT PHASE INFORMATION:" -ForegroundColor Yellow Write-Host "The script will now proceed to:" -ForegroundColor White Write-Host " • Create a virtual disk (VHDX) file" -ForegroundColor Cyan Write-Host " • Format and prepare the virtual disk" -ForegroundColor Cyan Write-Host " • Apply the Tiny11 Core image to the virtual disk" -ForegroundColor Cyan Write-Host " • Configure the boot manager for the installation" -ForegroundColor Cyan Write-Host "" Write-Host "Virtual Disk Details:" -ForegroundColor Yellow Write-Host " Path: $VHDXPath" -ForegroundColor White Write-Host " Size: $VHDXSizeGB GB" -ForegroundColor White Write-Host " Label: $VHDXLabel" -ForegroundColor White Write-Host " Boot Entry: $BootEntryName" -ForegroundColor White Write-Host "" $continue = Read-Host "Do you want to proceed with virtual disk creation and image application? (y/n)" if ($continue -ne 'y' -and $continue -ne 'Y') { Write-Host "Script terminated by user." -ForegroundColor Yellow exit 0 } Write-Host "" Write-Host "=== Phase 2: Creating Virtual Disk and Applying Image ===" -ForegroundColor Magenta Write-Host "" # Step 7: Create VHDX Write-Host "Step 7: Creating VHDX..." -ForegroundColor Magenta $vhdxSizeMB = $VHDXSizeGB * 1024 if (!(Create-VHDX -Path $VHDXPath -SizeMB $vhdxSizeMB -Label $VHDXLabel)) { throw "Failed to create VHDX" } # Step 8: Get WIM info and select index Write-Host "" Write-Host "Step 8: Getting Windows image information..." -ForegroundColor Magenta $selectedIndex = Get-WIMInfo -ISODriveLetter $tiny11Drive if (!$selectedIndex) { throw "Failed to get WIM information or select index" } # Step 9: Apply Windows image Write-Host "" Write-Host "Step 9: Applying Windows image..." -ForegroundColor Magenta if (!(Apply-WindowsImage -ISODriveLetter $tiny11Drive -Index $selectedIndex)) { throw "Failed to apply Windows image" } # Step 10: Configure boot manager Write-Host "" Write-Host "Step 10: Configuring boot manager..." -ForegroundColor Magenta if (!(Configure-BootManager -BootEntryName $BootEntryName)) { throw "Failed to configure boot manager" } # Success message Write-Host "" Write-Host "=== Installation Complete! ===" -ForegroundColor Green Write-Host "Windows 11 Tiny Core has been successfully installed to VHDX:" -ForegroundColor Green Write-Host $VHDXPath -ForegroundColor Cyan Write-Host "" Write-Host "You can now:" -ForegroundColor Yellow Write-Host "1. Detach the VHDX: diskpart -> select vdisk file=$VHDXPath -> detach vdisk" -ForegroundColor White Write-Host "2. Use the VHDX with Hyper-V or other virtualization software" -ForegroundColor White Write-Host "3. Boot from the VHDX on physical hardware (advanced)" -ForegroundColor White } catch { Write-Host "" Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red Write-Host "Installation failed. Please check the error message above." -ForegroundColor Red exit 1 } # Optional: Detach VHDX Write-Host "" $detach = Read-Host "Would you like to detach the VHDX now? (y/n)" if ($detach -eq 'y' -or $detach -eq 'Y') { Write-Host "Detaching VHDX..." -ForegroundColor Yellow $detachScript = @" select vdisk file="$VHDXPath" detach vdisk exit "@ $scriptPath = "$env:TEMP\detach_vhdx.txt" $detachScript | Out-File -FilePath $scriptPath -Encoding ASCII diskpart /s $scriptPath Remove-Item $scriptPath -Force Write-Host "VHDX detached successfully!" -ForegroundColor Green }Conclusion
This automated script streamlines the creation of Windows 11 Tiny Core VHDX files, making it accessible for both beginners and advanced users. The two-phase approach provides clear checkpoints and flexibility, while the comprehensive error handling ensures a smooth experience.
Whether you’re creating virtual machines, building portable Windows environments, or setting up testing scenarios, this script provides a robust and efficient solution for your Windows 11 deployment needs.
Last updated: June 2025 Compatible with: Windows 11, PowerShell 5.1+ Requirements: Administrator privileges, Windows 11 ISO
-
Extend Logical Volume in Ubuntu
Steps to Extend Logical Volume in Ubuntu
This guide outlines the steps to extend a logical volume in Ubuntu to utilize the full disk space.
Prerequisites
- Access to a terminal with
sudoprivileges -
cloud-guest-utilspackage installed (forgrowpart)
Steps
Step 1: Check the Disk Layout
Verify the current disk layout to ensure that the additional space is available and correctly recognized by the system.
sudo fdisk -l /dev/sdaStep 2: Check Volume Group
Check the volume group to see how much free space is available.
sudo vgdisplayStep 3: Install
growpartIf
growpartis not installed, install it using the following commands:sudo apt update sudo apt install cloud-guest-utilsStep 4: Extend the Partition
Use
growpartto extend the partition to include the new disk space. Replace/dev/sdaand3with your disk and partition number if different.sudo growpart /dev/sda 3Step 5: Resize the Physical Volume
Resize the physical volume to include the new space:
sudo pvresize /dev/sda3Step 6: Extend the Logical Volume
Extend the logical volume to use all available free space in the volume group. Replace
/dev/ubuntu-vg/ubuntu-lvwith your logical volume path if different.sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lvStep 7: Resize the Filesystem
Resize the filesystem to utilize the new space. For an ext4 filesystem, use:
sudo resize2fs /dev/ubuntu-vg/ubuntu-lvStep 8: Verify the Changes
Check the disk space again to ensure that the changes have taken effect:
df -hConclusion
By following these steps, you should be able to extend your logical volume to utilize the full disk space available.
- Access to a terminal with
-
Setting Up a Weekly Cron Job for Pi-hole Gravity Updates
Setting Up a Weekly Cron Job for Pi-hole Gravity Updates
In this blog post, I’ll walk you through the process of setting up a weekly cron job to send an SMS notification when the Pi-hole gravity update is completed. This setup is particularly useful if you’re running Pi-hole in a Docker container and want to ensure that your cron job persists across container restarts.
Prerequisites
- A running Pi-hole instance in a Docker container.
- Access to the host machine where Docker is running.
- Basic knowledge of shell scripting and cron jobs.
Step 1: Create the Shell Script
First, create a shell script on the host machine that will be executed by the cron job. This script will update the Pi-hole gravity database and send an SMS notification upon successful completion.
-
Create the Script:
Open a text editor and create a new file named
pihole_update_gravity_with_sms.sh:sudo nano /usr/local/bin/pihole_update_gravity_with_sms.sh -
Add the Script Content:
Copy and paste the following content into the file:
#!/bin/bash # Path to the Pi-hole gravity update script GRAVITY_SCRIPT="docker exec pihole /usr/local/bin/pihole updateGravity" # SMS API credentials SMS_USER="USERNAME" SMS_PASS="APIKEY" SMS_MESSAGE="Pi-hole Gravity Update Completed Successfully." echo "Starting gravity update..." # Execute the gravity update \$GRAVITY_SCRIPT # Check if the gravity update was successful if [ \$? -eq 0 ]; then echo "Gravity update completed successfully." # Send notification via SMS using the confirmed working syntax echo "Sending SMS notification..." /usr/bin/curl "https://smsapi.free-mobile.fr/sendmsg?user=\${SMS_USER}&pass=\${SMS_PASS}&msg=\${SMS_MESSAGE}" echo "SMS notification sent successfully." else echo "Gravity update failed. No SMS notification sent." fi -
Make the Script Executable:
Change the permissions of the script to make it executable:
sudo chmod +x /usr/local/bin/pihole_update_gravity_with_sms.sh
Step 2: Add the Script to the Host’s Cron Schedule
Next, add the script to the host’s cron schedule to run it weekly at 5:30 AM every Monday.
-
Edit the Crontab:
Open the crontab file for editing:
sudo crontab -e -
Add the Cron Job Entry:
Add the following line to the crontab file to run the script at 5:30 AM every Monday:
30 5 * * 1 /usr/local/bin/pihole_update_gravity_with_sms.sh -
Save and Exit:
Save the changes and exit the crontab editor.
Step 3: Verify the Cron Job
To ensure that the cron job is set up correctly, you can manually trigger the script and check the logs for any output or errors.
-
Manually Run the Script:
Execute the script manually to verify that it works as expected:
sudo /usr/local/bin/pihole_update_gravity_with_sms.sh -
Check the Logs:
Review the system logs to see if the script was executed and if there were any errors:
bash sudo tail -f /var/log/syslog
Conclusion
By following these steps, you have successfully set up a weekly cron job to send an SMS notification upon the completion of the Pi-hole gravity update. This setup ensures that you are promptly notified of the update status, helping you maintain the efficiency of your Pi-hole instance.
-
Automount Rclone Remote at System Startup
Automount Rclone Remote at System Startup (Using systemd)
To automount your Rclone remote at system startup, follow these steps:
1. Create a systemd Service Unit
Create a systemd unit file (example:
/etc/systemd/system/rclone-debridlink.service).[Unit] Description=Rclone Mount for DebridLink Seedbox After=network-online.target Wants=network-online.target [Service] Type=notify ExecStart=/usr/bin/rclone mount debridlink:/Seedbox /mnt/debridlink \\ --allow-other \\ --dir-cache-time 10s \\ --vfs-cache-mode full \\ --vfs-cache-max-size 500M \\ --vfs-read-chunk-size 8M \\ --vfs-read-chunk-size-limit 128M \\ --vfs-fast-fingerprint \\ --no-modtime \\ --read-only \\ --allow-non-empty \\ --log-level INFO \\ --log-file /var/log/rclone.log ExecStop=/bin/fusermount -u /mnt/debridlink Restart=on-failure User=<your-username> Group=<your-group> TimeoutSec=30 [Install] WantedBy=multi-user.targetReplace
<your-username>and<your-group>with the appropriate values (you can find them usingwhoamiandid -gn).
2. Enable FUSE Permissions
Ensure your user is part of the
fusegroup (if applicable):sudo usermod -aG fuse <your-username>Also install necessary FUSE packages (if missing):
sudo apt install fuse3
3. Enable and Start the Service
Enable and start your systemd service:
sudo systemctl daemon-reexec sudo systemctl daemon-reload sudo systemctl enable rclone-debridlink.service sudo systemctl start rclone-debridlink.serviceYou can check the status with:
systemctl status rclone-debridlink.service
✅ Your Rclone mount should now automatically mount on each system boot.
-
How to Set Up a Secure VPN Wi-Fi Access Point with OpenWRT and ProtonVPN (WireGuard)
In this guide, we’ll walk through setting up an OpenWRT-based Wi-Fi access point that forces all connected clients to route their traffic through ProtonVPN using WireGuard. This ensures privacy, prevents DNS leaks, and blocks fallback to your ISP (kill switch).
🔧 Prerequisites
- OpenWRT-compatible router with internet access
- OpenWRT firmware installed (with LuCI interface)
- ProtonVPN account with WireGuard support
1. Install Required Packages
SSH into your OpenWRT router and run:
opkg update opkg install luci-proto-wireguard wireguard-tools luci-app-wireguard kmod-wireguard
2. Configure WireGuard Interface
Use ProtonVPN’s WireGuard config generator and enter the config into:
LuCI > Network > Interfaces > Add new interface
- Name:
wg0 - Protocol: WireGuard VPN
- Assign firewall zone:
wgzone
Add interface details from ProtonVPN:
- Private key
- Public key
- Endpoint IP/port
- Allowed IPs:
0.0.0.0/0 - DNS server:
10.2.0.1(or as provided)
**Proton Doc: Link
3. Create a VPN-Only Wi-Fi Network
LuCI > Network > Wireless > Add
- SSID:
VPN-WiFi - Network: check vpnlan only
Then go to Network > Interfaces > Add:
- Name:
vpnlan - Protocol: Static
- IPv4 address:
192.168.100.1 - Netmask:
255.255.255.0 - Firewall zone: create new
vpnlan
Enable DHCP:
Network > DHCP and DNS > Interfaces > vpnlan- Start: 100
- Limit: 150
- Lease time: 12h
4. Configure Firewall Zones
LuCI > Network > Firewall > Zones
Create zones:
-
vpnlan: covers
vpnlannetwork- Input: accept
- Output: accept
- Forward: reject
- ✅ Masquerading
- ✅ MSS Clamping
-
wgzone: covers
wg0- Input: reject
- Output: accept
- Forward: reject
- ✅ Masquerading
Forwarding Rules:
- Allow
vpnlan ➝ wgzone - ❌ Do NOT allow
vpnlan ➝ wan
5. Force DNS Over VPN
Network > DHCP and DNS:
- ✅ Ignore resolv file
- DNS Forwardings:
10.2.0.1(or ProtonVPN DNS)
Firewall > Traffic Rules:
-
Allow DNS to VPN:
- Source zone:
vpnlan - Destination zone:
wgzone - Destination IP:
10.2.0.1 - Port: 53
- ✅ Accept
- Source zone:
-
Block All Other DNS:
- Source zone:
vpnlan - Port: 53
- ❌ Reject
- Source zone:
6. Test the Setup
✅ DNS Leak Test
Connect to
VPN-WiFi, visit https://dnsleaktest.comYou should only see ProtonVPN DNS (e.g., 185.x.x.x in Netherlands).
✅ Kill Switch Test
Temporarily bring down the VPN:
ifdown wg0Clients on
VPN-WiFishould lose all internet access.
🔒 Result
You now have a secure, VPN-only wireless access point. All connected devices are:
- Protected via ProtonVPN (WireGuard)
- Free of DNS leaks
- Kill-switched from WAN fallback
Enjoy secure browsing!
-
Network Drive Status CSC CACHE
📁 Désactivation des fichiers hors connexion dans le Centre de synchronisation pour résoudre les conflits CSC CACHE
📝 Problème
Un lecteur réseau affiche le statut
CSC CACHE, ce qui indique que les fichiers hors connexion sont activés via le Centre de synchronisation. Cela entraîne les problèmes suivants :- Dossiers incomplets ou contenus manquants.
- Conflits de fichiers ou fichiers obsolètes.
- Impossibilité d’accéder à certains fichiers présents sur le serveur.
✅ Objectif
Désactiver les fichiers hors connexion pour que le lecteur réseau reflète le contenu en temps réel sans mise en cache ni conflit.
🔧 Étapes à suivre
Étape 1 : Vérifier le statut dans le Centre de synchronisation
- Ouvrir le Panneau de configuration.
- Rechercher et ouvrir le Centre de synchronisation.
- Dans le menu de gauche, cliquer sur Gérer les fichiers hors connexion.
- Vérifier le statut :
- Si le message indique que les fichiers hors connexion sont activés, passer à l’étape suivante.
Étape 2 : Désactiver les fichiers hors connexion
- Dans la fenêtre Fichiers hors connexion, cliquer sur Désactiver les fichiers hors connexion.
- Cliquer sur OK.
- Redémarrer l’ordinateur lorsque cela est demandé.
Étape 3 : Vider le cache CSC (optionnel mais recommandé)
Si le lecteur réseau affiche toujours
CSC CACHEou si le problème persiste, il est conseillé de vider le cache des fichiers hors connexion.⚠️ Attention : cette opération supprimera tous les fichiers en cache. S’assurer que toutes les modifications ont été synchronisées et sauvegardées.
a. Modifier le registre pour vider le cache au redémarrage
- Appuyer sur
Windows + R, taperregedit, puis appuyer sur Entrée. -
Naviguer jusqu’à la clé suivante :
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CSC - Créer ou modifier la valeur DWORD suivante :
-
Nom :
FormatDatabase -
Valeur :
1
-
Nom :
- Fermer l’Éditeur du Registre et redémarrer l’ordinateur.
Étape 4 : Vérifier l’accès au lecteur réseau
Après redémarrage :
- Ouvrir l’Explorateur de fichiers.
- Accéder au lecteur réseau concerné.
- Vérifier que :
- Le lecteur n’indique plus
CSC CACHE. - Tous les fichiers et dossiers sont visibles et à jour.
- Le lecteur n’indique plus
-
Update Portainer on Ubuntu with Docker
Update Portainer on Ubuntu with Docker
This script stops and removes the existing Portainer container, pulls the latest image, and restarts Portainer using Docker.
Script:
update_portainer.sh#!/bin/bash # Update Portainer using Docker echo "Stopping existing Portainer container..." docker stop portainer echo "Removing existing Portainer container..." docker rm portainer echo "Pulling the latest Portainer image..." docker pull portainer/portainer-ce:lts echo "Restarting Portainer with the latest image..." docker run -d \ -p 8000:8000 \ -p 9443:9443 \ --name portainer \ --restart=always \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer_data:/data \ portainer/portainer-ce:lts echo "Portainer update complete."Make the Script Executable
chmod +x update_portainer.shRun the Script
./update_portainer.sh⚠️ Adjust container name, ports, or volume paths if your setup differs.
-
Change the Metrics of the Network adapater
Powershell script to change the metrics of the network adapter
Some software, such as Siemens, installs virtual network adapters. By default, Windows automatically manages network metrics, which can cause delays during startup. In some cases, the system attempts to obtain a network address for the virtual adapter before the physical one, significantly delaying network availability for users. To address this issue, the following script manually sets the network metric values to prioritize the physical adapter.
# Following pre requisites are required: # The network cards should be named properly # Here in my scenario I have named the physical cards as Pedagogie which I could address it in the script. # Define the remote computer name $remotePC = "PC 01" # Define the adapter names and desired metric values $pedagogieAdapterName = "Pedagogie" $siemensAdapterName = "Ethernet 2" $pedagogieMetricValue = 1 $siemensMetricValue = 100 # Execute the following block remotely Invoke-Command -ComputerName $remotePC -ScriptBlock { param($pedagogieAdapterName, $siemensAdapterName, $pedagogieMetricValue, $siemensMetricValue) # Get the network adapter interfaces $pedagogieInterface = Get-NetAdapter -Name $pedagogieAdapterName -ErrorAction Stop $siemensInterface = Get-NetAdapter -Name $siemensAdapterName -ErrorAction Stop # Set the new metric values Set-NetIPInterface -InterfaceIndex $pedagogieInterface.ifIndex -InterfaceMetric $pedagogieMetricValue Set-NetIPInterface -InterfaceIndex $siemensInterface.ifIndex -InterfaceMetric $siemensMetricValue Write-Output "$comp : Metric for adapter '$pedagogieAdapterName' set to $pedagogieMetricValue." Write-Output "$comp : Metric for adapter '$siemensAdapterName' set to $siemensMetricValue." } -ArgumentList $pedagogieAdapterName, $siemensAdapterName, $pedagogieMetricValue, $siemensMetricValue -
CRX-10 app not finding the Robot
CRX-10 app not finding the Robot
-
Freemobile SMS API template
```bash
SMS API credentials
SMS_USER=”USERNAME” SMS_PASS=”APIKEY” SMS_MESSAGE=”Pi-hole Gravity Update Completed Successfully.”
-
MailMerge avec ThunderBird
MailMerge avec Thunderbird
-
Nvidia Driver install
Documentation: Clean Nvidia Driver Installation using DDU and NVCleanInstall
-
Chocolatey package automation via github workflow
Chocolatey Package Deployment Workflow
-
Deploy the new Microsoft Teams client
Source: link
-
Copy a desired profile to default profile
Use defprof to copy the desired profile
-
Unable to update windows due to SSL error
-
Recover data using Duplicati
Points to remember while recovering using Duplicati.
-
Mounting a CIFS Share in an Unprivileged LXC Container Running Docker
This guide details how to mount a CIFS (SMB) share within an unprivileged LXC container running on Proxmox and make it accessible to a Docker container.
-
Metabase upgrade or migration to new server
Before upgrading the system, always take a backup of the database to prevent data loss.
-
Glpi-Agent
Ref: Doc
-
Microsoft 365 Apps activation error- Trusted Platform Module malfunctioned
When a motherboard has been replaced, Teams may not work and could give you an error stating “Trusted Platform Module malfunctioned”
-
File Association
Steps to Automate Setting File Associations via Group Policy Using Command Line
-
Powershell script to list the Members in the Administrateur Group
```powershell
Define the list of computers
$computers = @(“PC1”, “PC2”, “PC3”) # Replace with your actual computer names
-
Bitlocker key
suite à une maj de juillet certains ordis peuvent demander la clef bitlocker au démarrage Voici comment retrouver une clef bitlocker à partir de l’ID dans l’AD:
-
Fog Client unable to install CA certificate Error
During install of Fog client you might encounter the following error:
-
Topsolid Chocolatey Package Description
Topsolid License and update configuration:
-
Registering windows apps screen appears during logon
Registering windows apps screen appears during logon
-
Snippet for firewall rule in choco package
Snippet for firewall rule in choco package
-
Slow startup Troubleshooting
Slow start up can be troubleshooted via windows performance analysis tool available in windows ADK
- Download the latest version of Windows ADK and install the windows performance toolkit.
- Link: Télécharger le Windows ADK 10.1.26100.2454 (décembre 2024)
-
Disable automatic updates of Microsoft Store apps
Disable automatic updates of Microsoft Store apps
-
USB Devices Passthrough over IP
USB Devices Passthrough over IP
-
Recovery Partition Parameters
Process to hide and mark a partition as windows recovery partition
-
Kiosk JPO
Kiosk for JPO
-
Powershell Script to Backup Hyperv VM
This powershell Script backups the VM to Synology WORM shared Folder
-
Set Environment varaiable
To apply settings to the current user session
The set commands sets the variable but its not persistant after reboot
-
docker-stack-nexus
version: '3.3' services: nexus1: ports: - '8082:8081' container_name: nexus1 volumes: - chocolatey1-container-volume:/nexus-data - "/etc/timezone:/etc/timezone:ro" - "/etc/localtime:/etc/localtime:ro" image: sonatype/nexus3:latest networks: - network1 nexus2: ports: - '8083:8081' container_name: nexus2 volumes: - chocolatey2-container-volume:/nexus-data - "/etc/timezone:/etc/timezone:ro" - "/etc/localtime:/etc/localtime:ro" image: sonatype/nexus3:latest networks: - network1 networks: network1: name: nginx-proxy-manager_default external: true volumes: chocolatey1-container-volume: external: true name: chocolatey1-container-volume chocolatey2-container-volume: external: true name: chocolatey2-container-volume -
Flexlm command line switches
License Administration Tools
-
HyperV isolated network with internet access
HyperV isolated network with internet access
-
Container-time-and-date-settings
Container Time and date for logs
-
Convert from Dynamic VHD/VHDX Disk Format to / from Fixed in Hyper-V
Convert from Dynamic VHD/VHDX Disk Format to / from Fixed in Hyper-V
-
Update portainer
To update to the latest version of Portainer Server, use the following commands to stop then remove the old version. Your other applications/containers will not be removed.
-
SSH configuration
SSH
-
Insight Block Application and Internet
Insight Block Application and Internet
-
MarkDown Diagrams and Charts
Source: (Hedgedoc.org)[https://demo.hedgedoc.org/features?both]
Diagrams
-
Add DNS Entry To Hosts File Using The Command Prompt
How To Add DNS Entry To Hosts File Using The Command Prompt
-
Compare Dell Bios parameters between Pcs
This Powershell script compares the parameters of Bios between Pcs
-
Chocolatey Install script snippets
When you need to check if a file exists in the network share before proceeding with the install use Get-Item command.
The Get-Item -path \Network-share\file.txt checks if the file is in the path, if not it raises an error that stops the chocolatey install script because of the erroractionpreferrence set at the begining of the sript.
-
Chocolatey Uninstall MSI using uninstall string
To uninstall a package which uses MSI filetype.
-
3Dexperince how to update Hotfix.
How to patch the hotfix in 3Dexperience
-
TemperMonkey Script to paste Front Matter Template
The script loads the Frontmatter template to the clipboard
After creating new post just CTRL + V to paste the FrontMatter
-
Tia-portal-Please-reboot-before-installing-any-more-programs
Message “Please reboot before installing any more programs”
-
Determine if MSI/EXE supports certain flag/argument
Determine if MSI/EXE supports certain flag/argument.
-
2023-05-10-GLPI_sql_query_for_metabase
SELECT glpi_computers.name, glpi_infocoms.warranty_date, TIMESTAMPDIFF(YEAR, glpi_infocoms.warranty_date, CURDATE()) AS Age, MID(glpi_computers.name,8,1) AS Batiment, MID(glpi_computers.name,9,4) AS Salle FROM
glpi_computersLEFT JOIN glpi_infocoms ON glpi_computers.id = glpi_infocoms.items_id WHERE ((glpi_computers.name LIKE ‘028F2%’) AND (glpi_computers.states_id=2)) ORDER BY glpi_computers.name ASC LIMIT 2; -
How_to_Reset_MySQL_Master-Slave_Replication
How to Reset ( Re-Sync ) MySQL Master-Slave Replication
Source: https://tecadmin.net/reset-re-sync-mysql-master-slave-replication/
-
AutoIT Encrypt Script
AutoIT Encrypt Script
-
Set DHCP to static for PC in Base-IP
After importing CSV the DHCP type reflects as Regular to change the values to DHCP Static.
-
Windows_UI_Automation_with_Sikulix
Windows UI Automation with Sikulix
-
Insight Frequently used commands
Insight Freequently used commands
-
Chocolatey Softwares install commands
Chocolatey Interne software install commands
-
Fog Secure Boot issue
Fog Secure Boot issue
-
Modify BIOS settings via dell command configure winPE ISO
Modify BIOS settings via dell command configure via winPE
-
Deploy Roboguide package
Deploying Roboguide
Autoit script to install Roboguide
-
Insight Tech Console Backup WOL
Insight Tech Console Backup WOL
-
Change Vertical Menu to Horizontal Menu
Change Vertical Menu to Horizontal Menu
-
HP BIOS Settings Management
HP BIOS Settings Management source
-
Factory Reset your Aastra phone
How to factory reset an Aastra 6 series
- Applicable to Aastra 6751i, 6753i, 6755i, 6757i, 6730i, 6731i, 6739i
- Factory Reset your Aastra phone
- If you know the Admin Password
- If you do not know the Admin Password Important: These phones are outdated and no longer supported by 3CX. This guide is available for informational purposes only and will not be updated
-
Reinitialiser le pc aux paramètres dusine
Réinitialiser le pc aux paramètres d’usine
-
Powershell script to check if the software is installed on remote PC
``` function check-software-install() { Param ( [Parameter(Mandatory = $true)] [Array] $computers, [Parameter(Mandatory = $true)] [string] $softwarename )
-
Factory Reset Partition via Clonezilla
AUTOMATED UEFI-WINDOWS RESTORE USING CLONEZILLA
-
PartitionWizard Clean Uninstall
Partition Wizard Clean uninstall
-
Powershell remote install/uninstall softwares
Powershell Function to uninstall msi remotely and get the output status
-
GLPI Upgrade
When you would like to upgrade to the new version of Glpi
-
INITIATE SCCM CLIENT AGENT ACTIONS USING COMMAND LINE
Credits: https://www.manishbangia.com/initiate-sccm-client-actions-cmd-line/
-
Nexus Repository For Chocolatey
Installation:
-
DeepFreeze-Actions
DeepFreeze Actions
-
Powershell - Onliners
To remove appx provisioned package
-
Reinstall and re-register command for built-in Windows 10 apps
Credit : winhelponline
-
Installing Fusioninventory Plugin
Installez et configurez le plugin FusionInventory
-
Test-chocolatey Environment
function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield("_context","nonpublic,instance").getvalue( $executioncontext)).gettype().getfield("_authorizationManager","nonpublic,instance").setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager "Microsoft.PowerShell"))} Disable-ExecutionPolicy Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 Import-Module $env:ChocolateyInstall\helpers\chocolateyInstaller.psm1 -
Select the active network card adapter of the pc
``` Get-WmiObject -Class Win32NetworkAdapterConfiguration -ComputerName $env:COMPUTERNAME | where { $.IpAddress -eq ([System.Net.Dns]::GetHostByName($Inputmachine).AddressList[0]).IpAddressToString }
-
Bypass powershell execution policy
The following script helps to bypass the execution policy in the powershell session
function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield("_context","nonpublic,instance").getvalue( $executioncontext)).gettype().getfield("_authorizationManager","nonpublic,instance").setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager "Microsoft.PowerShell"))} Disable-ExecutionPolicy -
Apply changes to Pc while UWF is enabled
The following script helps to make changes on a PC with UWF Active.
``` Remove-Job * -Force $filter = Read-Host ‘Enter the Filter for PC::’ $comp = Get-ADComputer -SearchBase “ “ -Filter ‘Name -like $filter’|Select-Object -ExpandProperty Name | Out-GridView -PassThru
-
BSOD-INACCESSIBLE_BOOT_DEVICE
Resolve INACCESSIBLE_BOOT_DEVICE Error
Stage 1
- Boot Pc from windows CD -> repair option -> command prompt;
- for Safemode:
bcdedit /set {default} safeboot minimal - for Safe Mode with Networking type in:
bcdedit /set {current} safeboot network - If the pc is able to boot into safe mode, its evident that some driver or thirdparty apps is blocking the windows boot process
- to reset the pc to boot normally :
- use msconfig -> Boot -> disable safeboot
-
Docker-Glpi
Install Docker Engine on Ubuntu
Set up the repository
Update the apt package index and install packages to allow apt to use a repository over HTTPS:
sudo apt-get update sudo apt-get install \ ca-certificates \ curl \ gnupg \ lsb-releaseAdd Docker’s official GPG key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpgUse the following command to set up the stable repository. To add the nightly or test repository, add the word nightly or test (or both) after the word stable in the commands below. Learn about nightly and test channels.
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/nullInstall Docker Engine
Update the apt package index, and install the latest version of Docker Engine and containerd, or go to the next step to install a specific version: ``` sudo apt-get update
-
Glpi-PowerBI-Reporting
- Install Power BI Desktop on a machine
- Download the ODBC connector for MariaDB from the official site (Same principle for MySQL)
- Note- Please install version : mariadb-connector-odbc-3.1.13-win64.msi
-
The latest version: mariadb-connector-odbc-3.1.14-win64.msi has a bug - It crashes the ODBC Data Source utility while connection.
- Installation classic of mariadb-connector-odbc-3.1.13-win64.msi.
- Click the start button and search “ODBC Data Source (64 bit)” and lauch it with admin privilege.
- click add and in the Create New Data Source window select the MariaDB ODBC 3.1 Driver and click on Finish button

- Connection Name : glpi-test
- In the Next window Enter the requested information (in my case)
- Server Name: IP of the server
- Port :3306
- User name
- Password
*
- click on the button TestDSN ,If the test is successful, a message tells you so and you can choose your database -> choose Glpi from the dropdown.
- Open the PowerBI file and click on the refresh button on the ribbon menu, a window pops up and demands for the database password. Please fill in the respective info and thats-it.
- Enjoy!
-
Delete_device_and_driver_windows10
Delete device and driver

