Update projektplan and TODO with share analysis results

- projektplan: status section (server delivered, driver done), share
  analysis results in §2.1, customer checklist answers in §2.2, storage
  resolved in §2.3, new §2.6 on scoping the corpus, updated risks
- scripts/list-shares.ps1: enumerate SMB shares incl. paths, permissions
  and DFS namespaces on the file server
- TODO.md: restructured into blocking/server/done

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 14:10:14 +02:00
co-authored by Claude Opus 4.8
parent e7ef29c5d5
commit be6efa7b34
3 changed files with 243 additions and 38 deletions
+36 -8
View File
@@ -2,12 +2,40 @@
## phy-z-srv-gpu01 ## phy-z-srv-gpu01
Pre-work per [projektplan](server/phy-z-srv-gpu01/notes/20260710-projektplan.md) (§ references below): Per [projektplan](server/phy-z-srv-gpu01/notes/20260710-projektplan.md) (§ references below).
Server is delivered; base setup + NVIDIA driver are done (see `SETUP.md`).
- [x] Write a script to analyse phytrons whole file share which should be feeded to the llm: file number, structure, pdf kind, etc. → `server/phy-z-srv-gpu01/scripts/share-analysis.ps1` ### Blocking the knowledge base (customer conversation)
- [ ] Run the share analysis on the Windows server; evaluate: corpus size (§1 revisit trigger), scanned-PDF/OCR share, index size estimate
- [ ] Storage decision (§2.3): index estimate vs. ~960 GB usable NVMe — order extra disks **before** delivery if tight - [ ] **Scope the corpus (§2.6)** — share analysis found 1.26M extractable documents,
- [ ] Customer checklist (§2.2): AD bind account, read-only SMB service account, share include/exclude list, DNS name + TLS, internet access at install, chat-logging/GDPR (works council), maintenance ownership, concretize "other workloads" ~12× the planning threshold. Decide with the customer which shares/folders
- [ ] Ansible prep (§2.4): `cifs_mounts` and `llm_stack` role skeletons (`nvidia_gpu` exists); fill `group_vars/phy_z_srv_gpu01.yml` overrides; test non-GPU parts in a throwaway VM actually form the knowledge base. Everything in Phase 5 depends on this.
- [ ] German eval set (§2.5): collect 2030 Q&A with the customer — becomes the acceptance criterion - [ ] Evaluate the two CSVs still sitting on the file server: `D-toplevel-folders.csv`
- [ ] At install time: re-check and pin versions (driver / vLLM / Open WebUI / oikb / model shortlist) per re-entry checklist §5 (basis for the include/exclude list) and `D-file-types.csv` (resolve the
1.58M unidentified `other` files)
- [ ] Run `scripts/list-shares.ps1` on Z-FILESERVER — map shares → local paths,
so the analysis of `D:` can be tied to actual shares
- [ ] Data protection / works council: are chat logs stored? (§2.2) — before rollout
- [ ] AD details still needed: bind service account + base DN (group `llm_users` and
the read-only SMB account are agreed)
- [ ] German eval set (§2.5): 2030 Q&A with the customer — acceptance criterion
### Server / Ansible
- [ ] Role `cifs_mounts` — read-only mounts, credentials from `group_vars/secrets.yml`
- [ ] Role `llm_stack` — docker compose: vLLM (fixed `--gpu-memory-utilization`),
embedding server, Open WebUI + pgvector, reverse proxy, oikb timer.
Tag compose tasks with `compose` so `just compose` works.
- [ ] Pin versions at install time (vLLM / Open WebUI / oikb / model) per §5
- [ ] OCR pipeline for ~166k scanned PDFs (§2.1) — own work block, competes with vLLM for the GPU
- [ ] TLS: currently plain HTTP on `chat.phytron.local`; retrofit an internal CA
certificate (AD passwords travel in clear text until then)
- [ ] Hostname mismatch: server reports `phy-srv-gpu01`, repo/inventory/label use
`phy-z-srv-gpu01` — align
- [ ] Finish base hardening in `SETUP.md` (ssh, updates) via `just run phy_z_srv_gpu01`
### Done
- [x] Share analysis script → `scripts/share-analysis.ps1`, run on 2026-07-14 (§2.1)
- [x] Storage decision (§2.3) — 876 GB usable, no extra disks needed
- [x] NVIDIA driver via role `nvidia_gpu` (595 open kernel modules)
@@ -1,11 +1,26 @@
# Projektplan — Setup phy-z-srv-gpu01 (LLM-Server mit SMB-Wissensbasis) # Projektplan — Setup phy-z-srv-gpu01 (LLM-Server mit SMB-Wissensbasis)
Date: 2026-07-10 Date: 2026-07-10 (Stand-Update: 2026-09-03)
Status: Plan — Server-Lieferung in einigen Monaten erwartet Status: **In Umsetzung** — Server geliefert, Basis-Setup + GPU-Treiber fertig
Basiert auf: `20260706-hardware-assessment.md`, `20260706-software-assessment.md`, `20260707-deep-dive.md` Basiert auf: `20260706-hardware-assessment.md`, `20260706-software-assessment.md`, `20260707-deep-dive.md`
--- ---
## Stand 2026-09-03 (Kurzfassung — Details in den jeweiligen Abschnitten)
**Erledigt:** Server geliefert und im Rack; Basis-Setup (Ubuntu 24.04.4, Nutzer,
Netz, RAID/Filesystem — s. `SETUP.md` im Repo-Root); GPU-Treiber via Ansible-Rolle
`nvidia_gpu` (595er-Branch, **open** Kernel-Module — Blackwell läuft nicht mit den
proprietären Modulen, s. `manuals/20260714-nvidia-driver-install.md`); Docker
installiert. Share-Analyse gelaufen (§2.1).
**Als Nächstes:** CIFS-Mounts + Container-Stack (Rollen `cifs_mounts`, `llm_stack`)
— entspricht Phasen 35 in §3.
**Kritischer offener Punkt:** Der Bestand ist rund **12× größer** als die in §1
gesetzte Schwelle. Ohne Eingrenzung des Korpus (Kundengespräch) ist Phase 5 nicht
sinnvoll umsetzbar — s. §2.1 und §2.6.
## 0. Rahmenbedingungen (fixiert) ## 0. Rahmenbedingungen (fixiert)
| Punkt | Entscheidung | | Punkt | Entscheidung |
@@ -49,6 +64,14 @@ Deep Dive §4.7). Ausschlaggebend, Stand 2026-07-10 verifiziert:
Onyx (mit Eigenbau-Sync) oder Eigenbau-Pipeline (Deep Dive §4.5) neu bewerten. Onyx (mit Eigenbau-Sync) oder Eigenbau-Pipeline (Deep Dive §4.5) neu bewerten.
- Datei-genaue NTFS-ACLs werden doch Anforderung → Neubewertung (Deep Dive §4.4/4.5). - Datei-genaue NTFS-ACLs werden doch Anforderung → Neubewertung (Deep Dive §4.4/4.5).
**Status 2026-09-03:** Trigger 1 ist **eingetreten** (1,26 Mio. Office-Dateien,
§2.1). Die Produktentscheidung bleibt dennoch bei Open WebUI — aber **nur unter
der Bedingung**, dass der Korpus auf einen kuratierten Teilbestand eingegrenzt
wird (§2.6). Ein ungefilterter Vollindex über 1,26 Mio. Dokumente ist mit Open
WebUI nicht sinnvoll zu betreiben (und liefert auch inhaltlich schlechte
Antworten — RAG über einen kompletten, ungeordneten Fileserver retourniert
überwiegend Rauschen).
Jira später: für *Indizierung* von Jira-Inhalten hat Onyx zwar native Jira später: für *Indizierung* von Jira-Inhalten hat Onyx zwar native
Konnektoren, Open WebUI deckt das aber über Tools/MCP (Atlassian-MCP) bzw. Konnektoren, Open WebUI deckt das aber über Tools/MCP (Atlassian-MCP) bzw.
API-Workloads direkt gegen vLLM ab — kein Entscheidungskriterium für heute. API-Workloads direkt gegen vLLM ab — kein Entscheidungskriterium für heute.
@@ -70,28 +93,54 @@ GPU-Server nötig). Zu erheben:
Ergebnis bestimmt: Korpus-Umfang (→ §1-Trigger), OCR-Pipeline ja/nein, Ergebnis bestimmt: Korpus-Umfang (→ §1-Trigger), OCR-Pipeline ja/nein,
Index-Größe (→ Storage-Check §2.3), Erst-Indexierungsdauer. Index-Größe (→ Storage-Check §2.3), Erst-Indexierungsdauer.
### 2.2 Mit dem Kunden zu klären (Checkliste) **Ergebnis (Lauf 2026-07-14, Skript `scripts/share-analysis.ps1` auf `D:` des
Fileservers Z-FILESERVER):**
- [ ] AD/LDAP: Bind-Service-Account, Basis-DN, Gruppe „LLM-Nutzer" | Kennzahl | Wert | Konsequenz |
- [ ] **SMB-Service-Account read-only** für die Mounts (kein Nutzer-Account) |---|---|---|
- [ ] Welche Shares/Unterordner genau in den Index? Ausschlussliste (HR o. ä.) | Gesamt | **3.322.175 Dateien, 2.224 GB** | Scan lief über `D:` gesamt, nicht je Share → Obergrenze, enthält evtl. nicht freigegebene Daten |
- [ ] DNS-Name (z. B. `ki.phytron.local`) + TLS (internes CA-Zertifikat?) | `office` (extrahierbar) | **1.258.642 Dateien, 587 GB** | **12× über der §1-Schwelle** → Eingrenzung zwingend (§2.6) |
- [ ] Internetzugang des Servers (Modell-/Container-Downloads bei Installation; | `other` (unklassifiziert) | 1.575.752 Dateien, 934 GB | größter Block, **noch unidentifiziert**`D-file-types.csv` auswerten |
danach einschränkbar) | Bilder / CAD / Archive / Medien | 403k / 60k / 12,6k / 6,2k Dateien | nicht extrahierbar → Erwartungsmanagement |
- [ ] Protokollierung/Datenschutz: werden Chats gespeichert? Betriebsrat/DSGVO | PDFs | **924.076**, davon ~**18 % ohne Textlayer** (Stichprobe 200) | ≈ **166.000 Scan-PDFs → OCR**; eigener Zeit-/GPU-Aufwand, konkurriert mit vLLM |
früh einbinden | Änderungsrate | 94 % älter als 1 Jahr; nur ~158k in den letzten 365 Tagen | Sync-Frequenz unkritisch; **Alter ≠ Irrelevanz** (Datenblätter, Normen) → Filter primär ordner-/inhaltsbasiert, Aktualität nur sekundär |
- [ ] Update-/Wartungsfenster und wer den Betrieb nach Übergabe verantwortet | Duplikat-/Altversions-Muster | 33.179 Dateien | Filterregeln |
- [ ] „Andere Workloads" konkretisieren (Jira: Inhalte durchsuchen vs. | Pfade > 240 Zeichen | 25.169 | Extraktions-Toolchain gezielt testen |
Aktionen ausführen?) → bestimmt MIG-Layout später | Nicht-ASCII-Dateinamen | 126.111 | UTF-8 durchgängig (CIFS-Mount-Optionen → Extraktion → Index) |
### 2.3 Storage-Check (⚠️ vor Bestellung ggf. nachsteuern) **Noch auszuwerten** (CSVs liegen auf dem Fileserver, noch nicht gesichtet):
`D-toplevel-folders.csv` (Größe/Anzahl je Top-Level-Ordner — die Grundlage für
die Include-/Exclude-Liste) und `D-file-types.csv` (Auflösung der `other`-Kategorie).
2× 960 GB NVMe, vermutlich RAID1 → **~960 GB nutzbar** für OS + Docker + ### 2.2 Mit dem Kunden zu klären (Checkliste) — Stand 2026-09-03
Modelle (~100 GB) + Vektor-DB + Extraktions-Cache. Strategie: **kein lokaler
Spiegel der Shares** — nur Index (extrahierter Text + Embeddings, erfahrungsgemäß - [x] AD/LDAP: AD-Gruppe **`llm_users`** wird angelegt. *Offen:* Bind-Service-Account
wenige % des Rohvolumens). Nach der Share-Analyse gegenrechnen; wenn eng: und Basis-DN (werden für die LDAP-Konfiguration in Open WebUI gebraucht)
zwei weitere NVMe nachordern (Slots frei) — das ist **vor** Lieferung am - [x] **SMB-Service-Account read-only** wird separat angelegt (kein Nutzer-Account)
billigsten zu ändern. - [ ] **Welche Shares/Unterordner in den Index?** — offen, wird besprochen;
**wichtigster Punkt**, s. §2.1/§2.6
- [x] DNS-Name **`chat.phytron.local`** (vorerst); **kein TLS/keine lokalen
Zertifikate** zu Beginn → reines HTTP im LAN. *Hinweis:* AD-Passwörter gehen
damit im Klartext über das Netz; für später ein internes CA-Zertifikat
einplanen (Reverse Proxy ist vorbereitet, nur Zertifikat fehlt)
- [x] Internetzugang: **unbeschränkt**, keine Einschränkung nötig
- [ ] Protokollierung/Datenschutz: **noch zu besprechen** (Chat-Logs =
Verhaltensdaten; Betriebsrat/DSGVO) — vor dem Rollout klären, nicht vor der
technischen Umsetzung
- [x] Betrieb/Wartung/Updates: **wir** (Softbox)
- [x] „Andere Workloads": **Jira vorerst nicht relevant** → MIG bleibt aus, die
volle GPU geht an den LLM-Stack
### 2.3 Storage-Check — ✅ erledigt, unkritisch
Ist-Stand auf dem Server: **876 GB nutzbar** (`/`, LVM), davon 818 GB frei.
Gegenrechnung mit §2.1: selbst ein Vollindex des `office`-Bestands
(1,26 Mio. Dokumente → grob 150250 GB inkl. extrahiertem Text, Embeddings und
Index-Overhead) plus Modelle (~100 GB) passt. Strategie bleibt: **kein lokaler
Spiegel der Shares**, nur Index. **Keine zusätzlichen NVMe nötig.**
Der begrenzende Faktor ist damit *nicht* der Plattenplatz, sondern Retrieval-Qualität
und Indexgröße (§2.6).
### 2.4 Ansible vorbereiten (im Repo, testbar ohne GPU) ### 2.4 Ansible vorbereiten (im Repo, testbar ohne GPU)
@@ -116,6 +165,32 @@ Monaten neu sichten (§ Wiedereinstieg). Kleines deutsches Eval-Set (2030
Fragen mit erwarteten Antworten aus dem Bestand) schon jetzt mit dem Kunden Fragen mit erwarteten Antworten aus dem Bestand) schon jetzt mit dem Kunden
sammeln — das ist später das Abnahmekriterium. sammeln — das ist später das Abnahmekriterium.
### 2.6 Korpus eingrenzen (neu, wichtigster offener Punkt)
Die Share-Analyse (§2.1) hat 1,26 Mio. potenziell extrahierbare Dokumente
ergeben. „Alles indizieren" ist weder technisch sinnvoll noch inhaltlich
wünschenswert. Vorschlag für das Kundengespräch:
1. **Ordner statt Alter als Filter.** Aus `D-toplevel-folders.csv` gemeinsam mit
dem Kunden die Ordner markieren, die tatsächlich Wissensbasis sind
(Doku, Normen, Datenblätter, Handbücher, Projektdoku) — Rest bleibt außen vor.
Reiner Aktualitätsfilter wäre falsch: Datenblätter und Normen sind über Jahre
gültig.
2. **Ausschlusslisten** für Duplikate/Altversionen (33k Treffer), Archiv- und
Backup-Ordner, nicht extrahierbare Formate (CAD, Bilder, Medien).
3. **Zielgröße nennen.** Als Orientierung: eine gut funktionierende
Wissensbasis liegt eher im Bereich **10.000100.000 Dokumente**. Das ist
kein hartes Limit, aber der Bereich, in dem Retrieval-Qualität und
Indexierungsdauer beherrschbar bleiben.
4. **Iterativ starten.** Phase 5 zuerst mit *einem* gut abgegrenzten Share/Ordner,
Qualität am Eval-Set (§2.5) messen, dann erweitern. Das erspart einen
tagelangen Fehlversuch über den Gesamtbestand.
**Erwartungsmanagement gegenüber dem Kunden:** Die ursprüngliche Anforderung
lautete sinngemäß „unsere Dokumente auslesen". Realistisch ist „ein kuratierter
Teil unserer Dokumente, dafür mit guten Antworten". Das früh sagen — nicht erst,
wenn der Vollindex schlechte Treffer liefert.
## 3. Phasen ab Server-Lieferung ## 3. Phasen ab Server-Lieferung
| Phase | Inhalt | Ergebnis/Abnahme | | Phase | Inhalt | Ergebnis/Abnahme |
@@ -133,9 +208,11 @@ sammeln — das ist später das Abnahmekriterium.
| Risiko | Umgang | | Risiko | Umgang |
|---|---| |---|---|
| Storage zu klein für Index (§2.3) | Share-Analyse → ggf. NVMe nachordern **vor** Lieferung | | ~~Storage zu klein für Index~~ | ✅ erledigt — 876 GB nutzbar, reicht (§2.3) |
| Viele gescannte PDFs → OCR-Aufwand & -Qualität | Analyse-Stichprobe; OCR-Phase einplanen; Erwartungen dämpfen | | **Korpus 12× über der Planungsschwelle (§2.1)** | **Eingrenzung auf kuratierten Teilbestand (§2.6); ohne Kundenentscheid keine Phase 5** |
| SM120/Treiber-Kinderkrankheiten (Deep Dive §1.6) | Treiber ≥ 580, NVIDIA-vLLM-Container, Burn-in in Phase 2, HPE-Support | | ~166.000 Scan-PDFs → OCR-Aufwand & -Qualität | eigener Arbeitsblock, GPU-Konkurrenz zu vLLM einplanen; Erwartungen dämpfen |
| ~~SM120/Treiber-Kinderkrankheiten~~ | ✅ erledigt — Treiber 595 **open** läuft (proprietäre Module scheitern an `RmInitAdapter`) |
| Kein TLS zu Beginn (§2.2) | AD-Passwörter im Klartext im LAN; internes CA-Zertifikat nachrüsten, Reverse Proxy vorbereiten |
| Software-Stand veraltet bis Lieferung | Versionen **erst bei Installation pinnen**; Wiedereinstiegs-Checkliste | | Software-Stand veraltet bis Lieferung | Versionen **erst bei Installation pinnen**; Wiedereinstiegs-Checkliste |
| Erst-Indexierung dauert Tage | Teilbestand zuerst; Sync nachts; Dauer vorab abschätzen | | Erst-Indexierung dauert Tage | Teilbestand zuerst; Sync nachts; Dauer vorab abschätzen |
| RAM-Konkurrenz Extraktion/OCR vs. vLLM | vLLM hat feste VRAM-Quote; Indexer-Jobs drosseln (nice/cgroups); 128 GB reichen für OWUI-Stack | | RAM-Konkurrenz Extraktion/OCR vs. vLLM | vLLM hat feste VRAM-Quote; Indexer-Jobs drosseln (nice/cgroups); 128 GB reichen für OWUI-Stack |
@@ -144,12 +221,12 @@ sammeln — das ist später das Abnahmekriterium.
## 5. Wiedereinstiegs-Checkliste (wenn der Server da ist) ## 5. Wiedereinstiegs-Checkliste (wenn der Server da ist)
1. Dieses Dokument + Deep Dive lesen; offene Punkte aus §2.2 abhaken 1. [x] Dieses Dokument + Deep Dive lesen; offene Punkte aus §2.2 abhaken
2. Ergebnis der Share-Analyse vorliegen? (sonst zuerst!) 2. [x] Ergebnis der Share-Analyse vorliegen? → §2.1
3. Neu sichten & **dann erst pinnen**: vLLM-Version (RTX-PRO-6000-Support), 3. [ ] Neu sichten & **dann erst pinnen**: vLLM-Version (RTX-PRO-6000-/SM120-Support),
Open WebUI + oikb Release Notes, NVIDIA-Treiber, Modell-Shortlist §2.5 Open WebUI + oikb Release Notes, Modell-Shortlist §2.5 — NVIDIA-Treiber ✅ (595-open)
4. Storage-Entscheidung §2.3 umgesetzt? 4. [x] Storage-Entscheidung §2.3 → unkritisch, nichts nachzubestellen
5. Dann Phasenplan §3 von oben abarbeiten; jede Phase = Ansible-Commit + ggf. 5. [ ] Dann Phasenplan §3 von oben abarbeiten; jede Phase = Ansible-Commit + ggf.
Runbook unter `manuals/` Runbook unter `manuals/`
## 6. Bewusst offen gelassen (kein Jetzt-Entscheid nötig) ## 6. Bewusst offen gelassen (kein Jetzt-Entscheid nötig)
@@ -0,0 +1,100 @@
<#
.SYNOPSIS
Lists all SMB shares of a Windows file server incl. local path, size and
permissions — the basis for the include/exclude decision (projektplan §2.6).
.DESCRIPTION
Run ON the file server (or against a remote one via -ComputerName). Reports
per share: name, local path, description, share-level permissions, NTFS
groups, plus optional size/file count. Admin shares (C$, ADMIN$, IPC$) are
skipped unless -IncludeAdminShares is given.
Purely read-only. Requires local admin for Get-SmbShare (part of Windows
Server, no extra module needed).
.EXAMPLE
.\list-shares.ps1
.EXAMPLE
# remote, and with size per share (slow on large shares)
.\list-shares.ps1 -ComputerName Z-FILESERVER -WithSize
#>
param(
[string]$ComputerName = $env:COMPUTERNAME,
[switch]$IncludeAdminShares,
[switch]$WithSize,
[string]$OutDir = (Join-Path (Get-Location) "share-list")
)
$ErrorActionPreference = 'Continue'
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
Write-Host "=== SMB shares on $ComputerName ===" -ForegroundColor Cyan
$shares = Get-SmbShare -CimSession $ComputerName -ErrorAction Stop
if (-not $IncludeAdminShares) {
$shares = $shares | Where-Object { -not $_.Name.EndsWith('$') }
}
$result = foreach ($sh in $shares) {
Write-Host (" {0,-25} {1}" -f $sh.Name, $sh.Path)
# share-level permissions
$sharePerms = try {
(Get-SmbShareAccess -Name $sh.Name -CimSession $ComputerName -ErrorAction Stop |
ForEach-Object { "$($_.AccountName)=$($_.AccessRight)" }) -join '; '
} catch { 'n/a' }
# NTFS permissions (top level only) — who actually has access
$ntfsPerms = try {
$uncPath = "\\$ComputerName\$($sh.Name)"
((Get-Acl -LiteralPath $uncPath -ErrorAction Stop).Access |
ForEach-Object { "$($_.IdentityReference)=$($_.FileSystemRights)" } |
Select-Object -Unique | Select-Object -First 10) -join '; '
} catch { 'n/a' }
$sizeGB = $null; $fileCount = $null
if ($WithSize) {
Write-Host " measuring size ..." -ForegroundColor DarkGray
try {
$m = Get-ChildItem -LiteralPath "\\$ComputerName\$($sh.Name)" -Recurse -File -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum
$sizeGB = [Math]::Round($m.Sum / 1GB, 2)
$fileCount = $m.Count
} catch { }
}
[PSCustomObject]@{
Name = $sh.Name
Path = $sh.Path
Description = $sh.Description
SharePermissions = $sharePerms
NtfsPermissions = $ntfsPerms
SizeGB = $sizeGB
FileCount = $fileCount
}
}
$csv = Join-Path $OutDir "smb-shares.csv"
$result | Export-Csv $csv -NoTypeInformation -Encoding UTF8
$result | Format-Table Name, Path, SizeGB, FileCount -AutoSize
# DFS namespaces (if used) — often the path users actually see
Write-Host "`n=== DFS namespaces ===" -ForegroundColor Cyan
try {
$roots = Get-DfsnRoot -ErrorAction Stop
if ($roots) {
$dfs = foreach ($r in $roots) {
Get-DfsnFolder -Path "$($r.Path)\*" -ErrorAction SilentlyContinue | ForEach-Object {
$t = Get-DfsnFolderTarget -Path $_.Path -ErrorAction SilentlyContinue
[PSCustomObject]@{ DfsPath = $_.Path; Targets = ($t.TargetPath -join '; ') }
}
}
$dfs | Export-Csv (Join-Path $OutDir "dfs-namespaces.csv") -NoTypeInformation -Encoding UTF8
$dfs | Format-Table -AutoSize
} else { Write-Host " none" }
} catch {
Write-Host " no DFS role / not available on this host"
}
Write-Host "`nResults in: $OutDir" -ForegroundColor Green