diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index f1e816f..0000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Deploy project website - -on: - push: - branches: - - main - paths: - - 'site/**' - - '.github/workflows/pages.yml' - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: false - -jobs: - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 - - - name: Configure Pages - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6 - - - name: Upload site - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 - with: - path: site - - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5 diff --git a/.github/workflows/powershell-validation.yml b/.github/workflows/powershell-validation.yml index 97bae83..c13414f 100644 --- a/.github/workflows/powershell-validation.yml +++ b/.github/workflows/powershell-validation.yml @@ -161,14 +161,6 @@ jobs: shell: powershell run: .\tests\startup-inventory.tests.ps1 - - name: Validate static site SEO with PowerShell 7 - shell: pwsh - run: .\tests\site-seo.tests.ps1 - - - name: Validate static site SEO with Windows PowerShell 5.1 - shell: powershell - run: .\tests\site-seo.tests.ps1 - - name: Test bootstrap synchronization with PowerShell 7 shell: pwsh run: .\tests\bootstrap-sync.tests.ps1 diff --git a/README.md b/README.md index 08f19a8..4e16370 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![No telemetry](https://img.shields.io/badge/telemetry-none-2ea44f)](SECURITY.md)

- Windows Diagnostics Toolkit interactive Wide dashboard + Windows Diagnostics Toolkit interactive Wide dashboard

**Generate a local Windows support report with diagnostics that are read-only by design and guarded by automated safety checks.** @@ -110,7 +110,7 @@ Remove-Item Env:WDT_TUI_LOGO -ErrorAction SilentlyContinue After collection completes, the TUI shows the elapsed time, report paths, and the number of `WARN` and `ERROR` findings. `Enter` returns to the menu and `Esc` exits.

- Windows Diagnostics Toolkit completed diagnostics screen + Windows Diagnostics Toolkit completed diagnostics screen

A `WARN` means the toolkit found a condition worth reviewing. It does not mean the collection failed. A non-zero module exit code is reported separately as an execution failure. @@ -266,7 +266,7 @@ The GitHub Actions workflow runs validation, dependency-free tests, and a report - [Detailed usage](docs/usage.md) - [Built-in module authoring](docs/module-authoring.md) - [Anonymized TXT and Markdown report](docs/report-example.md) -- [Project website and troubleshooting cases](site/index.html) +- [Project website and troubleshooting cases](https://wdt.digital/) - [Contributing](CONTRIBUTING.md) - [Security policy](SECURITY.md) diff --git a/docs/seo-launch-checklist.md b/docs/seo-launch-checklist.md index 1105927..ddbefae 100644 --- a/docs/seo-launch-checklist.md +++ b/docs/seo-launch-checklist.md @@ -1,6 +1,7 @@ # SEO launch checklist -This checklist covers the manual steps that cannot be completed from the static-site source alone. +The canonical static-site source and deployment workflow live in +[`0x0bug/wdt-site`](https://github.com/0x0bug/wdt-site). This checklist covers the manual steps that cannot be completed from that source alone. ## Published URLs @@ -11,16 +12,16 @@ This checklist covers the manual steps that cannot be completed from the static- ## After merging a site change -1. Confirm the GitHub Pages workflow completes successfully. +1. Confirm the GitHub Pages workflow in `0x0bug/wdt-site` completes successfully. 2. Open the homepage, guide index, one article, `robots.txt`, and `sitemap.xml` in a private browser window. 3. Confirm each page returns the expected content over HTTPS. -4. Check that canonical URLs use the published GitHub Pages domain and repository path. +4. Check that canonical URLs use `https://wdt.digital/`. 5. Check the social card in at least one Open Graph or social-preview debugger. 6. Update sitemap `lastmod` only for pages whose primary content changed. ## Google Search Console -1. Add the GitHub Pages URL as a URL-prefix property. +1. Add `https://wdt.digital/` as a URL-prefix property. 2. Choose an available verification method. 3. Add the provided verification token or verification file in a separate focused pull request. 4. Submit `sitemap.xml` after verification. @@ -69,7 +70,7 @@ tech-support read-only ``` -Upload a raster 1280×640 version of `site/assets/social-preview.svg` as the repository social preview when available. +Upload the raster 1280×640 social preview maintained in `0x0bug/wdt-site` as the repository social preview when available. ## Content maintenance diff --git a/docs/usage.md b/docs/usage.md index 73d478c..7f83f56 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -49,7 +49,7 @@ Run the entry point without module switches: ```

- Windows Diagnostics Toolkit interactive Wide dashboard + Windows Diagnostics Toolkit interactive Wide dashboard

The initial state enables the recommended diagnostics, Privacy Mode, and Markdown export. The interface lets you: @@ -125,7 +125,7 @@ After collection completes, the result screen displays: - controls for returning to the menu or exiting.

- Windows Diagnostics Toolkit result screen after collection + Windows Diagnostics Toolkit result screen after collection

`WARN` represents diagnostic state that deserves review. It does not change the process exit code. A non-zero exit code means a module failed to execute. diff --git a/site/.nojekyll b/site/.nojekyll deleted file mode 100644 index 8b13789..0000000 --- a/site/.nojekyll +++ /dev/null @@ -1 +0,0 @@ - diff --git a/site/404.html b/site/404.html deleted file mode 100644 index c581982..0000000 --- a/site/404.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - Page not found | Windows Diagnostics Toolkit - - - - - - - -
- -
-
-
-
Error 404
-

Page not found

-

The address may be outdated or incomplete. The project homepage and troubleshooting library are still available.

- -
-
- - - diff --git a/site/assets/site.js b/site/assets/site.js deleted file mode 100644 index 2ebbe3e..0000000 --- a/site/assets/site.js +++ /dev/null @@ -1,58 +0,0 @@ -(() => { - const navToggle = document.querySelector('.nav-toggle'); - const navLinks = document.querySelector('#primary-links'); - - if (navToggle && navLinks) { - const closeNavigation = () => { - navLinks.classList.remove('is-open'); - navToggle.setAttribute('aria-expanded', 'false'); - }; - - navToggle.addEventListener('click', () => { - const willOpen = !navLinks.classList.contains('is-open'); - navLinks.classList.toggle('is-open', willOpen); - navToggle.setAttribute('aria-expanded', String(willOpen)); - }); - - navLinks.addEventListener('click', (event) => { - if (event.target instanceof HTMLAnchorElement) { - closeNavigation(); - } - }); - - window.addEventListener('resize', () => { - if (window.innerWidth > 760) { - closeNavigation(); - } - }); - } - - document.querySelectorAll('[data-copy-target]').forEach((button) => { - button.addEventListener('click', async () => { - const targetId = button.getAttribute('data-copy-target'); - const target = targetId ? document.getElementById(targetId) : null; - const text = target?.textContent?.replace(/^PS>\s?/gm, '').trim(); - - if (!text) { - return; - } - - try { - await navigator.clipboard.writeText(text); - const originalLabel = button.textContent; - button.textContent = 'COPIED'; - button.classList.add('is-copied'); - window.setTimeout(() => { - button.textContent = originalLabel; - button.classList.remove('is-copied'); - }, 1800); - } catch { - const range = document.createRange(); - range.selectNodeContents(target); - const selection = window.getSelection(); - selection?.removeAllRanges(); - selection?.addRange(range); - } - }); - }); -})(); diff --git a/site/assets/social-preview.png b/site/assets/social-preview.png deleted file mode 100644 index 545783e..0000000 Binary files a/site/assets/social-preview.png and /dev/null differ diff --git a/site/assets/social-preview.svg b/site/assets/social-preview.svg deleted file mode 100644 index 84df4cd..0000000 --- a/site/assets/social-preview.svg +++ /dev/null @@ -1,37 +0,0 @@ - - Windows Diagnostics Toolkit - Read-only PowerShell diagnostics with a terminal-first interface, local reports and no telemetry. - - - - - - - - WDT / WINDOWS DIAGNOSTICS TOOLKIT - [READY] - - PS C:\WDT> .\Invoke-WindowsDiagnostics.ps1 - See what Windows - is doing. - Change nothing. - Read-only diagnostics · Local reports · No telemetry - - DIAGNOSTICS - 10 / 10 - - - [x]System information - [x]Security posture - [x]Performance snapshot - [x]Network context - [x]Disk health - [x]RUN DIAGNOSTICS - - - [x] READ-ONLY - [x] LOCAL - [x] PRIVATE - github.com/0x0bug/windows-diagnostics-toolkit - MIT - diff --git a/site/assets/style.css b/site/assets/style.css deleted file mode 100644 index 45fdb74..0000000 --- a/site/assets/style.css +++ /dev/null @@ -1 +0,0 @@ -:root{color-scheme:dark;--bg:#000000;--surface:#070909;--surface-2:#0c1010;--surface-3:#111616;--text:#f1f1f1;--muted:#858d8d;--dim:#5d5d5d;--line:#244141;--line-bright:#33e6e6;--accent:#33e6e6;--ok:#25ff45;--warn:#ffff24;--danger:#ff4b5f;--max:1220px;--article:820px;--mono:"Cascadia Code","Cascadia Mono","SFMono-Regular",Consolas,"Liberation Mono",monospace}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;min-width:0;background:var(--bg);color:var(--text);font:16px/1.65 var(--mono);text-rendering:optimizeLegibility}body::before{content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;background-image:linear-gradient(rgba(51,230,230,0.025) 1px,transparent 1px),linear-gradient(90deg,rgba(51,230,230,0.018) 1px,transparent 1px);background-size:40px 40px;mask-image:linear-gradient(to bottom,rgba(0,0,0,0.85),transparent 75%)}body::after{content:"";position:fixed;inset:0;z-index:1000;pointer-events:none;opacity:0.12;background:repeating-linear-gradient(to bottom,transparent 0,transparent 3px,rgba(255,255,255,0.025) 4px)}::selection{color:#000;background:var(--accent)}img{max-width:100%}a{color:var(--accent)}a:hover{color:#9cffff}a:focus-visible,button:focus-visible,summary:focus-visible{outline:2px solid var(--warn);outline-offset:4px}button,a{-webkit-tap-highlight-color:transparent}button{font:inherit}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.skip-link{position:fixed;left:1rem;top:-5rem;z-index:1100;background:var(--warn);color:#000;padding:0.7rem 1rem;font-weight:800;text-decoration:none}.skip-link:focus{top:1rem}.wrap{width:min(calc(100% - 2rem),var(--max));margin-inline:auto}.ok{color:var(--ok)}.warn{color:var(--warn)}.danger{color:var(--danger)}.site-header,header{position:sticky;top:0;z-index:100;border-bottom:1px solid var(--line-bright);background:rgba(0,0,0,0.94);backdrop-filter:blur(12px)}nav,.terminal-nav{min-height:72px;display:flex;align-items:center;justify-content:space-between;gap:1.5rem}.brand{display:inline-flex;align-items:center;gap:0.8rem;color:var(--text);text-decoration:none;min-width:0}.brand-mark{display:inline-grid;place-items:center;min-width:3.25rem;height:2rem;padding-inline:0.45rem;border:1px solid var(--accent);color:var(--accent);font-weight:900;letter-spacing:0.08em}.brand-copy{display:grid;line-height:1.2}.brand-copy strong{font-size:0.88rem}.brand-copy small{color:var(--dim);font-size:0.66rem;letter-spacing:0.08em}.nav-links{display:flex;align-items:center;gap:0.35rem;flex-wrap:wrap}.nav-links a{color:var(--muted);text-decoration:none;padding:0.45rem 0.65rem;font-size:0.8rem;text-transform:uppercase;letter-spacing:0.06em}.nav-links a::before{content:"[";color:var(--dim)}.nav-links a::after{content:"]";color:var(--dim)}.nav-links a:hover{color:var(--text)}.nav-links a:hover::before,.nav-links a:hover::after{color:var(--accent)}.nav-links .nav-github{color:var(--accent)}.nav-toggle{display:none;border:1px solid var(--line-bright);background:#000;color:var(--accent);min-width:44px;min-height:44px;cursor:pointer}.hero{min-height:calc(100vh - 72px);display:grid;grid-template-columns:minmax(0,0.82fr) minmax(620px,1.35fr);align-items:center;gap:clamp(2rem,4vw,4.5rem);padding-block:clamp(4rem,8vw,7.5rem)}.hero-copy{min-width:0}.prompt-line{color:var(--dim);margin-bottom:1.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:0.82rem}.prompt-user{color:var(--accent)}.prompt-command{color:var(--text)}.terminal-cursor{display:inline-block;width:0.58em;height:1.05em;margin-left:0.22em;vertical-align:-0.16em;background:var(--accent);animation:cursor-blink 1.05s steps(1,end) infinite}@keyframes cursor-blink{50%{opacity:0}}.eyebrow,.section-index{margin:0 0 0.8rem;color:var(--accent);font-size:0.76rem;font-weight:800;letter-spacing:0.12em;text-transform:uppercase}.status-dot{display:inline-block;width:0.55rem;height:0.55rem;margin-right:0.55rem;background:var(--ok);box-shadow:0 0 14px rgba(37,255,69,0.85)}h1,h2,h3{color:var(--text);line-height:1.08;text-wrap:balance}h1{margin:0;font-size:clamp(3rem,6.1vw,6.35rem);letter-spacing:-0.075em;font-weight:850}h1 span{color:var(--accent)}h2{margin:0;font-size:clamp(2.25rem,4.5vw,4.8rem);letter-spacing:-0.06em}h3{font-size:1.15rem;letter-spacing:-0.025em}.lead{max-width:62ch;margin:1.6rem 0 0;color:#bbc2c2;font-size:clamp(1rem,1.35vw,1.16rem)}.actions{display:flex;flex-wrap:wrap;gap:0.65rem;margin-top:2rem}.button{display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;min-height:48px;padding:0.7rem 1rem;border:1px solid var(--line-bright);border-radius:0;background:#050707;color:var(--accent);text-decoration:none;font-size:0.8rem;font-weight:800;letter-spacing:0.035em;text-transform:uppercase;transition:color 150ms ease,background-color 150ms ease,transform 150ms ease}.button:hover{transform:translate(-2px,-2px);color:#000;background:var(--accent);box-shadow:4px 4px 0 #174848}.button.primary{border-color:var(--ok);background:var(--ok);color:#001503}.button.primary:hover{background:#7aff8d;box-shadow:4px 4px 0 #0a4d13}.button.ghost{border-color:var(--line);color:var(--muted)}.button.ghost:hover{border-color:var(--muted);background:var(--text);color:#000;box-shadow:4px 4px 0 #4b5151}.hero-status{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0;margin:2.5rem 0 0;border-top:1px solid var(--line);border-left:1px solid var(--line)}.hero-status div{display:grid;grid-template-columns:auto 1fr;gap:0.65rem;padding:0.62rem 0.7rem;border-right:1px solid var(--line);border-bottom:1px solid var(--line);font-size:0.7rem}.hero-status dt{color:var(--dim)}.hero-status dd{margin:0;text-align:right;color:var(--text)}.tui-frame{position:relative;margin:0;border:1px solid var(--line-bright);background:#000;box-shadow:12px 12px 0 rgba(51,230,230,0.08)}.tui-frame::before,.tui-frame::after{content:"";position:absolute;width:12px;height:12px;pointer-events:none}.tui-frame::before{left:-1px;top:-1px;border-left:3px solid var(--accent);border-top:3px solid var(--accent)}.tui-frame::after{right:-1px;bottom:-1px;border-right:3px solid var(--accent);border-bottom:3px solid var(--accent)}.frame-label,.panel-top,.terminal-bar,.console-head,.console-foot,.terminal-footer{display:flex;align-items:center;justify-content:space-between;gap:1rem;color:var(--dim);font-size:0.68rem;letter-spacing:0.08em;text-transform:uppercase}.frame-label{min-height:38px;padding:0 0.85rem;border-bottom:1px solid var(--line)}.frame-state{color:var(--accent)}.tui-frame img{display:block;width:100%;height:auto;background:#000}.tui-frame figcaption{min-height:42px;padding:0.7rem 0.85rem;border-top:1px solid var(--line);color:var(--dim);font-size:0.72rem}.signal-strip{overflow:hidden;border-block:1px solid var(--line-bright);background:var(--accent);color:#001414}.signal-track{display:flex;justify-content:space-between;gap:2rem;padding-block:0.65rem;white-space:nowrap;font-size:0.72rem;font-weight:800;letter-spacing:0.06em}.signal-track strong{color:#003e3e}.section-block{padding-block:clamp(5rem,9vw,9rem)}.section-heading{display:grid;grid-template-columns:minmax(0,1fr) minmax(320px,0.66fr);align-items:end;gap:3rem;padding-bottom:2.5rem;border-bottom:1px solid var(--line-bright)}.section-intro{max-width:68ch;margin:0;color:var(--muted)}.workflow-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));margin-top:2rem;border-left:1px solid var(--line);border-top:1px solid var(--line)}.terminal-panel{min-height:260px;padding:1.25rem;border-right:1px solid var(--line);border-bottom:1px solid var(--line);background:rgba(7,9,9,0.78)}.terminal-panel.is-active{box-shadow:inset 3px 0 0 var(--warn)}.terminal-panel h3{margin:2.5rem 0 1rem}.terminal-panel h3 span{color:var(--warn)}.terminal-panel p{color:var(--muted)}.key-line{display:flex;align-items:center;flex-wrap:wrap;gap:0.42rem;margin-top:1.5rem;font-size:0.7rem}kbd{display:inline-flex;align-items:center;justify-content:center;min-width:2rem;min-height:1.75rem;padding:0.12rem 0.4rem;border:1px solid var(--dim);border-bottom-color:var(--text);background:#090c0c;color:var(--text);font:700 0.72rem/1 var(--mono)}.result-frame{margin-top:2rem}.quick-start{border-block:1px solid var(--line);background:rgba(7,12,12,0.78)}.command-layout,.privacy-layout,.faq-layout{display:grid;grid-template-columns:minmax(0,0.8fr) minmax(520px,1.2fr);align-items:center;gap:clamp(3rem,7vw,7rem)}.plain-status-list{display:grid;gap:0.65rem;margin:2rem 0 0;padding:0;list-style:none;color:var(--muted);font-size:0.82rem}.plain-status-list li{display:flex;align-items:flex-start;gap:0.7rem}.terminal{border:1px solid var(--line-bright);background:#000;box-shadow:10px 10px 0 rgba(51,230,230,0.08)}.terminal-bar{min-height:42px;padding:0 0.85rem;border-bottom:1px solid var(--line)}.copy-button{min-height:30px;padding-inline:0.7rem;border:1px solid var(--line);background:#000;color:var(--accent);cursor:pointer;font-size:0.68rem;font-weight:800;letter-spacing:0.08em}.copy-button:hover{border-color:var(--accent);background:var(--accent);color:#000}.copy-button.is-copied{border-color:var(--ok);color:var(--ok)}pre{margin:1.25rem 0;padding:1.25rem;overflow-x:auto;border:1px solid var(--line);border-radius:0;background:#000;color:var(--text);white-space:pre-wrap;font:0.86rem/1.8 var(--mono)}.command-terminal pre{margin:0;border:0;min-height:210px;display:grid;align-items:center}.ps-prompt{color:var(--accent)}.terminal-footer{min-height:38px;padding:0 0.85rem;border-top:1px solid var(--line)}.diagnostic-console{margin-top:2rem;border:1px solid var(--line-bright);background:#000}.console-head,.console-foot{padding:0.75rem 1rem}.console-head{border-bottom:1px solid var(--line-bright);color:var(--accent)}.console-foot{border-top:1px solid var(--line);flex-wrap:wrap}.diagnostic-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}.diagnostic-row{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:0.85rem;min-height:112px;padding:1rem;border-right:1px solid var(--line);border-bottom:1px solid var(--line);color:var(--text);text-decoration:none;transition:background-color 150ms ease}.diagnostic-row:nth-child(2n){border-right:0}.diagnostic-row:nth-last-child(-n + 2){border-bottom:0}.diagnostic-row:hover{color:var(--text);background:#071111}.diagnostic-row:hover strong{color:var(--warn)}.diagnostic-row:hover .row-arrow{color:var(--warn);transform:translate(2px,-2px)}.diagnostic-row .check{color:var(--ok);font-weight:800}.diagnostic-row strong{display:block;font-size:0.88rem}.diagnostic-row small{display:block;margin-top:0.35rem;color:var(--dim);font-size:0.72rem;line-height:1.45}.row-arrow{color:var(--accent);transition:transform 150ms ease,color 150ms ease}.privacy-section{border-block:1px solid var(--line);background:#050707}.privacy-list li{align-items:flex-start}.privacy-list strong{display:block;color:var(--text)}.privacy-list small{display:block;margin-top:0.2rem;color:var(--dim);line-height:1.45}.redaction-panel{border:1px solid var(--line-bright);background:#000}.redaction-panel .panel-top{min-height:42px;padding:0 0.85rem;border-bottom:1px solid var(--line)}.redaction-panel pre{margin:0;border:0;font-size:0.92rem;line-height:2}.redaction-panel pre span{color:var(--ok)}.redaction-panel>p{margin:0;padding:0.85rem 1rem;border-top:1px solid var(--line);color:var(--dim);font-size:0.72rem}.guide-list{margin-top:2rem;border-top:1px solid var(--line-bright)}.guide-list a{display:grid;grid-template-columns:56px minmax(240px,1fr) minmax(240px,0.9fr) auto;align-items:center;gap:1rem;min-height:82px;padding:0.8rem 0;border-bottom:1px solid var(--line);color:var(--text);text-decoration:none}.guide-list a>span{color:var(--dim)}.guide-list a strong{font-size:0.9rem}.guide-list a small{color:var(--dim)}.guide-list a b{color:var(--accent);font-size:0.72rem}.guide-list a:hover{color:var(--text);background:linear-gradient(90deg,rgba(51,230,230,0.06),transparent)}.guide-list a:hover strong{color:var(--warn)}.faq-layout{align-items:start}.faq{border-top:1px solid var(--line-bright)}.faq details{border-bottom:1px solid var(--line)}.faq summary{display:flex;align-items:center;justify-content:space-between;gap:1rem;padding:1.2rem 0;cursor:pointer;color:var(--text);list-style:none;font-weight:750}.faq summary::-webkit-details-marker{display:none}.faq summary b{color:var(--accent);font-size:1.2rem;font-weight:400;transition:transform 150ms ease}.faq details[open] summary b{transform:rotate(45deg);color:var(--warn)}.faq details[open] summary span{color:var(--warn)}.faq p{margin:0;padding:0 3rem 1.4rem 0;color:var(--muted)}.final-cta{padding-block:2rem 7rem}.final-console{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:2rem;padding:clamp(1.5rem,4vw,3rem);border:1px solid var(--line-bright);box-shadow:12px 12px 0 rgba(51,230,230,0.08)}.final-console h2{font-size:clamp(2rem,4vw,4rem)}.final-console p:not(.section-index){color:var(--muted)}footer{border-top:1px solid var(--line-bright);padding:1.5rem 0 3rem;color:var(--dim);font-size:0.72rem}.footer-grid{display:flex;justify-content:space-between;gap:2rem}.footer-grid div:first-child{display:grid}.footer-grid strong{color:var(--text)}.breadcrumb{display:flex;flex-wrap:wrap;gap:0.45rem;margin:2rem auto 0;color:var(--dim);font-size:0.78rem}.breadcrumb a{color:var(--muted)}.article{width:min(calc(100% - 2rem),var(--article));margin-inline:auto;padding:3.5rem 0 6rem}.article.breadcrumb{padding:0}.article h1{font-size:clamp(2.5rem,6vw,4.6rem);overflow-wrap:anywhere}.article h2{margin-top:3.5rem;font-size:clamp(1.7rem,4vw,2.45rem)}.article h3{margin-top:2.25rem}.article p,.article li{max-width:75ch}.article ul,.article ol{padding-left:1.35rem}.article li + li{margin-top:0.5rem}.article-meta{color:var(--dim);font-size:0.78rem}.article .lead{color:#aeb7b7}.article a{text-underline-offset:0.2em}.article-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0;margin:2rem 0;border-left:1px solid var(--line);border-top:1px solid var(--line)}.card{border-right:1px solid var(--line);border-bottom:1px solid var(--line);background:#050707;padding:1.25rem}.card h2,.card h3{margin:0 0 0.75rem;font-size:1.15rem}.card p{color:var(--muted)}.card a{font-weight:700}.toc{border:1px solid var(--line-bright);background:#030505;padding:1.1rem 1.25rem;margin:2rem 0}.toc strong{display:block;margin-bottom:0.45rem;color:var(--accent)}.toc ul{margin:0}.checklist{padding-left:0 !important;list-style:none}.checklist li{position:relative;padding-left:2rem}.checklist li::before{content:"[x]";position:absolute;left:0;color:var(--ok);font-weight:800}.callout{border-left:3px solid var(--warn);border-block:1px solid #464600;border-right:1px solid #464600;background:rgba(255,255,36,0.035);padding:1rem 1.15rem;margin:1.5rem 0}.related-links{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));border-left:1px solid var(--line);border-top:1px solid var(--line);margin-top:1.25rem}.related-links a{display:block;border-right:1px solid var(--line);border-bottom:1px solid var(--line);background:#050707;padding:0.9rem 1rem;text-decoration:none;font-weight:700}.kicker{font-size:0.78rem;color:var(--dim)}@media (max-width:1180px){.hero{grid-template-columns:1fr;min-height:auto}.hero-copy{max-width:850px}.hero-tui{max-width:100%}.signal-track{justify-content:flex-start;overflow-x:auto;scrollbar-width:none}.signal-track::-webkit-scrollbar{display:none}}@media (max-width:920px){.section-heading,.command-layout,.privacy-layout,.faq-layout,.final-console{grid-template-columns:1fr}.workflow-grid{grid-template-columns:1fr}.terminal-panel{min-height:0}.diagnostic-grid{grid-template-columns:1fr}.diagnostic-row,.diagnostic-row:nth-child(2n),.diagnostic-row:nth-last-child(-n + 2){border-right:0;border-bottom:1px solid var(--line)}.diagnostic-row:last-child{border-bottom:0}.guide-list a{grid-template-columns:42px minmax(0,1fr) auto}.guide-list a small{grid-column:2 / 4;margin-top:-0.8rem}}@media (max-width:760px){body::after{display:none}.signal-track{flex-wrap:wrap;justify-content:flex-start;gap:0.45rem 1.25rem;overflow-x:visible;white-space:normal}nav,.terminal-nav{min-height:64px}.brand-copy small{display:none}.nav-toggle{display:inline-grid;place-items:center}.nav-links{position:absolute;top:64px;left:0;right:0;display:none;align-items:stretch;padding:0.75rem 1rem 1rem;border-bottom:1px solid var(--line-bright);background:#000}.nav-links.is-open{display:grid}.nav-links a{min-height:44px;display:flex;align-items:center}.hero{padding-block:3.5rem 5rem}.prompt-line{margin-bottom:1.2rem}.hero-status{grid-template-columns:1fr}.section-block{padding-block:5rem}.section-heading{gap:1.5rem}.command-layout,.privacy-layout{gap:3rem}.footer-grid{flex-direction:column}.article-grid,.related-links{grid-template-columns:1fr}}@media (max-width:560px){.wrap{width:min(calc(100% - 1.25rem),var(--max))}h1{font-size:clamp(2.65rem,14vw,4.25rem);overflow-wrap:anywhere}h2{font-size:clamp(2rem,11vw,3.25rem)}.hero-actions .button{flex:1 1 100%}.hero-status div{font-size:0.66rem}.frame-label span:first-child{max-width:70%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tui-frame figcaption{font-size:0.64rem}.diagnostic-row{min-height:100px}.guide-list a{grid-template-columns:32px minmax(0,1fr)}.guide-list a b{display:none}.guide-list a small{grid-column:2;margin-top:-0.65rem}.final-console{padding:1.25rem}.final-console .actions .button{flex:1 1 100%}}@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*::before,*::after{animation-duration:0.01ms !important;animation-iteration-count:1 !important;transition-duration:0.01ms !important}} \ No newline at end of file diff --git a/site/assets/tui-result-real.png b/site/assets/tui-result-real.png deleted file mode 100644 index c70d486..0000000 Binary files a/site/assets/tui-result-real.png and /dev/null differ diff --git a/site/assets/tui-wide-real.png b/site/assets/tui-wide-real.png deleted file mode 100644 index f078622..0000000 Binary files a/site/assets/tui-wide-real.png and /dev/null differ diff --git a/site/cases/index.html b/site/cases/index.html deleted file mode 100644 index 5dec4d3..0000000 --- a/site/cases/index.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - Windows Troubleshooting Guides and Diagnostic Checks | WDT - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- -
-
Windows troubleshooting library
-

Windows diagnostic and troubleshooting guides

-

Start from the symptom, run the relevant read-only PowerShell module and use the generated evidence to narrow the next step. The toolkit reports state; it does not automatically repair or reconfigure Windows.

- -
-

Collect a diagnostic report for tech support

Create a local TXT and Markdown support report, redact common identifiers with Privacy Mode and review the result before sharing.

Open the support report guide
-

Windows application keeps crashing

Use Application Error, Windows Error Reporting, hang, BugCheck and recent dump metadata to confirm repeated failures.

Open the application crash guide
-

Windows is using too much memory

Compare available physical memory, pagefile usage and top processes by working set without installing a monitoring agent.

Open the high memory guide
-

Windows Update may need a restart

Inspect installed updates, reboot indicators and Windows Update related services without starting a scan or installing anything.

Open the pending reboot guide
-

Disk space is low or disk health is unclear

List physical disk health and volume free-space percentages with a configurable low-space warning threshold.

Open the disk health guide
-

Windows Firewall profiles are disabled

Check Domain, Private and Public firewall state together with Defender, Secure Boot, TPM and BitLocker context.

Open the Firewall guide
-

Windows Time service is stopped

Inspect W32Time state, start mode, timezone, clock values, configured source, verbose status and optional events.

Open the time synchronization guide
-

Unexpected default route or proxy

Compare route metrics, gateways, adapters, DNS, DHCP and WinINET or WinHTTP proxy context without changing the network stack.

Open the network route guide
-
- -
Privacy reminder: use the combined runner with -PrivacyMode before attaching a report to a public issue, forum or support request. Always inspect the final file manually.
-
-
- - - diff --git a/site/cases/windows-application-crashes.html b/site/cases/windows-application-crashes.html deleted file mode 100644 index 0d629bf..0000000 --- a/site/cases/windows-application-crashes.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - Find Windows Application Crash Logs with PowerShell | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesApplication crash logs
-
-
Windows crash troubleshooting
-

Find Windows application crash logs with PowerShell

-

Create a local report of recent application crashes, hangs, system BugCheck events and dump-file metadata without opening dump contents or changing Windows Error Reporting.

- - - - -

Run the crash and hang diagnostic

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -Crashes -PrivacyMode -ExportMarkdown
-

The command creates a local TXT report and an optional Markdown report. -PrivacyMode replaces common identifying values in the combined report, but the final file should still be reviewed before it is posted publicly.

- -

When this guide is useful

- - -

What the report checks

-
-

Application events

Recent Application Error, Windows Error Reporting and Application Hang metadata from the Windows Event Log.

-

System failures

Recent BugCheck events that may indicate a blue-screen or system-level crash rather than an application-only failure.

-

Dump metadata

File name, timestamp, size and source for recent Minidump, MEMORY.DMP and user CrashDumps files.

-

Findings summary

An aggregated WARN or ERROR item appears before the detailed event list when relevant evidence is found.

-
- -

How to interpret Windows crash evidence

-

Look for repetition

-

Repeated application names, providers, event IDs and similar timestamps are more useful than a single isolated event. A cluster of failures from the same program can support an application-specific or vendor-specific investigation.

-

Separate application failures from system crashes

-

An Application Error or Application Hang event does not mean Windows itself crashed. A BugCheck event or fresh system dump is stronger evidence of a system-level failure and should be investigated separately.

-

Compare event time with the user-visible symptom

-

Match the report timestamps to the time the application closed or froze. Old events may be unrelated to the current problem, especially on systems with a long Event Log history.

-

Treat dump files as evidence, not a diagnosis

-

The toolkit lists dump metadata but never opens or analyzes the dump. Debugging a dump requires a separate debugger and suitable symbols.

- -
Privacy note: application names and dump-file names remain visible because they are diagnostically useful. Remove anything sensitive before sharing the report.
- -

What the toolkit does not do

- - -

Reasonable next steps

-
    -
  1. Save the redacted report with the exact time and steps that reproduced the crash.
  2. -
  3. Check whether the failure affects one application, several applications or the whole system.
  4. -
  5. Attach only the relevant report excerpt to a vendor or project issue.
  6. -
  7. For a BugCheck, preserve the dump and investigate it with an appropriate debugger or qualified support channel.
  8. -
- -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/cases/windows-diagnostic-report-for-tech-support.html b/site/cases/windows-diagnostic-report-for-tech-support.html deleted file mode 100644 index fc6ef28..0000000 --- a/site/cases/windows-diagnostic-report-for-tech-support.html +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - Create a Windows Diagnostic Report for Tech Support | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesDiagnostic report for tech support
-
-
Windows technical support
-

Create a Windows diagnostic report for tech support

-

Generate one local TXT and Markdown report for Windows 10 or Windows 11, review the findings summary and share only the redacted evidence relevant to the support request.

- - - - -

1. Create the combined Windows report

-

Open PowerShell in the toolkit directory and run:

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -All -PrivacyMode -ExportMarkdown
-

For Windows PowerShell 5.1:

-
powershell.exe -NoProfile -ExecutionPolicy Bypass `
-  -File .\Invoke-WindowsDiagnostics.ps1 `
-  -All -PrivacyMode -ExportMarkdown
-

The one-run execution-policy bypass applies only to that PowerShell process. It does not change the machine-wide execution policy.

- -

2. Understand what the report contains

-
-

Findings summary

Actionable conditions are grouped as OK, WARN or ERROR before the detailed diagnostic output.

-

System and performance

Windows build, hardware summary, uptime, memory, CPU snapshot, pagefile and top processes.

-

Security and updates

Defender, Firewall, Secure Boot, TPM, BitLocker, installed updates and reboot indicators.

-

Network and time

Adapters, DNS, DHCP, routes, proxy state, reachability, W32Time source and synchronization context.

-

Disk, services and tasks

Physical disk health, free space, automatic services not running and optional startup or scheduled-task context.

-

Crashes and Event Logs

Recent application failures, hangs, BugCheck events, dump metadata and critical or error events.

-
- -

3. Read the report efficiently

-
    -
  1. Start with the overall status and findings summary.
  2. -
  3. Match each warning or error to the symptom and time when the problem occurred.
  4. -
  5. Open only the detailed module sections connected to the support request.
  6. -
  7. Do not treat every WARN item as a required fix; some states are expected on particular systems.
  8. -
- -

4. Review privacy before sharing

-

Privacy Mode replaces common computer names, user names, profile paths, IP and MAC addresses, SIDs, GUIDs and device identifiers with stable per-report tokens. It is intended to make a support report safer to share, not to guarantee complete anonymity.

- -
Never post an unreviewed diagnostic report publicly. Standalone module output is raw; Privacy Mode applies to combined reports produced by Invoke-WindowsDiagnostics.ps1.
- -

5. Attach useful evidence to the support request

-

A useful report is accompanied by a short description of the symptom. Include:

- - -

What the toolkit does not do

- - -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/cases/windows-disk-health-low-space.html b/site/cases/windows-disk-health-low-space.html deleted file mode 100644 index c110904..0000000 --- a/site/cases/windows-disk-health-low-space.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - Check Windows Disk Health and Low Disk Space | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesDisk health and low space
-
-
Windows storage troubleshooting
-

Check Windows disk health and low disk space

-

List physical disk model, media type, health status and size together with volume capacity and free-space percentage without deleting files or changing storage configuration.

- - - - -

Run the disk health diagnostic

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -Disk -PrivacyMode -ExportMarkdown
-

To change the low-free-space threshold in the standalone module:

-
pwsh -NoProfile -File .\scripts\disk-health.ps1 `
-  -LowFreeSpacePercent 20
- -

When this guide is useful

- - -

What the storage report checks

-
-

Physical disks

Disk model, reported media type, operational health and total size from built-in Windows storage sources.

-

Volumes

Drive letter or mount context, file system, total capacity, free space and free-space percentage.

-

Low-space findings

A warning is generated when a volume falls below the configured threshold, which is 15% by default.

-

Unavailable sources

Permission or platform limitations are reported instead of silently presenting missing storage data as healthy.

-
- -

How to interpret disk health and free space

-

Low free space can affect unrelated-looking tasks

-

Windows Update, browser caches, application installers, paging and crash-dump creation all need storage. A nearly full system volume can therefore contribute to failures outside a file-management workflow.

-

The percentage and absolute space both matter

-

Fifteen percent of a small drive and fifteen percent of a multi-terabyte drive represent very different absolute capacities. Use the warning as a prompt to review both values and the workload.

-

A healthy status is not a complete hardware test

-

The report shows health values exposed by Windows. It does not perform a surface scan, read vendor SMART attributes directly or stress the device. A disk can still require vendor diagnostics when symptoms persist.

-

Map volumes to the correct physical device

-

Systems with multiple disks, Storage Spaces, virtual disks or removable media can have a more complex relationship between physical devices and drive letters. Preserve the full report for context.

- -
Read-only boundary: the toolkit does not delete files, clean temporary data, run CHKDSK, repair file systems, initialize disks or change partitions.
- -

What the toolkit does not do

- - -

Reasonable next steps

-
    -
  1. Record the free-space values before deleting or moving data.
  2. -
  3. Confirm which volume contains Windows, applications, pagefiles and user data.
  4. -
  5. Use built-in or vendor-supported tools for deeper hardware testing when health warnings or I/O errors persist.
  6. -
  7. Back up important data before attempting repair or partition changes.
  8. -
- -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/cases/windows-firewall-disabled.html b/site/cases/windows-firewall-disabled.html deleted file mode 100644 index 5beceed..0000000 --- a/site/cases/windows-firewall-disabled.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - Check Disabled Windows Firewall Profiles with PowerShell | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesWindows Firewall profiles
-
-
Windows security troubleshooting
-

Check disabled Windows Firewall profiles with PowerShell

-

Collect a local security posture snapshot for Domain, Private and Public Firewall profiles together with Microsoft Defender, Secure Boot, TPM and BitLocker state.

- - - - -

Run the security posture diagnostic

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -Security -PrivacyMode -ExportMarkdown
-

The combined runner writes a local report and places security findings near the top. Privacy Mode redacts common identifying values, but the report should still be checked manually before sharing.

- -

When this guide is useful

- - -

What the security report checks

-
-

Windows Firewall profiles

Enabled state for Domain, Private and Public profiles, reported separately so one disabled profile is not confused with all profiles being disabled.

-

Microsoft Defender

Available component and protection status from built-in read-only Windows sources.

-

Secure Boot and TPM

Availability of Secure Boot and Trusted Platform Module information where Windows exposes it to the current user.

-

BitLocker status

Volume protection state without reading recovery keys, key protectors or other secret material.

-
- -

How to interpret a disabled Firewall profile

-

Check which profile is active

-

Windows maintains separate Domain, Private and Public profiles. A disabled profile may not be the currently active network profile, but it is still useful context for policy review.

-

Look for managed or third-party security

-

Another endpoint security product or centrally managed policy may control the expected Firewall state. The report identifies state but does not determine the organization’s intended configuration.

-

Do not treat every warning as an instruction

-

A WARN finding means the state deserves review. It does not automatically mean that enabling a feature is safe on every managed, specialized or test system.

-

Compare multiple signals

-

Firewall, Defender, Secure Boot, TPM and BitLocker measure different layers of protection. One enabled feature does not compensate for every other disabled feature, and one unavailable source does not prove that protection is absent.

- -
Important: the toolkit does not enable Firewall profiles, edit rules, change Defender settings, configure BitLocker or modify security policy.
- -

What the toolkit does not do

- - -

Reasonable next steps

-
    -
  1. Record the active network profile and whether the machine is domain joined or centrally managed.
  2. -
  3. Confirm whether another security product is expected to manage the Firewall.
  4. -
  5. Compare the report with the organization’s approved baseline or Microsoft guidance for that environment.
  6. -
  7. Make configuration changes only through an authorized administrative process.
  8. -
- -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/cases/windows-high-memory-usage.html b/site/cases/windows-high-memory-usage.html deleted file mode 100644 index 4865741..0000000 --- a/site/cases/windows-high-memory-usage.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - Diagnose High Memory Usage on Windows with PowerShell | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesHigh memory usage
-
-
Windows performance troubleshooting
-

Diagnose high memory usage on Windows with PowerShell

-

Take a one-time read-only snapshot of available physical memory, pagefile usage, CPU load and the processes using the most working-set memory.

- - - - -

Run the performance snapshot

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -Performance -PrivacyMode -ExportMarkdown
-

For custom thresholds and a longer process list, run the standalone module:

-
pwsh -NoProfile -File .\scripts\performance-snapshot.ps1 `
-  -TopProcessCount 20 -LowMemoryPercent 20 -HighCpuPercent 90
- -

When this guide is useful

- - -

What the performance report checks

-
-

Physical memory

Total and available RAM plus the percentage still available at the moment the snapshot is taken.

-

Pagefile usage

Current pagefile allocation and usage where Windows exposes the data.

-

CPU snapshot

A point-in-time CPU load value used to flag unusually high load at the moment of collection.

-

Top processes

Processes ranked by working set and cumulative CPU time. Process command lines, owners and executable paths are not collected.

-
- -

How to interpret high memory usage

-

Available memory matters more than the used percentage alone

-

Windows intentionally uses free RAM for cache and can reclaim it when applications need memory. A high used percentage is more concerning when available memory is very low, pagefile use is rising and the user sees slowdowns or allocation failures.

-

Working set is not the same as total private memory

-

The process list ranks working-set memory currently resident in physical RAM. Shared pages, cached data and memory that has been paged out can make other tools show different totals.

-

Cumulative CPU time is not current CPU usage

-

A long-running process may have a high cumulative CPU total even when it is idle during the snapshot. Use the current CPU load together with the process list rather than treating cumulative time as a live percentage.

-

One snapshot cannot prove a memory leak

-

A memory leak is normally established by observing growth over time under comparable conditions. This report captures evidence at one moment and is best used as a starting point or reproducible support attachment.

- -
Default findings: the module warns below 15% available memory, at 95% CPU load and at 80% pagefile usage. Thresholds are configurable in the standalone script.
- -

What the toolkit does not do

- - -

Reasonable next steps

-
    -
  1. Save the snapshot while the slowdown is occurring.
  2. -
  3. Compare available memory, pagefile usage and the largest working sets.
  4. -
  5. Repeat the snapshot under the same workload if growth over time is suspected.
  6. -
  7. Use application-specific diagnostics when one process consistently dominates memory use.
  8. -
- -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/cases/windows-network-routes.html b/site/cases/windows-network-routes.html deleted file mode 100644 index c31031f..0000000 --- a/site/cases/windows-network-routes.html +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - Inspect Windows Default Routes and Proxy Settings | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesRoutes and proxy settings
-
-
Windows network troubleshooting
-

Inspect Windows default routes and proxy settings

-

Compare active adapters, gateways, route metrics, DNS, DHCP and WinINET or WinHTTP proxy state without changing the Windows network stack.

- - - - -

Run the network diagnostic

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -Network -PrivacyMode -ExportMarkdown
-

The report places default routes first, then includes adapter, address, DNS, DHCP, gateway, route, proxy and basic reachability context. Privacy Mode replaces common identifiers in the combined report.

- -

When this guide is useful

- - -

What the network report checks

-
-

Adapters and addresses

Active adapters, IPv4 and IPv6 addresses, DHCP state, DHCP server and DNS suffix or search-list context.

-

Gateways and routes

Default routes appear first with next hop, interface and effective metric, followed by a bounded list of active routes.

-

DNS and reachability

Configured DNS servers plus simple gateway, DNS-resolution and internet-reachability checks.

-

Proxy context

Current-user WinINET state and read-only WinHTTP proxy output. Proxy credentials and sensitive URL query values are removed.

-
- -

How to interpret routes and proxy settings

-

Compare effective route metrics

-

For competing default routes, the path with the lower effective metric is usually preferred. The metric must still be considered together with interface state and the actual software using the network.

-

Expect virtual adapters to add routes

-

Hypervisors, containers, VPN clients and security products may create virtual interfaces and route entries. Their presence is not automatically a fault; compare the result with installed and currently running software.

-

Distinguish WinINET from WinHTTP

-

Applications may use different Windows networking APIs. A browser can use current-user WinINET settings while a service or command-line component uses WinHTTP, so one proxy can be configured while the other is direct.

-

Use reachability checks as clues

-

A successful gateway test with failed DNS resolution points in a different direction than a failed gateway test. These checks are deliberately simple and should be combined with the rest of the report.

- -
Privacy note: network reports can reveal private addressing, DNS suffixes, proxy hosts and infrastructure names. Use Privacy Mode and review the report before sharing it.
- -

What the toolkit does not do

- - -

Reasonable next steps

-
    -
  1. Identify the active adapter and the preferred default route in the report.
  2. -
  3. Compare WinINET and WinHTTP proxy state with the affected application.
  4. -
  5. Check whether unexpected routes belong to known VPN, hypervisor or security software.
  6. -
  7. Preserve the redacted report before changing network configuration so the original state is not lost.
  8. -
- -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/cases/windows-time-service-stopped.html b/site/cases/windows-time-service-stopped.html deleted file mode 100644 index af0dfad..0000000 --- a/site/cases/windows-time-service-stopped.html +++ /dev/null @@ -1,138 +0,0 @@ - - - - - - Diagnose a Stopped Windows Time Service with PowerShell | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesWindows Time service
-
-
Windows time synchronization
-

Diagnose a stopped Windows Time service with PowerShell

-

Inspect W32Time state, start mode, timezone, local and UTC clock values, configured source, verbose status and optional events without starting the service or changing its configuration.

- - - - -

Run the Windows Time diagnostic

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -Time -PrivacyMode -ExportMarkdown
-

The combined report summarizes the current time-service context. For additional event evidence, the standalone module can include recent Time-Service warnings and errors:

-
pwsh -NoProfile -File .\scripts\time-sync-diagnostics.ps1 `
-  -IncludeTimeServiceEvents
- -

When this guide is useful

- - -

What the report checks

-
-

Service state

Current W32Time status and start mode, including whether the computer is domain joined.

-

Clock context

Timezone plus current local and UTC values to make obvious offsets or display confusion easier to spot.

-

Configured source

The source reported by w32tm.exe /query /source, collected without changing peers or requesting synchronization.

-

Verbose status and events

Read-only status details and optional recent Time-Service warning or error events.

-
- -

How to interpret a stopped Windows Time service

-

A stopped service is not always an error

-

On a non-domain computer, Windows may start the time service when needed rather than keeping it continuously active. Service state should be considered together with start mode, source, status and recent events.

-

Domain membership changes the expectation

-

Domain-joined systems normally depend on the domain time hierarchy. A stopped service or unavailable source on such a machine deserves closer review than the same state on an isolated home computer.

-

Check the reported source

-

A local hardware clock, unavailable source or unexpected provider can explain why synchronization is not occurring. The source alone is not a complete diagnosis, but it narrows the investigation.

-

Separate timezone errors from synchronization errors

-

A correct UTC clock with an incorrect timezone can look like time drift to the user. Compare both local and UTC values before assuming the synchronization service is at fault.

- -
Read-only boundary: the toolkit never runs a resynchronization command, changes peers, starts or stops W32Time, or edits time-service configuration.
- -

What the toolkit does not do

- - -

Reasonable next steps

-
    -
  1. Compare local time, UTC time and timezone in the report.
  2. -
  3. Check whether the computer is domain joined and whether the reported source matches the expected environment.
  4. -
  5. Review recent Time-Service events if the source is unavailable or the clock repeatedly drifts.
  6. -
  7. Apply any configuration change only through documented Windows or organization procedures.
  8. -
- -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/cases/windows-update-pending-reboot.html b/site/cases/windows-update-pending-reboot.html deleted file mode 100644 index 2c786b0..0000000 --- a/site/cases/windows-update-pending-reboot.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - Check Windows Update Pending Reboot Indicators | WDT - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
HomeGuidesWindows Update pending reboot
-
-
Windows Update troubleshooting
-

Check Windows Update pending reboot indicators

-

Inspect recent installed updates, common reboot indicators and Windows Update related service state without starting an update scan, installing anything or resetting update components.

- - - - -

Run the Windows Update diagnostic

-
pwsh -NoProfile -File .\Invoke-WindowsDiagnostics.ps1 `
-  -Updates -PrivacyMode -ExportMarkdown
-

For optional recent update-related Event Log entries, run the standalone module:

-
pwsh -NoProfile -File .\scripts\windows-update-check.ps1 `
-  -IncludeEventLog -SinceDays 14 -MaxEvents 20
- -

When this guide is useful

- - -

What the update report checks

-
-

Windows version and build

The operating-system version and build provide context for update history and support discussions.

-

Recently installed updates

Recent update records exposed by built-in Windows read-only sources.

-

Pending reboot indicators

Common registry-backed indicators used by Windows components and servicing processes to signal that a restart may be required.

-

Update-related services

Current state and start mode for relevant Windows Update and servicing services without starting or stopping them.

-

Optional Event Log evidence

Recent Windows Update related warnings and errors when the standalone option is requested.

-

Findings summary

Unavailable sources, reboot indicators and other notable state are summarized before the detailed output.

-
- -

How to interpret pending reboot indicators

-

A reboot indicator is evidence, not a complete diagnosis

-

Different Windows components can leave separate indicators. One indicator may remain after the operation that created it, while another may appear only during a specific servicing stage.

-

Compare the indicator with update timing

-

Recent installed updates, installer activity and Event Log timestamps help determine whether the reboot state is current and relevant to the reported problem.

-

Service state depends on trigger-start behavior

-

Some Windows Update services do not run continuously. A stopped service is not automatically a fault; start mode, recent events and the observed update behavior matter.

-

Do not infer that all updates are current

-

The diagnostic reads local history and state. It does not contact Microsoft Update or prove that no newer update is available.

- -
Read-only boundary: the toolkit does not start an update scan, install or remove updates, reset Windows Update, clean SoftwareDistribution, change services or write registry values.
- -

What the toolkit does not do

- - -

Reasonable next steps

-
    -
  1. Record the pending reboot indicators and the most recent update timestamps.
  2. -
  3. Check whether important work, encryption operations or managed maintenance policies affect restart timing.
  4. -
  5. Review optional update events when the local state appears inconsistent.
  6. -
  7. Follow the approved restart or servicing procedure for the machine.
  8. -
- -

Related Windows troubleshooting guides

- - - -
-
- - - diff --git a/site/index.html b/site/index.html deleted file mode 100644 index be569a8..0000000 --- a/site/index.html +++ /dev/null @@ -1,384 +0,0 @@ - - - - - - Windows Diagnostics Tool for PowerShell | WDT - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
PS C:\WDT> .\Invoke-WindowsDiagnostics.ps1
-

SYSTEM READY

-

See what Windows is doing.
Change nothing.

-

A read-only PowerShell diagnostics toolkit with a responsive terminal interface, local reports, Privacy Mode and no telemetry.

- -
-
MODE
READ-ONLY
-
UPLOAD
NONE
-
RUNTIME
PS 5.1 / 7+
-
PLATFORM
WIN 10 / 11
-
-
- -
-
WDT / INTERACTIVE DASHBOARDUTF-8
- - Windows Diagnostics Toolkit Wide terminal dashboard with selected diagnostics and Privacy Mode enabled - -
[OK] Responsive layout · keyboard-first controls · current selection preserved on resize
-
-
- -
-
- [x] READ-ONLY BY DESIGN - [x] NO INSTALLER - [x] NO TELEMETRY - [x] LOCAL TXT + MARKDOWN - [x] PRIVACY MODE -
-
- -
-
-
-
-

01 / INTERFACE

-

Built around the terminal,
not bolted onto it.

-
-

Run the entry point without switches. Select the diagnostic areas, choose report options and review the result without leaving the TUI.

-
- -
-
-
STEP 01SELECT
-

Choose checks

-

Toggle ten diagnostic areas, Privacy Mode, Markdown export and the output directory.

-

navigateSpacetoggle

-
-
-
STEP 02RUN
-

Collect locally

-

Only reviewed diagnostic operations are launched. The toolkit does not apply fixes or reconfigure Windows.

-

AallRrecommendedEnterrun

-
-
-
STEP 03REVIEW
-

Read findings

-

See elapsed time, report paths and aggregated WARN and ERROR counts before opening the report.

-

EntermenuEscexit

-
-
- -
-
WDT / COLLECTION RESULTCOMPLETE
- - Windows Diagnostics Toolkit completion screen showing warnings, errors and elapsed time - -
[WARN] A warning means the observed state deserves review. It does not mean collection failed.
-
-
-
- -
-
-
-

02 / QUICK START

-

Clone. Run. Diagnose.

-

No installer and no third-party PowerShell modules. The built-in Windows PowerShell 5.1 is enough.

-
    -
  • [OK] Interactive TUI opens by default
  • -
  • [OK] Recommended checks preselected
  • -
  • [OK] Privacy Mode enabled by default
  • -
-

The planned v0.1.0-beta publication will add a one-line bootstrap that downloads a fixed GitHub Release ZIP and verifies its SHA-256 checksum before execution. Until publication, use the clone workflow. The bootstrap still requires trust in the file delivered through GitHub Pages.

-
-
-
PowerShell
-
PS> git clone https://github.com/0x0bug/windows-diagnostics-toolkit.git
-PS> cd windows-diagnostics-toolkit
-PS> .\Invoke-WindowsDiagnostics.ps1
- -
-
-
-
-

Beta release command

-

Available when v0.1.0-beta is published. Review every report before publication; Privacy Mode cannot guarantee removal of arbitrary sensitive text in Windows Event Log messages.

-
-
-
PS> irm https://0x0bug.github.io/windows-diagnostics-toolkit/run.ps1 | iex
-
-PS> irm https://0x0bug.github.io/windows-diagnostics-toolkit/run.ps1 -OutFile .\wdt-run.ps1
-PS> notepad .\wdt-run.ps1
-PS> .\wdt-run.ps1
- -
-
-
- -
-
-
-
-

03 / DIAGNOSTICS

-

One report.
Ten diagnostic modules.

-
-

Ten diagnostic modules are grouped here into six troubleshooting areas. Collect broad context or run only the module related to the problem; the findings summary appears before the detailed sections.

-
- - -
-
- -
-
-
-

04 / PRIVACY

-

Local first.
Share only what you choose.

-

Reports stay in the selected output directory. Privacy Mode replaces common identifying values with stable per-report tokens.

-
    -
  • [x]Read-onlyNo registry, service, firewall, DNS or system configuration changes.
  • -
  • [x]No telemetryNo upload, remote collection or tracking endpoint.
  • -
  • [x]AuditableSource code, validation policy and tests are public.
  • -
-
-
-
PRIVACY MODEENABLED
-
ComputerName : <HOST-1>
-UserName     : <USER-1>
-IPv4Address  : <IP-1>
-MacAddress   : <MAC-1>
-DeviceId     : <ID-1>
-

[!] Process, application and dump-file names remain visible because they are diagnostically useful.

-
-
-
- -
- -
- -
-
-
-

06 / FAQ

-

Expected behavior,
without guesswork.

-
-
-
Does the toolkit change Windows settings?

No. Production diagnostics collect and report state without applying automatic fixes, changing services, editing the registry or reconfiguring the network.

-
Why do I see the ASCII logo?

Automatic mode uses Unicode only for compatible interactive UTF-8 output. OEM encodings such as cp866, redirected output and unsupported hosts receive the printable ASCII fallback.

-
Does resizing restart diagnostics?

No. Resize events redraw the current interface and preserve the current selection. A running diagnostic operation is not restarted.

-
Where are reports stored?

Reports are written to the selected output directory. Nothing is uploaded automatically.

-
Does WARN mean the tool failed?

No. WARN describes observed state that deserves review. A module execution failure is reported separately with a non-zero exit code.

-
-
-
- -
-
-
-

READY / RUN

-

Diagnose Windows before changing Windows.

-

Open source, dependency-free and designed to produce evidence you can actually share.

-
- -
-
-
- - - - diff --git a/site/robots.txt b/site/robots.txt deleted file mode 100644 index 33bd5b2..0000000 --- a/site/robots.txt +++ /dev/null @@ -1,4 +0,0 @@ -User-agent: * -Allow: / - -Sitemap: https://0x0bug.github.io/windows-diagnostics-toolkit/sitemap.xml diff --git a/site/run.ps1 b/site/run.ps1 deleted file mode 100644 index da24993..0000000 --- a/site/run.ps1 +++ /dev/null @@ -1,127 +0,0 @@ -& { -$ErrorActionPreference = 'Stop' - -$wdtVersion = '0.1.0-beta' -$wdtArchiveName = 'windows-diagnostics-toolkit-v0.1.0-beta.zip' -$wdtReleaseBaseUri = 'https://github.com/0x0bug/windows-diagnostics-toolkit/releases/download/v0.1.0-beta' -$wdtArchiveUri = "$wdtReleaseBaseUri/$wdtArchiveName" -$wdtChecksumUri = "$wdtArchiveUri.sha256" - -function Get-WdtExpectedChecksum { - param([Parameter(Mandatory = $true)][string]$ChecksumText) - - $matches = @([System.Text.RegularExpressions.Regex]::Matches($ChecksumText, '(?im)(? - - - https://0x0bug.github.io/windows-diagnostics-toolkit/ - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/ - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-diagnostic-report-for-tech-support.html - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-application-crashes.html - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-high-memory-usage.html - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-update-pending-reboot.html - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-disk-health-low-space.html - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-firewall-disabled.html - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-time-service-stopped.html - 2026-07-10 - - - https://0x0bug.github.io/windows-diagnostics-toolkit/cases/windows-network-routes.html - 2026-07-10 - - diff --git a/tests/bootstrap-sync.tests.ps1 b/tests/bootstrap-sync.tests.ps1 index 3b565da..84f3244 100644 --- a/tests/bootstrap-sync.tests.ps1 +++ b/tests/bootstrap-sync.tests.ps1 @@ -8,29 +8,34 @@ function Assert-True { if (-not $Condition) { throw $Message } } -function Get-NormalizedText { - param([Parameter(Mandatory = $true)][string]$Path) - return (Get-Content -LiteralPath $Path -Raw).Replace("`r`n", "`n").Replace("`r", "`n") -} - $repositoryRoot = Split-Path -Parent $PSScriptRoot $canonicalBootstrap = Join-Path -Path $repositoryRoot -ChildPath 'scripts\bootstrap\run.ps1' -$publishedBootstrap = Join-Path -Path $repositoryRoot -ChildPath 'site\run.ps1' $syncScript = Join-Path -Path $repositoryRoot -ChildPath 'scripts\Sync-WdtSiteBootstrap.ps1' $powerShellPath = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName $temporaryRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ('wdt-bootstrap-sync-tests-' + [System.Guid]::NewGuid().ToString('N')) $previousLastExitCode = $global:LASTEXITCODE -foreach ($requiredFile in @($canonicalBootstrap, $publishedBootstrap, $syncScript)) { +foreach ($requiredFile in @($canonicalBootstrap, $syncScript)) { Assert-True (Test-Path -LiteralPath $requiredFile -PathType Leaf) "Required file is missing: $requiredFile" } $canonicalHash = (Get-FileHash -LiteralPath $canonicalBootstrap -Algorithm SHA256).Hash -$publishedHash = (Get-FileHash -LiteralPath $publishedBootstrap -Algorithm SHA256).Hash -$byteEqual = [string]::Equals($canonicalHash, $publishedHash, [System.StringComparison]::OrdinalIgnoreCase) -if (-not $byteEqual) { - Assert-True ((Get-NormalizedText -Path $canonicalBootstrap) -ceq (Get-NormalizedText -Path $publishedBootstrap)) 'Canonical and published bootstrap differ beyond CRLF/LF line endings.' +$tokens = $null +$parseErrors = $null +$syncAst = [System.Management.Automation.Language.Parser]::ParseFile($syncScript, [ref]$tokens, [ref]$parseErrors) +Assert-True ($parseErrors.Count -eq 0) 'Sync script contains PowerShell parser errors.' + +$forbiddenCommands = @('git', 'gh', 'Invoke-RestMethod', 'Invoke-WebRequest', 'curl', 'curl.exe') +$commandAsts = @($syncAst.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.CommandAst] +}, $true)) +foreach ($commandAst in $commandAsts) { + $commandName = $commandAst.GetCommandName() + Assert-True ($commandName -notin $forbiddenCommands) "Sync script contains a forbidden external or network command: $commandName" } +$syncScriptText = Get-Content -LiteralPath $syncScript -Raw +Assert-True ($syncScriptText -notmatch '(?i)api\.github\.com') 'Sync script contains a GitHub API endpoint.' New-Item -ItemType Directory -Path $temporaryRoot -Force | Out-Null try { diff --git a/tests/readme.tests.ps1 b/tests/readme.tests.ps1 index 0a1a6d7..2cd3e04 100644 --- a/tests/readme.tests.ps1 +++ b/tests/readme.tests.ps1 @@ -50,19 +50,14 @@ Assert-True (-not $readme.Contains('The interface is ASCII-first')) 'README stil Assert-True (-not $readme.Contains('Set-ExecutionPolicy Unrestricted')) 'README recommends a persistent unsafe Execution Policy change.' Assert-True (-not $readme.Contains('C:\Users\')) 'README contains an absolute user profile path.' -$requiredAssets = @( - 'site\assets\tui-wide-real.png', - 'site\assets\tui-result-real.png' -) -foreach ($relativeAsset in $requiredAssets) { - $assetPath = Join-Path -Path $repositoryRoot -ChildPath $relativeAsset - Assert-True (Test-Path -LiteralPath $assetPath -PathType Leaf) ("Documentation asset is missing: {0}" -f $relativeAsset) -} - -Assert-True ($readme.Contains('site/assets/tui-wide-real.png')) 'README does not reference the real Wide TUI visual.' -Assert-True ($readme.Contains('site/assets/tui-result-real.png')) 'README does not reference the real result visual.' -Assert-True ($usage.Contains('../site/assets/tui-wide-real.png')) 'Usage guide does not reference the real Wide TUI visual.' -Assert-True ($usage.Contains('../site/assets/tui-result-real.png')) 'Usage guide does not reference the real result visual.' +$wideTuiImage = 'https://wdt.digital/assets/tui-wide-real.png' +$resultTuiImage = 'https://wdt.digital/assets/tui-result-real.png' +Assert-True ($readme.Contains($wideTuiImage)) 'README does not reference the published Wide TUI visual.' +Assert-True ($readme.Contains($resultTuiImage)) 'README does not reference the published result visual.' +Assert-True ($usage.Contains($wideTuiImage)) 'Usage guide does not reference the published Wide TUI visual.' +Assert-True ($usage.Contains($resultTuiImage)) 'Usage guide does not reference the published result visual.' +Assert-True (-not $readme.Contains('site/assets/')) 'README still references legacy local site assets.' +Assert-True (-not $usage.Contains('site/assets/')) 'Usage guide still references legacy local site assets.' Assert-True ($usage.Contains('ASCII appears instead of the Unicode logo')) 'Usage guide is missing Unicode fallback troubleshooting.' Assert-True (-not $usage.Contains('## Real Co-Authored Commit')) 'Usage guide still contains contributor workflow content.' diff --git a/tests/site-seo.tests.ps1 b/tests/site-seo.tests.ps1 deleted file mode 100644 index 5539a7e..0000000 --- a/tests/site-seo.tests.ps1 +++ /dev/null @@ -1,276 +0,0 @@ -[CmdletBinding()] -param() - -$ErrorActionPreference = 'Stop' - -$repositoryRoot = Split-Path -Parent $PSScriptRoot -$siteRoot = Join-Path -Path $repositoryRoot -ChildPath 'site' -$separatorCharacters = [char[]]@('\', '/') -$siteRootFull = [System.IO.Path]::GetFullPath($siteRoot).TrimEnd($separatorCharacters) -$publishedBaseUrl = 'https://0x0bug.github.io/windows-diagnostics-toolkit/' -$projectPathPrefix = '/windows-diagnostics-toolkit/' - -$issues = New-Object System.Collections.Generic.List[string] - -function Add-TestIssue { - param([Parameter(Mandatory = $true)][string]$Message) - $issues.Add($Message) -} - -function Get-FirstMatchValue { - param( - [Parameter(Mandatory = $true)][string]$Text, - [Parameter(Mandatory = $true)][string]$Pattern - ) - - $options = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Singleline - $match = [regex]::Match($Text, $Pattern, $options) - if (-not $match.Success) { - return $null - } - - return [System.Net.WebUtility]::HtmlDecode($match.Groups[1].Value.Trim()) -} - -function Get-RelativeSitePath { - param([Parameter(Mandatory = $true)][string]$FullPath) - - $full = [System.IO.Path]::GetFullPath($FullPath) - if (-not $full.StartsWith($siteRootFull, [System.StringComparison]::OrdinalIgnoreCase)) { - return $full - } - - return $full.Substring($siteRootFull.Length).TrimStart($separatorCharacters).Replace('\', '/') -} - -function Resolve-LocalSiteTarget { - param( - [Parameter(Mandatory = $true)][System.IO.FileInfo]$SourceFile, - [Parameter(Mandatory = $true)][string]$Reference - ) - - $referenceWithoutFragment = ($Reference -split '#', 2)[0] - $pathPart = ($referenceWithoutFragment -split '\?', 2)[0] - if ([string]::IsNullOrWhiteSpace($pathPart)) { - return $null - } - - if ($pathPart -match '(?i)^(https?:|mailto:|tel:|javascript:|data:)') { - return $null - } - - $isDirectoryReference = $pathPart.EndsWith('/') -or $pathPart -eq '.' -or $pathPart -eq '..' - - if ($pathPart.StartsWith($projectPathPrefix, [System.StringComparison]::OrdinalIgnoreCase)) { - $relative = $pathPart.Substring($projectPathPrefix.Length).Replace('/', [System.IO.Path]::DirectorySeparatorChar) - $target = Join-Path -Path $siteRoot -ChildPath $relative - } - elseif ($pathPart.StartsWith('/')) { - Add-TestIssue "Unsupported absolute local path in $($SourceFile.Name): $Reference" - return $null - } - else { - $relative = $pathPart.Replace('/', [System.IO.Path]::DirectorySeparatorChar) - $target = Join-Path -Path $SourceFile.DirectoryName -ChildPath $relative - } - - $target = [System.IO.Path]::GetFullPath($target) - if ($isDirectoryReference) { - $target = Join-Path -Path $target -ChildPath 'index.html' - } - - return $target -} - -if (-not (Test-Path -LiteralPath $siteRoot -PathType Container)) { - throw "Site directory is missing: $siteRoot" -} - -$htmlFiles = @(Get-ChildItem -LiteralPath $siteRoot -Recurse -Filter '*.html' -File | Sort-Object -Property FullName) -if ($htmlFiles.Count -lt 2) { - Add-TestIssue 'Expected at least a homepage and one additional HTML page.' -} - -$titleOwners = @{} -$canonicalOwners = @{} -$descriptionOwners = @{} -$indexableCanonicals = New-Object System.Collections.Generic.List[string] - -foreach ($file in $htmlFiles) { - $relativePath = Get-RelativeSitePath -FullPath $file.FullName - $html = Get-Content -LiteralPath $file.FullName -Raw - $isNotFoundPage = $relativePath -eq '404.html' - - if ($html -notmatch '(?i)]*lang=["'']en["'']') { - Add-TestIssue "$relativePath is missing html lang=en." - } - - $title = Get-FirstMatchValue -Text $html -Pattern '\s*(.*?)\s*' - if ([string]::IsNullOrWhiteSpace($title)) { - Add-TestIssue "$relativePath is missing a title." - } - else { - if ($title.Length -lt 15 -or $title.Length -gt 70) { - Add-TestIssue "$relativePath title length is $($title.Length); expected 15-70 characters." - } - if ($titleOwners.ContainsKey($title)) { - Add-TestIssue "$relativePath duplicates the title used by $($titleOwners[$title])." - } - else { - $titleOwners[$title] = $relativePath - } - } - - $description = Get-FirstMatchValue -Text $html -Pattern ']*name=["'']description["''][^>]*content=["''](.*?)["''][^>]*>' - if ([string]::IsNullOrWhiteSpace($description)) { - Add-TestIssue "$relativePath is missing a meta description." - } - else { - if ($description.Length -lt 70 -or $description.Length -gt 180) { - Add-TestIssue "$relativePath description length is $($description.Length); expected 70-180 characters." - } - if (-not $isNotFoundPage) { - if ($descriptionOwners.ContainsKey($description)) { - Add-TestIssue "$relativePath duplicates the description used by $($descriptionOwners[$description])." - } - else { - $descriptionOwners[$description] = $relativePath - } - } - } - - $robots = Get-FirstMatchValue -Text $html -Pattern ']*name=["'']robots["''][^>]*content=["''](.*?)["''][^>]*>' - if ($isNotFoundPage) { - if ($robots -notmatch '(?i)noindex') { - Add-TestIssue '404.html must contain a noindex robots directive.' - } - } - elseif ($robots -match '(?i)noindex') { - Add-TestIssue "$relativePath unexpectedly contains noindex." - } - - $h1Count = [regex]::Matches($html, ']*rel=["'']canonical["''][^>]*href=["''](.*?)["''][^>]*>' - if ([string]::IsNullOrWhiteSpace($canonical)) { - Add-TestIssue "$relativePath is missing a canonical URL." - } - else { - if (-not $canonical.StartsWith($publishedBaseUrl, [System.StringComparison]::OrdinalIgnoreCase)) { - Add-TestIssue "$relativePath canonical is outside the published site: $canonical" - } - if ($canonicalOwners.ContainsKey($canonical)) { - Add-TestIssue "$relativePath duplicates the canonical used by $($canonicalOwners[$canonical])." - } - else { - $canonicalOwners[$canonical] = $relativePath - $indexableCanonicals.Add($canonical) - } - } - - foreach ($requiredMeta in @('og:title', 'og:description', 'og:url', 'og:image')) { - $metaPattern = ']*property=["'']{0}["''][^>]*content=["''](.*?)["''][^>]*>' -f [regex]::Escape($requiredMeta) - $value = Get-FirstMatchValue -Text $html -Pattern $metaPattern - if ([string]::IsNullOrWhiteSpace($value)) { - Add-TestIssue "$relativePath is missing $requiredMeta." - } - } - - foreach ($requiredTwitterMeta in @('twitter:card', 'twitter:title', 'twitter:description', 'twitter:image')) { - $metaPattern = ']*name=["'']{0}["''][^>]*content=["''](.*?)["''][^>]*>' -f [regex]::Escape($requiredTwitterMeta) - $value = Get-FirstMatchValue -Text $html -Pattern $metaPattern - if ([string]::IsNullOrWhiteSpace($value)) { - Add-TestIssue "$relativePath is missing $requiredTwitterMeta." - } - } - } - - $jsonOptions = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase -bor [System.Text.RegularExpressions.RegexOptions]::Singleline - $jsonLdMatches = [regex]::Matches($html, ']*type=["'']application/ld\+json["''][^>]*>(.*?)', $jsonOptions) - foreach ($jsonLdMatch in $jsonLdMatches) { - try { - $null = $jsonLdMatch.Groups[1].Value | ConvertFrom-Json - } - catch { - Add-TestIssue "$relativePath contains invalid JSON-LD: $($_.Exception.Message)" - } - } - - $referenceMatches = [regex]::Matches($html, '(?i)\b(?:href|src)=["'']([^"'']+)["'']') - foreach ($referenceMatch in $referenceMatches) { - $reference = [System.Net.WebUtility]::HtmlDecode($referenceMatch.Groups[1].Value) - $target = Resolve-LocalSiteTarget -SourceFile $file -Reference $reference - if ($null -eq $target) { - continue - } - - if (-not (Test-Path -LiteralPath $target -PathType Leaf)) { - Add-TestIssue "$relativePath contains a broken local reference: $reference -> $(Get-RelativeSitePath -FullPath $target)" - } - } -} - -$sitemapPath = Join-Path -Path $siteRoot -ChildPath 'sitemap.xml' -if (-not (Test-Path -LiteralPath $sitemapPath -PathType Leaf)) { - Add-TestIssue 'sitemap.xml is missing.' -} -else { - try { - [xml]$sitemap = Get-Content -LiteralPath $sitemapPath -Raw - $sitemapUrls = @($sitemap.urlset.url | ForEach-Object { [string]$_.loc }) - foreach ($canonical in $indexableCanonicals) { - if ($canonical -notin $sitemapUrls) { - Add-TestIssue "Canonical URL is missing from sitemap.xml: $canonical" - } - } - - foreach ($url in $sitemapUrls) { - if (-not $url.StartsWith($publishedBaseUrl, [System.StringComparison]::OrdinalIgnoreCase)) { - Add-TestIssue "Sitemap URL is outside the published site: $url" - continue - } - - $relative = $url.Substring($publishedBaseUrl.Length) - if ([string]::IsNullOrWhiteSpace($relative) -or $relative.EndsWith('/')) { - $relative = $relative + 'index.html' - } - $target = Join-Path -Path $siteRoot -ChildPath ($relative.Replace('/', [System.IO.Path]::DirectorySeparatorChar)) - if (-not (Test-Path -LiteralPath $target -PathType Leaf)) { - Add-TestIssue "Sitemap URL has no matching site file: $url" - } - } - } - catch { - Add-TestIssue "sitemap.xml is invalid: $($_.Exception.Message)" - } -} - -$robotsPath = Join-Path -Path $siteRoot -ChildPath 'robots.txt' -if (-not (Test-Path -LiteralPath $robotsPath -PathType Leaf)) { - Add-TestIssue 'robots.txt is missing.' -} -else { - $robotsText = Get-Content -LiteralPath $robotsPath -Raw - if ($robotsText -notmatch '(?im)^Sitemap:\s*https://0x0bug\.github\.io/windows-diagnostics-toolkit/sitemap\.xml\s*$') { - Add-TestIssue 'robots.txt does not reference the published sitemap URL.' - } -} - -Write-Host 'Windows Diagnostics Toolkit site SEO validation' -Write-Host ('HTML files : {0}' -f $htmlFiles.Count) -Write-Host ('Indexable pages : {0}' -f $indexableCanonicals.Count) -Write-Host ('Validation issues : {0}' -f $issues.Count) - -if ($issues.Count -gt 0) { - Write-Host '' - foreach ($issue in $issues) { - Write-Host ('- {0}' -f $issue) - } - throw 'Static site SEO validation failed.' -} - -Write-Host 'Static site SEO validation passed.'