PS scripts, web platform, Forgejo CI, xetup.exe launcher
Initial deployment suite for X9.cz MSP Windows 10/11 deployment: - PowerShell scripts 00-11: admin account, bloatware removal, software (winget+Atera), system registry tweaks, default profile, personalization, scheduled tasks, BackInfo desktop info, Windows activation, PC identity/rename, network, Dell Update - Web platform: xetup.x9.cz (nginx), spec/annotation page, /dl shortlink, GitHub mirror - Forgejo Actions CI: auto-build xetup.exe on push, publish to releases/latest - Go xetup.exe: embeds all scripts/assets, per-feature checkboxes, load/save config
This commit is contained in:
commit
c42943cfa8
60 changed files with 10202 additions and 0 deletions
14
.claude/MEMORY.md
Normal file
14
.claude/MEMORY.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Memory Index
|
||||||
|
|
||||||
|
## User
|
||||||
|
- [Filip Zubik - X9.cz](user_filip.md) - MSP owner/developer, Czech communication, pragmatic approach
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
- [Communication preferences](feedback_preferences.md) - Czech, discussion first, interactive reviews, phased approach
|
||||||
|
|
||||||
|
## Project
|
||||||
|
- [Xetup project state](project_xetup_state.md) - Current state, architecture decisions, what's next (as of 2026-04-15)
|
||||||
|
- [Technical findings](project_technical_findings.md) - Deep code analysis, UCPD issue, Win11 compatibility, tools research
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
- [Colleague specs and review](reference_colleague_specs.md) - Where to find colleague's input documents and review results
|
||||||
14
.claude/memory/MEMORY.md
Normal file
14
.claude/memory/MEMORY.md
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Memory Index
|
||||||
|
|
||||||
|
## User
|
||||||
|
- [Filip Zubik - X9.cz](user_filip.md) - MSP owner/developer, Czech communication, pragmatic approach
|
||||||
|
|
||||||
|
## Feedback
|
||||||
|
- [Communication preferences](feedback_preferences.md) - Czech, discussion first, interactive reviews, phased approach
|
||||||
|
|
||||||
|
## Project
|
||||||
|
- [Xetup project state](project_xetup_state.md) - Current state, architecture decisions, what's next (as of 2026-04-15)
|
||||||
|
- [Technical findings](project_technical_findings.md) - Deep code analysis, UCPD issue, Win11 compatibility, tools research
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
- [Colleague specs and review](reference_colleague_specs.md) - Where to find colleague's input documents and review results
|
||||||
15
.claude/memory/feedback_preferences.md
Normal file
15
.claude/memory/feedback_preferences.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
name: Communication and workflow preferences
|
||||||
|
description: How Filip wants to work - Czech communication, discussion before coding, interactive reviews
|
||||||
|
type: feedback
|
||||||
|
---
|
||||||
|
|
||||||
|
- Communicate in Czech (code/comments/logs stay English)
|
||||||
|
- When Filip says "nekodujme, udelame diskusi" - he wants discussion first, not jumping to code
|
||||||
|
- Prefers interactive HTML pages for reviews/decisions (not just markdown)
|
||||||
|
- Likes phased approaches - start simple, evolve
|
||||||
|
- Values colleague's field experience over theoretical best practices
|
||||||
|
- When specs conflict, ask rather than assume
|
||||||
|
- Filip appreciates out-of-the-box thinking (Go binaries, web platforms) but wants practical solutions
|
||||||
|
- Don't over-engineer for 20 machines/month scale
|
||||||
|
- Filip works on macOS, targets Windows - cross-compilation matters
|
||||||
57
.claude/memory/project_technical_findings.md
Normal file
57
.claude/memory/project_technical_findings.md
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
name: Technical findings from deep code analysis and research (2026-04-15)
|
||||||
|
description: Detailed technical analysis results - Win11 compatibility issues, UCPD driver, code quality assessment, and modernization opportunities
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code quality assessment (all scripts in windows-deployment-new/scripts/)
|
||||||
|
- Overall: solid, production-ready for Win10/11 22H2
|
||||||
|
- 3-level registry fallback in 03-system-registry.ps1: direct write → ACL fix (SeTakeOwnershipPrivilege) → SYSTEM scheduled task
|
||||||
|
- Proper hive handling: GC.Collect + WaitForPendingFinalizers + 500ms sleep before reg unload, always in finally block
|
||||||
|
- Error handling: $ErrorActionPreference = "Continue", try/catch everywhere, WARN level for non-critical failures
|
||||||
|
- Logging: every step to C:\Windows\Setup\Scripts\Deploy.log with color-coded console output
|
||||||
|
|
||||||
|
## Critical issues found
|
||||||
|
|
||||||
|
### 1. UCPD.sys (User Choice Protection Driver)
|
||||||
|
- Kernel-mode driver since Feb 2024, v4.3 as of early 2026
|
||||||
|
- Blocks direct registry writes to UserChoice for .pdf, .htm, .html etc.
|
||||||
|
- Our HKCR approach works as system-wide fallback but isn't clean
|
||||||
|
- Fix: disable UCPD service + scheduled task during deployment, set associations, re-enable
|
||||||
|
- Or use SetUserFTA tool (~$20, kolbi.cz)
|
||||||
|
|
||||||
|
### 2. System tray EnableAutoTray=0 broken on 24H2
|
||||||
|
- Win11 23H2/24H2 ignores this registry key
|
||||||
|
- Icon stream cache clearing is a workaround but not 100%
|
||||||
|
- No reliable registry-only solution exists for 24H2
|
||||||
|
|
||||||
|
### 3. OneDrive removal too aggressive
|
||||||
|
- 03-system-registry.ps1 lines 244-273: uninstalls + deletes OneDriveSetup.exe
|
||||||
|
- 04-default-profile.ps1 lines 240-261: removes RunOnce keys + Explorer namespace
|
||||||
|
- Must remove these blocks entirely
|
||||||
|
|
||||||
|
### 4. Edge policies incomplete
|
||||||
|
- Currently only: HideFirstRunExperience, CreateDesktopShortcutDefault
|
||||||
|
- Need to add: BrowserSignin=0, CopilotPageContext=0, NewTabPageContentEnabled=0, StandaloneHubsSidebarEnabled=0, ShowRecommendationsEnabled=0, DefaultBrowserSettingsCampaignEnabled=0, and ~10 more
|
||||||
|
|
||||||
|
### 5. ConfigureStartPins applyOnce
|
||||||
|
- New in 24H2 (KB5062660): {"pinnedList":[], "applyOnce": true}
|
||||||
|
- Applies layout once, then users can customize
|
||||||
|
- Better than our current approach (XML lock + UnlockStartLayout task)
|
||||||
|
|
||||||
|
## Win10/Win11 compatibility matrix
|
||||||
|
- All core registry keys work on both versions
|
||||||
|
- Win11-specific keys (TaskbarAl, ShowCopilotButton, TaskbarDa, TaskbarMn) harmlessly create empty keys on Win10
|
||||||
|
- Scripts handle version differences through graceful degradation
|
||||||
|
|
||||||
|
## Config.json issues
|
||||||
|
- desktopInfo settings (position, fontSize, color) are defined but ignored by 07-desktop-info.ps1
|
||||||
|
- deployment.locale is not used anywhere
|
||||||
|
- Software list has only 3 packages (TODO in SPEC)
|
||||||
|
|
||||||
|
## Tools landscape (researched 2026-04-15)
|
||||||
|
- Chris Titus WinUtil: PS-based, `irm christitus.com/win | iex`, has Win11 Creator tab
|
||||||
|
- Win11Debloat (Raphire): got GUI in Feb 2026, configurable via Apps.json
|
||||||
|
- Sophia Script: 150+ tweaks, most granular but slower
|
||||||
|
- Go binary advantages: bypasses execution policy, single file, no dependencies, cross-compile from macOS
|
||||||
|
- Charmbracelet stack (bubbletea/huh/lipgloss): best for TUI forms in Go
|
||||||
73
.claude/memory/project_xetup_state.md
Normal file
73
.claude/memory/project_xetup_state.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
---
|
||||||
|
name: Xetup project state as of 2026-04-15
|
||||||
|
description: Current state of the xetup Windows deployment project - architecture decisions, what exists, what's planned
|
||||||
|
type: project
|
||||||
|
---
|
||||||
|
|
||||||
|
## What xetup is
|
||||||
|
Automated Windows 10/11 setup for X9.cz MSP clients. Replaces ~3 hours of manual work with a single script/tool.
|
||||||
|
|
||||||
|
## Current repo structure (cleaned 2026-04-15)
|
||||||
|
```
|
||||||
|
xetup/
|
||||||
|
├── review.html ← interactive review page v2 (with colleague comments)
|
||||||
|
├── xetup-review.md ← exported review v1 results from colleague
|
||||||
|
├── xetup-win-setup-spec.md ← original spec from colleague
|
||||||
|
├── xetup-win-setup-novinky.md ← v2 additions from colleague (taskbar pins, explorer, network, admin desc)
|
||||||
|
├── W11.pdf ← reference PDF
|
||||||
|
└── windows-deployment-new/ ← the active codebase
|
||||||
|
├── Deploy-Windows.ps1 ← master script
|
||||||
|
├── CLAUDE.md / SPEC.md
|
||||||
|
├── config/config.json
|
||||||
|
├── assets/
|
||||||
|
│ ├── Backinfo/ ← BackInfo.exe + ini + ps1 (ready to use)
|
||||||
|
│ └── Logo/ ← X9 ico + jpeg (moved here 2026-04-15)
|
||||||
|
└── scripts/
|
||||||
|
├── 00-admin-account.ps1
|
||||||
|
├── 01-bloatware.ps1
|
||||||
|
├── 02-software.ps1
|
||||||
|
├── 03-system-registry.ps1
|
||||||
|
├── 04-default-profile.ps1
|
||||||
|
├── 05-personalization.ps1
|
||||||
|
├── 06-scheduled-tasks.ps1
|
||||||
|
├── 07-desktop-info.ps1 ← TO BE REPLACED by BackInfo
|
||||||
|
└── 08-activation.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key decisions made (2026-04-15 session)
|
||||||
|
1. **BackInfo wins over custom DesktopInfo** - colleague prefers it (INI config, auto-update, centered text). Our 07-desktop-info.ps1 will be deleted.
|
||||||
|
2. **OneDrive must NOT be removed** - current code aggressively deletes it, breaks M365. Must fix 03-system-registry.ps1 and 04-default-profile.ps1.
|
||||||
|
3. **RDP must NOT be removed** - was in SPEC but never implemented (good). Remove from SPEC entirely.
|
||||||
|
4. **Colleague's spec has priority** over our implementation when they overlap.
|
||||||
|
5. **adminx9 account: no password** (changed from config-driven password), FullName = "X9.cz s.r.o."
|
||||||
|
6. **Nextcloud not needed** - assets are in repo, only Atera MSI downloads from web.
|
||||||
|
7. **Flash2 not integrated** - it's the old version of this tool, just for inspiration.
|
||||||
|
8. **Atera Agent**: curl from `https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337` then `msiexec /i setup.msi /qn`
|
||||||
|
|
||||||
|
## Architecture direction (decided 2026-04-15)
|
||||||
|
- **Go TUI launcher** (xetup.exe) - single binary, embeds PS scripts + assets
|
||||||
|
- Charmbracelet stack: bubbletea, huh, lipgloss
|
||||||
|
- Self-update from web (version.json check)
|
||||||
|
- **Web platform** at xetup.x9.cz:
|
||||||
|
- Forgejo for git hosting + issues + CI
|
||||||
|
- Auto-generated documentation from spec.yaml
|
||||||
|
- Comments via Forgejo Issues API
|
||||||
|
- Landing page + download + changelog
|
||||||
|
- Deployment reporting dashboard (later)
|
||||||
|
- **spec.yaml** as single source of truth for both exe and docs
|
||||||
|
|
||||||
|
## What needs to happen next
|
||||||
|
1. Create spec.yaml from all gathered specs + review
|
||||||
|
2. Initialize Go project structure
|
||||||
|
3. Fix PS scripts (OneDrive removal, admin password, BackInfo integration)
|
||||||
|
4. Set up repo (GitHub initially, Forgejo later)
|
||||||
|
5. First Go build with TUI form
|
||||||
|
6. CI pipeline (GitHub Actions)
|
||||||
|
7. Web landing page
|
||||||
|
|
||||||
|
## Technical findings from deep analysis
|
||||||
|
- UCPD kernel driver (since Feb 2024) blocks PDF default association via UserChoice - need to disable UCPD during deployment
|
||||||
|
- System tray "show all icons" broken in Win11 24H2 - EnableAutoTray=0 ignored
|
||||||
|
- Edge needs ~15 more policy keys than we currently set
|
||||||
|
- ConfigureStartPins has new applyOnce property in 24H2
|
||||||
|
- Current code quality is solid: 3-level registry fallback, proper hive handling with GC+finally
|
||||||
24
.claude/memory/reference_colleague_specs.md
Normal file
24
.claude/memory/reference_colleague_specs.md
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
name: Colleague spec documents and review results
|
||||||
|
description: Where to find colleague's input - specs, review comments, and novinky files in repo root
|
||||||
|
type: reference
|
||||||
|
---
|
||||||
|
|
||||||
|
## Input documents from colleague (in repo root)
|
||||||
|
- `xetup-win-setup-spec.md` - original full spec (15 sections covering directory structure, personalization, taskbar, power, proxy, PC rename, bloatware, SW install, BackInfo, Edge, bootstrap launcher)
|
||||||
|
- `xetup-win-setup-novinky.md` - v2 additions: A) taskbar pinned apps (admin vs user XML layout), B) Explorer settings (ShowRecent/Frequent off, FullPath), C) network discovery + private network, D) admin account FullName
|
||||||
|
- `xetup-review.md` - exported review v1 with colleague's comments
|
||||||
|
- `W11.pdf` - reference PDF from colleague
|
||||||
|
|
||||||
|
## Key colleague comments from review v1
|
||||||
|
- Nextcloud not needed for assets
|
||||||
|
- adminX9 without password
|
||||||
|
- Flash2 is just the old version, for inspiration only
|
||||||
|
- OneDrive and RDP problems reported from first version
|
||||||
|
- Atera: specific curl URL provided with cid=31 and aeid parameter
|
||||||
|
- Logo files added to repo (now in assets/Logo/)
|
||||||
|
|
||||||
|
## Review v2
|
||||||
|
- `review.html` in repo root - interactive review page with all steps organized into 9 groups
|
||||||
|
- Uses localStorage prefix `xr2-` for state
|
||||||
|
- 27 steps total, incorporates all colleague feedback + novinky
|
||||||
17
.claude/memory/user_filip.md
Normal file
17
.claude/memory/user_filip.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
---
|
||||||
|
name: Filip Zubik - X9.cz
|
||||||
|
description: MSP owner/developer at X9.cz, builds Windows deployment automation, prefers Czech communication, pragmatic approach
|
||||||
|
type: user
|
||||||
|
---
|
||||||
|
|
||||||
|
- Runs X9.cz - MSP (Managed Service Provider) deploying ~20 Windows machines/month
|
||||||
|
- Developer + business owner - makes architectural decisions
|
||||||
|
- Works on macOS, deploys to Windows
|
||||||
|
- Prefers Czech for communication, English for code/comments/logs
|
||||||
|
- Pragmatic - wants working solutions, not over-engineering
|
||||||
|
- Open to modern approaches (Go binaries, TUI, web platforms)
|
||||||
|
- Has a colleague ("kolega") who is the hands-on technician doing the actual deployments
|
||||||
|
- Colleague provides real-world specs and feedback from field experience
|
||||||
|
- Filip values colleague's input - "spec kolegy ma prednost"
|
||||||
|
- Likes interactive review workflows (HTML review pages with approve/reject/discuss)
|
||||||
|
- Thinks long-term - wants living documentation, feedback loops, continuous improvement
|
||||||
76
.forgejo/workflows/release.yml
Normal file
76
.forgejo/workflows/release.yml
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- '**.go'
|
||||||
|
- 'go.mod'
|
||||||
|
- 'go.sum'
|
||||||
|
- 'scripts/**'
|
||||||
|
- 'assets/**'
|
||||||
|
- 'embed.go'
|
||||||
|
- '.forgejo/workflows/release.yml'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-release:
|
||||||
|
# Runner label 'ubuntu-latest' maps to golang:1.24-alpine container (see runner config)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: sh
|
||||||
|
working-directory: /repo
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup
|
||||||
|
working-directory: /
|
||||||
|
run: |
|
||||||
|
apk add --no-cache git curl jq mingw-w64-gcc
|
||||||
|
git clone --depth=1 \
|
||||||
|
"http://x9:${{ secrets.FORGEJO_TOKEN }}@xetup-forgejo:3000/${{ github.repository }}.git" \
|
||||||
|
/repo
|
||||||
|
cd /repo
|
||||||
|
git checkout "${{ github.sha }}"
|
||||||
|
|
||||||
|
- name: Build xetup.exe
|
||||||
|
run: |
|
||||||
|
CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc \
|
||||||
|
GOOS=windows GOARCH=amd64 \
|
||||||
|
go build -ldflags="-s -w -H windowsgui" -o xetup.exe ./cmd/xetup/
|
||||||
|
echo "Built: $(ls -lh xetup.exe | awk '{print $5}')"
|
||||||
|
|
||||||
|
- name: Publish latest release
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.FORGEJO_TOKEN }}
|
||||||
|
API: http://xetup-forgejo:3000/api/v1
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
SHORT=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||||
|
|
||||||
|
# Delete existing 'latest' release and tag to recreate cleanly
|
||||||
|
RID=$(curl -sf -H "Authorization: token $TOKEN" \
|
||||||
|
"$API/repos/$REPO/releases/tags/latest" | jq -r '.id // empty')
|
||||||
|
if [ -n "$RID" ]; then
|
||||||
|
curl -sf -X DELETE -H "Authorization: token $TOKEN" \
|
||||||
|
"$API/repos/$REPO/releases/$RID" || true
|
||||||
|
fi
|
||||||
|
curl -sf -X DELETE -H "Authorization: token $TOKEN" \
|
||||||
|
"$API/repos/$REPO/tags/latest" || true
|
||||||
|
|
||||||
|
# Create new 'latest' release
|
||||||
|
RID=$(curl -sf -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"$API/repos/$REPO/releases" \
|
||||||
|
-d "{\"tag_name\":\"latest\",\"name\":\"latest\",\"body\":\"Auto-built from ${SHORT}\",\"prerelease\":true}" \
|
||||||
|
| jq -r '.id')
|
||||||
|
|
||||||
|
# Upload xetup.exe
|
||||||
|
curl -sf -X POST \
|
||||||
|
-H "Authorization: token $TOKEN" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
"$API/repos/$REPO/releases/$RID/assets?name=xetup.exe" \
|
||||||
|
--data-binary @xetup.exe
|
||||||
|
|
||||||
|
echo "Released xetup.exe (commit ${SHORT})"
|
||||||
|
|
||||||
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Secrets
|
||||||
|
.env
|
||||||
|
*.key
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
flash.zip
|
||||||
|
|
||||||
|
# Large reference files
|
||||||
|
W11.pdf
|
||||||
175
CLAUDE.md
Normal file
175
CLAUDE.md
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
# CLAUDE.md - Instructions for Claude Code
|
||||||
|
|
||||||
|
## Project context
|
||||||
|
|
||||||
|
MSP deployment script for X9.cz - automated preparation of new Windows 10/11 computers for clients.
|
||||||
|
Replaces ~3 hours of manual setup with a single PowerShell script (evolving toward Go TUI launcher).
|
||||||
|
|
||||||
|
**Key parameters:**
|
||||||
|
- Target OS: Windows 10 and Windows 11 (x64), including unsupported HW
|
||||||
|
- Execution: as Administrator on already-installed Windows (not WinPE/autounattend)
|
||||||
|
- Volume: ~20 machines per month, various clients
|
||||||
|
- Operator: MSP technician on-site at client
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Communication
|
||||||
|
|
||||||
|
- Communicate with the user in Czech
|
||||||
|
- Code, comments, log messages: English only (no diacritics rule still applies)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Repo structure
|
||||||
|
|
||||||
|
```
|
||||||
|
windows-deployment-new/
|
||||||
|
├── CLAUDE.md <- this file
|
||||||
|
├── SPEC.md <- technical specification
|
||||||
|
├── Deploy-Windows.ps1 <- master script (entry point)
|
||||||
|
├── scripts/
|
||||||
|
│ ├── 00-admin-account.ps1 <- create hidden admin account
|
||||||
|
│ ├── 01-bloatware.ps1 <- remove AppX, Capabilities, Features
|
||||||
|
│ ├── 02-software.ps1 <- winget installs + Adobe PDF default
|
||||||
|
│ ├── 03-system-registry.ps1 <- HKLM tweaks
|
||||||
|
│ ├── 04-default-profile.ps1 <- C:\Users\Default\NTUSER.DAT changes
|
||||||
|
│ ├── 05-personalization.ps1 <- colors, wallpaper, theme
|
||||||
|
│ ├── 06-scheduled-tasks.ps1 <- register scheduled tasks
|
||||||
|
│ ├── 07-desktop-info.ps1 <- TO BE DELETED (replaced by BackInfo)
|
||||||
|
│ └── 08-activation.ps1 <- Windows activation via slmgr
|
||||||
|
├── config/
|
||||||
|
│ └── config.json <- per-client config
|
||||||
|
├── assets/
|
||||||
|
│ ├── Backinfo/ <- BackInfo.exe + .ini + backinfo_W11.ps1
|
||||||
|
│ └── Logo/ <- X9-ikona.ico, X9-logo.jpeg
|
||||||
|
└── tests/
|
||||||
|
└── Test-Deployment.ps1 <- post-deployment verification
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conventions and rules
|
||||||
|
|
||||||
|
### PowerShell
|
||||||
|
- Always `#Requires -RunAsAdministrator` in master script
|
||||||
|
- `$ErrorActionPreference = "Continue"` - script must survive partial failures
|
||||||
|
- Log every step to `C:\Windows\Setup\Scripts\Deploy.log`
|
||||||
|
- Logging via `Write-Log` function defined in master script
|
||||||
|
- `Invoke-Step` function wraps every step - catches errors, logs, continues
|
||||||
|
- Comments in English, code in English
|
||||||
|
- NO diacritics - no accented characters anywhere: not in comments, not in user messages, not in log output
|
||||||
|
- NO emoticons - not in comments, not in output messages
|
||||||
|
- Reason: encoding issues across systems, log readability, compatibility
|
||||||
|
|
||||||
|
### Master script structure
|
||||||
|
```powershell
|
||||||
|
# 1. Load config.json
|
||||||
|
# 2. Run individual scripts in order
|
||||||
|
# 3. Print summary report at end (OK/ERROR counts)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Master script switches
|
||||||
|
| Switch | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| `-SkipBloatware` | Skip step 1 |
|
||||||
|
| `-SkipSoftware` | Skip step 2 |
|
||||||
|
| `-SkipDefaultProfile` | Skip step 4 |
|
||||||
|
| `-DryRun` | Run without changes, log only |
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Test VM: Windows 10/11 x64 on VMware ESXi (X9.cz internal infrastructure)
|
||||||
|
- Before each test: take snapshot
|
||||||
|
- After test: revert snapshot
|
||||||
|
- Dev environment: x64 VM only - NOT ARM (no Parallels/Apple Silicon for testing)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Important notes
|
||||||
|
|
||||||
|
### BackInfo (replaces custom DesktopInfo)
|
||||||
|
BackInfo.exe IS used. Located in assets/Backinfo/. Deployment:
|
||||||
|
1. Copy assets/Backinfo/ to C:\Program Files\Backinfo\
|
||||||
|
2. Run backinfo_W11.ps1 (detects OS, writes registry, creates Startup shortcut)
|
||||||
|
3. BackInfo.exe auto-starts on every logon, reads INI, renders BMP with system info
|
||||||
|
- Configurable via BackInfo.ini (fonts, positions, data sources)
|
||||||
|
- Displays: hostname (centered, large), username, OS, HW info, network info
|
||||||
|
- DELETE 07-desktop-info.ps1 - no longer needed
|
||||||
|
|
||||||
|
### Adobe Reader as default PDF app
|
||||||
|
- After install: set .pdf -> AcroRd32 association
|
||||||
|
- Scheduled task PDF-DefaultApp restores association on every logon (guard against Edge overwriting it)
|
||||||
|
- NOTE: UCPD.sys (kernel driver since Feb 2024) blocks UserChoice writes. Consider disabling UCPD during deployment.
|
||||||
|
|
||||||
|
### Default Profile
|
||||||
|
- Changes to C:\Users\Default\NTUSER.DAT via reg load / reg unload
|
||||||
|
- Applies to all new users - critical for MSP deployment
|
||||||
|
- Currently logged-in user gets changes via direct write to HKCU
|
||||||
|
|
||||||
|
### Winget
|
||||||
|
- Always use --accept-package-agreements --accept-source-agreements
|
||||||
|
- Check winget availability before running installs
|
||||||
|
- Log result of every install
|
||||||
|
|
||||||
|
### Atera Agent
|
||||||
|
- Download: `Invoke-WebRequest -Uri "https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337" -OutFile setup.msi`
|
||||||
|
- Install: `msiexec /i setup.msi /qn`
|
||||||
|
|
||||||
|
### Admin account (adminx9)
|
||||||
|
- NO PASSWORD (changed from previous version)
|
||||||
|
- FullName = "X9.cz s.r.o." (via ADSI)
|
||||||
|
- Hidden from login screen
|
||||||
|
- Added to Administrators group
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DO NOT
|
||||||
|
|
||||||
|
- Do not use $ErrorActionPreference = "Stop" - script must survive partial failure
|
||||||
|
- Do not remove Calculator (Microsoft.WindowsCalculator) - intentionally kept
|
||||||
|
- Do not use ARM VM for testing
|
||||||
|
- Do not write scripts depending on specific username - script is universal
|
||||||
|
- Do not use hardcoded paths that do not exist on clean Windows
|
||||||
|
- NO diacritics - no accented characters in any part of any script
|
||||||
|
- NO emoticons - none in comments, log messages or output
|
||||||
|
- Do not remove OneDrive - must remain installable for M365
|
||||||
|
- Do not remove RDP/RDS - must remain functional
|
||||||
|
- Do not remove Microsoft-RemoteDesktopConnection from Optional Features
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Planned changes (from review v2, 2026-04-15)
|
||||||
|
|
||||||
|
### Must fix
|
||||||
|
- [ ] Remove OneDrive uninstall from 03-system-registry.ps1 and 04-default-profile.ps1
|
||||||
|
- [ ] Remove password from admin account, add FullName = "X9.cz s.r.o."
|
||||||
|
- [ ] Delete 07-desktop-info.ps1, replace with BackInfo deployment step
|
||||||
|
- [ ] Add powercfg settings (standby-timeout-ac 0, monitor-timeout-ac 60, etc.)
|
||||||
|
- [ ] Add proxy auto-detect disable (AutoDetect = 0)
|
||||||
|
- [ ] Add Atera Agent install step
|
||||||
|
- [ ] Extend Edge policies (~15 more keys)
|
||||||
|
|
||||||
|
### New features (from colleague spec v2)
|
||||||
|
- [ ] Taskbar pinned apps: admin vs user variants via XML layout + -ProfileType parameter
|
||||||
|
- [ ] Explorer: ShowRecent=0, ShowFrequent=0, FullPath=1 in CabinetState
|
||||||
|
- [ ] Network discovery: enable ping, set private network profile (post-restart step)
|
||||||
|
- [ ] PC rename: Rename-Computer as final step before restart
|
||||||
|
- [ ] C:\X9 directory structure with custom folder icon
|
||||||
|
|
||||||
|
### Architecture evolution
|
||||||
|
- [ ] Go TUI launcher (xetup.exe) embedding PS scripts
|
||||||
|
- [ ] spec.yaml as single source of truth
|
||||||
|
- [ ] Web platform at xetup.x9.cz (Forgejo + docs + comments)
|
||||||
|
- [ ] Self-update mechanism in xetup.exe
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
| # | Question | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | BackInfo replacement | DONE - using BackInfo.exe from assets/ |
|
||||||
|
| 2 | Complete SW list for winget | TODO - list incomplete |
|
||||||
|
| 3 | Per-client variability via config.json | FUTURE |
|
||||||
|
| 4 | Admin account adminx9 | DECIDED - no password, FullName "X9.cz s.r.o." |
|
||||||
|
| 5 | UCPD driver workaround for PDF default | TODO - disable during deployment |
|
||||||
|
| 6 | Atera MFA bypass | OPEN - does aeid parameter avoid MFA? |
|
||||||
276
Deploy-Windows.ps1
Normal file
276
Deploy-Windows.ps1
Normal file
|
|
@ -0,0 +1,276 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$SkipBloatware,
|
||||||
|
[switch]$SkipSoftware,
|
||||||
|
[switch]$SkipDefaultProfile,
|
||||||
|
[switch]$DryRun,
|
||||||
|
[ValidateSet("default","admin","user")]
|
||||||
|
[string]$ProfileType = "default"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Paths
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$ScriptRoot = $PSScriptRoot
|
||||||
|
$LogDir = "C:\Windows\Setup\Scripts"
|
||||||
|
$LogFile = "$LogDir\Deploy.log"
|
||||||
|
$ConfigFile = "$ScriptRoot\config\config.json"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Logging
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
function Write-Log {
|
||||||
|
param(
|
||||||
|
[string]$Message,
|
||||||
|
[ValidateSet("INFO","OK","ERROR","WARN","STEP")]
|
||||||
|
[string]$Level = "INFO"
|
||||||
|
)
|
||||||
|
$timestamp = Get-Date -Format "HH:mm:ss"
|
||||||
|
$line = "[$timestamp] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
switch ($Level) {
|
||||||
|
"OK" { Write-Host $line -ForegroundColor Green }
|
||||||
|
"ERROR" { Write-Host $line -ForegroundColor Red }
|
||||||
|
"WARN" { Write-Host $line -ForegroundColor Yellow }
|
||||||
|
"STEP" { Write-Host $line -ForegroundColor Cyan }
|
||||||
|
default { Write-Host $line }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step runner - catches errors, logs, always continues
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$StepResults = [System.Collections.Generic.List[hashtable]]::new()
|
||||||
|
|
||||||
|
function Invoke-Step {
|
||||||
|
param(
|
||||||
|
[string]$Name,
|
||||||
|
[scriptblock]$Action
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Log "---- $Name ----" -Level STEP
|
||||||
|
|
||||||
|
if ($DryRun) {
|
||||||
|
Write-Log "DryRun - skipping execution" -Level WARN
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "DRYRUN" })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
& $Action
|
||||||
|
Write-Log "$Name - OK" -Level OK
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "OK" })
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "$Name - ERROR: $_" -Level ERROR
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "ERROR" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Init
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (-not (Test-Path $LogDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
Write-Log "Deploy-Windows.ps1 started" -Level INFO
|
||||||
|
Write-Log "Computer: $env:COMPUTERNAME" -Level INFO
|
||||||
|
Write-Log "User: $env:USERNAME" -Level INFO
|
||||||
|
Write-Log "Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -Level INFO
|
||||||
|
if ($DryRun) { Write-Log "Mode: DRY RUN" -Level WARN }
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Load config
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$Config = $null
|
||||||
|
Invoke-Step -Name "Load config.json" -Action {
|
||||||
|
if (-not (Test-Path $ConfigFile)) {
|
||||||
|
throw "config.json not found: $ConfigFile"
|
||||||
|
}
|
||||||
|
$script:Config = Get-Content $ConfigFile -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
Write-Log "Config loaded from $ConfigFile" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Build step enable/disable map from config + CLI overrides
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$stepsEnabled = @{
|
||||||
|
adminAccount = $true
|
||||||
|
bloatware = $true
|
||||||
|
software = $true
|
||||||
|
systemRegistry = $true
|
||||||
|
defaultProfile = $true
|
||||||
|
personalization = $true
|
||||||
|
scheduledTasks = $true
|
||||||
|
backinfo = $true
|
||||||
|
network = $true
|
||||||
|
pcIdentity = $true
|
||||||
|
activation = $true
|
||||||
|
dellUpdate = $true
|
||||||
|
}
|
||||||
|
if ($Config -and $Config.steps) {
|
||||||
|
foreach ($key in @($stepsEnabled.Keys)) {
|
||||||
|
$val = $Config.steps.$key
|
||||||
|
if ($null -ne $val) { $stepsEnabled[$key] = [bool]$val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# CLI switches override config.steps
|
||||||
|
if ($SkipBloatware) { $stepsEnabled['bloatware'] = $false }
|
||||||
|
if ($SkipSoftware) { $stepsEnabled['software'] = $false }
|
||||||
|
if ($SkipDefaultProfile) { $stepsEnabled['defaultProfile'] = $false }
|
||||||
|
|
||||||
|
function Skip-Step {
|
||||||
|
param([string]$Name)
|
||||||
|
Write-Log "$Name - SKIPPED (disabled in config)" -Level WARN
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "SKIPPED" })
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 0a - Admin account
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['adminAccount']) {
|
||||||
|
Invoke-Step -Name "Step 0a - Admin account" -Action {
|
||||||
|
& "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 0a - Admin account" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 0b - Windows activation
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['activation']) {
|
||||||
|
Invoke-Step -Name "Step 0b - Windows activation" -Action {
|
||||||
|
& "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 0b - Windows activation" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 1 - Bloatware removal
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['bloatware']) {
|
||||||
|
Invoke-Step -Name "Step 1 - Bloatware removal" -Action {
|
||||||
|
& "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 1 - Bloatware removal" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 2 - Software installation
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['software']) {
|
||||||
|
Invoke-Step -Name "Step 2 - Software installation" -Action {
|
||||||
|
& "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 2 - Software installation" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 3 - System registry (HKLM)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['systemRegistry']) {
|
||||||
|
Invoke-Step -Name "Step 3 - System registry" -Action {
|
||||||
|
& "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 3 - System registry" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 4 - Default profile (NTUSER.DAT)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['defaultProfile']) {
|
||||||
|
Invoke-Step -Name "Step 4 - Default profile" -Action {
|
||||||
|
& "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile -ProfileType $ProfileType
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 4 - Default profile" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 5 - Personalization
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['personalization']) {
|
||||||
|
Invoke-Step -Name "Step 5 - Personalization" -Action {
|
||||||
|
& "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 5 - Personalization" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 6 - Scheduled tasks
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['scheduledTasks']) {
|
||||||
|
Invoke-Step -Name "Step 6 - Scheduled tasks" -Action {
|
||||||
|
& "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 6 - Scheduled tasks" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 7 - BackInfo
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['backinfo']) {
|
||||||
|
Invoke-Step -Name "Step 7 - BackInfo" -Action {
|
||||||
|
& "$ScriptRoot\scripts\07-backinfo.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 7 - BackInfo" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 9 - Network
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['network']) {
|
||||||
|
Invoke-Step -Name "Step 9 - Network" -Action {
|
||||||
|
& "$ScriptRoot\scripts\10-network.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 9 - Network" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 11 - Dell Command | Update (auto-skipped on non-Dell hardware)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['dellUpdate']) {
|
||||||
|
Invoke-Step -Name "Step 11 - Dell Command | Update" -Action {
|
||||||
|
& "$ScriptRoot\scripts\11-dell-update.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 11 - Dell Command | Update" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 10 - PC identity (rename + C:\X9) - runs last, rename needs restart
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['pcIdentity']) {
|
||||||
|
Invoke-Step -Name "Step 10 - PC identity" -Action {
|
||||||
|
& "$ScriptRoot\scripts\09-pc-identity.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 10 - PC identity" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
Write-Log "SUMMARY" -Level INFO
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
|
||||||
|
$countOK = ($StepResults | Where-Object { $_.Status -eq "OK" }).Count
|
||||||
|
$countError = ($StepResults | Where-Object { $_.Status -eq "ERROR" }).Count
|
||||||
|
$countSkipped = ($StepResults | Where-Object { $_.Status -eq "SKIPPED" }).Count
|
||||||
|
$countDryRun = ($StepResults | Where-Object { $_.Status -eq "DRYRUN" }).Count
|
||||||
|
|
||||||
|
foreach ($r in $StepResults) {
|
||||||
|
$lvl = switch ($r.Status) {
|
||||||
|
"OK" { "OK" }
|
||||||
|
"ERROR" { "ERROR" }
|
||||||
|
"SKIPPED" { "WARN" }
|
||||||
|
"DRYRUN" { "WARN" }
|
||||||
|
}
|
||||||
|
Write-Log "$($r.Status.PadRight(8)) $($r.Name)" -Level $lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "----------------------------------------" -Level INFO
|
||||||
|
Write-Log "OK: $countOK ERROR: $countError SKIPPED: $countSkipped DRYRUN: $countDryRun" -Level INFO
|
||||||
|
Write-Log "Log saved to: $LogFile" -Level INFO
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
|
||||||
|
if ($countError -gt 0) {
|
||||||
|
Write-Log "Deployment finished with errors. Review log: $LogFile" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
Write-Log "Deployment finished successfully." -Level OK
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
137
Remove-ClaudeCode.ps1
Normal file
137
Remove-ClaudeCode.ps1
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Claude Code - odstraneni citlivych dat a volitelne cele instalace
|
||||||
|
.USAGE
|
||||||
|
# Jen citliva data (API key + repo):
|
||||||
|
.\Remove-ClaudeCode.ps1 -RepoPath "C:\Projects\windows-deployment"
|
||||||
|
|
||||||
|
# Vse vcetne Claude Code a Node.js:
|
||||||
|
.\Remove-ClaudeCode.ps1 -RepoPath "C:\Projects\windows-deployment" -Full
|
||||||
|
#>
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string] $RepoPath,
|
||||||
|
|
||||||
|
[switch] $Full
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Step { param([string]$Msg) Write-Host "`n[REMOVE] $Msg" -ForegroundColor Yellow }
|
||||||
|
function Write-OK { param([string]$Msg) Write-Host " OK: $Msg" -ForegroundColor Green }
|
||||||
|
function Write-Skip { param([string]$Msg) Write-Host " SKIP: $Msg" -ForegroundColor DarkGray }
|
||||||
|
|
||||||
|
Write-Host "`n========================================" -ForegroundColor Red
|
||||||
|
Write-Host " Claude Code Cleanup" -ForegroundColor Red
|
||||||
|
Write-Host "========================================`n" -ForegroundColor Red
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 1. API KEY
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Removing ANTHROPIC_API_KEY..."
|
||||||
|
|
||||||
|
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $null, "User")
|
||||||
|
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $null, "Machine")
|
||||||
|
$env:ANTHROPIC_API_KEY = $null
|
||||||
|
Write-OK "API key removed from environment variables"
|
||||||
|
|
||||||
|
# Claude Code si uklada API key take v ~/.claude
|
||||||
|
$claudeConfig = Join-Path $HOME ".claude"
|
||||||
|
if (Test-Path $claudeConfig) {
|
||||||
|
Remove-Item $claudeConfig -Recurse -Force
|
||||||
|
Write-OK "Removed ~/.claude config directory"
|
||||||
|
} else {
|
||||||
|
Write-Skip "~/.claude not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 2. REPO
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Removing repository at $RepoPath..."
|
||||||
|
|
||||||
|
if (Test-Path $RepoPath) {
|
||||||
|
# Nejdriv over ze je to skutecne git repo - pojistka
|
||||||
|
$gitDir = Join-Path $RepoPath ".git"
|
||||||
|
if (Test-Path $gitDir) {
|
||||||
|
Remove-Item $RepoPath -Recurse -Force
|
||||||
|
Write-OK "Repository removed"
|
||||||
|
} else {
|
||||||
|
Write-Host " WARN: $RepoPath does not look like a git repo. Skipping for safety." -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Skip "Repo path not found: $RepoPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 3. GIT CREDENTIALS (pokud byly ulozeny)
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Clearing git credentials for repo..."
|
||||||
|
|
||||||
|
try {
|
||||||
|
git credential reject | Out-Null
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
# Windows Credential Manager - GitHub tokeny
|
||||||
|
try {
|
||||||
|
$creds = cmdkey /list 2>$null | Select-String "github"
|
||||||
|
foreach ($cred in $creds) {
|
||||||
|
$target = ($cred -split '\s+') | Where-Object { $_ -like "*github*" } | Select-Object -First 1
|
||||||
|
if ($target) {
|
||||||
|
cmdkey /delete:$target | Out-Null
|
||||||
|
Write-OK "Removed credential: $target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Skip "No GitHub credentials found in Credential Manager"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 4. VOLITELNE - Claude Code + Node.js
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
if ($Full) {
|
||||||
|
Write-Step "Uninstalling Claude Code..."
|
||||||
|
try {
|
||||||
|
npm uninstall -g @anthropic-ai/claude-code
|
||||||
|
Write-OK "Claude Code uninstalled"
|
||||||
|
} catch {
|
||||||
|
Write-Skip "Claude Code not installed via npm or npm not available"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Step "Uninstalling Node.js..."
|
||||||
|
try {
|
||||||
|
winget uninstall OpenJS.NodeJS.LTS --silent
|
||||||
|
Write-OK "Node.js uninstalled"
|
||||||
|
} catch {
|
||||||
|
Write-Skip "Node.js not found via winget - remove manually if needed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 5. POWERSHELL HISTORY (muze obsahovat API key z parametru)
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Clearing PowerShell history..."
|
||||||
|
|
||||||
|
$historyPath = (Get-PSReadlineOption).HistorySavePath
|
||||||
|
if ($historyPath -and (Test-Path $historyPath)) {
|
||||||
|
# Vymaz pouze radky obsahujici ApiKey / sk-ant
|
||||||
|
$lines = Get-Content $historyPath | Where-Object { $_ -notmatch 'ApiKey|sk-ant-|ANTHROPIC' }
|
||||||
|
$lines | Set-Content $historyPath
|
||||||
|
Write-OK "Sensitive lines removed from PS history"
|
||||||
|
} else {
|
||||||
|
Write-Skip "PS history file not found"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# SUMMARY
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Host "`n========================================" -ForegroundColor Green
|
||||||
|
Write-Host " Cleanup complete!" -ForegroundColor Green
|
||||||
|
if ($Full) {
|
||||||
|
Write-Host " Removed: API key, repo, ~/.claude, Node.js, Claude Code" -ForegroundColor White
|
||||||
|
} else {
|
||||||
|
Write-Host " Removed: API key, repo, ~/.claude config" -ForegroundColor White
|
||||||
|
Write-Host " Node.js and Claude Code kept (use -Full to remove)" -ForegroundColor DarkGray
|
||||||
|
}
|
||||||
|
Write-Host "========================================`n" -ForegroundColor Green
|
||||||
125
Run.cmd
Normal file
125
Run.cmd
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Auto-elevate to Administrator if not already elevated
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo Requesting administrator privileges...
|
||||||
|
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Paths
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
set "DEPLOY_PS1=%SCRIPT_DIR%Deploy-Windows.ps1"
|
||||||
|
set "CONFIG_JSON=%SCRIPT_DIR%config\config.json"
|
||||||
|
set "CONFIG_EDITOR=%SCRIPT_DIR%config-editor.hta"
|
||||||
|
set "LOG_FILE=C:\Windows\Setup\Scripts\Deploy.log"
|
||||||
|
|
||||||
|
:MENU
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo ================================================
|
||||||
|
echo X9 - Windows Deployment
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
echo Config : %CONFIG_JSON%
|
||||||
|
echo Log : %LOG_FILE%
|
||||||
|
echo.
|
||||||
|
echo [1] Full deployment (uses config.json)
|
||||||
|
echo [2] Dry run (no changes, log only)
|
||||||
|
echo [3] Skip bloatware removal
|
||||||
|
echo [4] Skip software install
|
||||||
|
echo [5] Open config editor (config-editor.hta)
|
||||||
|
echo [0] Exit
|
||||||
|
echo.
|
||||||
|
set /p CHOICE=" Select [0-5]: "
|
||||||
|
|
||||||
|
if "%CHOICE%"=="0" goto EXIT
|
||||||
|
if "%CHOICE%"=="1" goto FULL
|
||||||
|
if "%CHOICE%"=="2" goto DRYRUN
|
||||||
|
if "%CHOICE%"=="3" goto SKIP_BLOATWARE
|
||||||
|
if "%CHOICE%"=="4" goto SKIP_SOFTWARE
|
||||||
|
if "%CHOICE%"=="5" goto OPEN_EDITOR
|
||||||
|
|
||||||
|
echo Invalid choice. Try again.
|
||||||
|
timeout /t 2 >nul
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [1] Full deployment
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:FULL
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting full deployment...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%"
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [2] Dry run
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:DRYRUN
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting dry run (no changes will be made)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -DryRun
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [3] Skip bloatware
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:SKIP_BLOATWARE
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting deployment (bloatware removal skipped)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipBloatware
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [4] Skip software
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:SKIP_SOFTWARE
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting deployment (software install skipped)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipSoftware
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [5] Config editor
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:OPEN_EDITOR
|
||||||
|
if not exist "%CONFIG_EDITOR%" (
|
||||||
|
echo ERROR: config-editor.hta not found: %CONFIG_EDITOR%
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
)
|
||||||
|
start "" mshta.exe "%CONFIG_EDITOR%"
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Done
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:DONE
|
||||||
|
echo.
|
||||||
|
echo ================================================
|
||||||
|
echo Deployment finished.
|
||||||
|
echo Log: %LOG_FILE%
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:EXIT
|
||||||
|
endlocal
|
||||||
|
exit /b 0
|
||||||
293
SPEC.md
Normal file
293
SPEC.md
Normal file
|
|
@ -0,0 +1,293 @@
|
||||||
|
# MSP Windows Deployment - Specification (SPEC.md)
|
||||||
|
|
||||||
|
> Version: 0.2 (draft)
|
||||||
|
> Author: X9.cz
|
||||||
|
> Purpose: Automated preparation of new Windows 10/11 computers for clients
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Script replaces ~3 hours of manual computer setup. Run once as Administrator on
|
||||||
|
already-installed Windows, performs everything automatically, saves result to Default
|
||||||
|
Profile so settings apply to every subsequent user.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Windows 10 or Windows 11 (x64)
|
||||||
|
- Run as Administrator
|
||||||
|
- Internet connection (for winget installs)
|
||||||
|
- Computer received either as clean OEM install or with manufacturer pre-installed Windows
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What the script does NOT do
|
||||||
|
|
||||||
|
- Does not install Windows (not an autounattend.xml for clean install)
|
||||||
|
- Does not create images
|
||||||
|
- Does not manage the computer ongoing (one-time deployment)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Script structure
|
||||||
|
|
||||||
|
Script is divided into steps. Each step logs its result. Steps can be skipped with switches.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 0a - Admin account
|
||||||
|
|
||||||
|
Creates local admin account `adminx9`:
|
||||||
|
- Password from `config.json` (`adminAccount.password`)
|
||||||
|
- Added to Administrators group
|
||||||
|
- Password never expires, user cannot change password
|
||||||
|
- Hidden from Windows login screen (SpecialAccounts\UserList = 0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 0b - Windows activation
|
||||||
|
|
||||||
|
Activates Windows using product key from config:
|
||||||
|
- Key from `config.json` (`activation.productKey`) - set to real MAK/retail key for production
|
||||||
|
- Falls back to GVLK (KMS client key) matched by detected OS edition
|
||||||
|
- Optional KMS server via `activation.kmsServer`
|
||||||
|
- If already activated, skips silently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 1 - Bloatware removal
|
||||||
|
|
||||||
|
### 1a - AppX packages (UWP apps)
|
||||||
|
|
||||||
|
Removed for all users (-AllUsers) and from provisioned packages (so they do not return for new users).
|
||||||
|
|
||||||
|
| Package | Description |
|
||||||
|
|---|---|
|
||||||
|
| Microsoft.Microsoft3DViewer | 3D Viewer |
|
||||||
|
| Microsoft.BingSearch | Bing Search |
|
||||||
|
| Microsoft.WindowsCamera | Camera |
|
||||||
|
| Clipchamp.Clipchamp | Clipchamp video editor |
|
||||||
|
| Microsoft.WindowsAlarms | Clock / Alarm |
|
||||||
|
| Microsoft.Copilot | Copilot AI |
|
||||||
|
| Microsoft.549981C3F5F10 | Cortana |
|
||||||
|
| Microsoft.Windows.DevHome | Dev Home |
|
||||||
|
| MicrosoftCorporationII.MicrosoftFamily | Family Safety |
|
||||||
|
| Microsoft.WindowsFeedbackHub | Feedback Hub |
|
||||||
|
| Microsoft.Edge.GameAssist | Game Assist |
|
||||||
|
| Microsoft.GetHelp | Help |
|
||||||
|
| Microsoft.Getstarted | Tips / Get Started |
|
||||||
|
| microsoft.windowscommunicationsapps | Mail and Calendar |
|
||||||
|
| Microsoft.WindowsMaps | Maps |
|
||||||
|
| Microsoft.MixedReality.Portal | Mixed Reality |
|
||||||
|
| Microsoft.BingNews | News |
|
||||||
|
| Microsoft.MicrosoftOfficeHub | Office Hub |
|
||||||
|
| Microsoft.Office.OneNote | OneNote |
|
||||||
|
| Microsoft.OutlookForWindows | Outlook (new) |
|
||||||
|
| Microsoft.Paint | Paint (new UWP) |
|
||||||
|
| Microsoft.MSPaint | Paint (legacy) |
|
||||||
|
| Microsoft.People | People |
|
||||||
|
| Microsoft.Windows.Photos | Photos |
|
||||||
|
| Microsoft.PowerAutomateDesktop | Power Automate |
|
||||||
|
| MicrosoftCorporationII.QuickAssist | Quick Assist |
|
||||||
|
| Microsoft.SkypeApp | Skype |
|
||||||
|
| Microsoft.ScreenSketch | Snipping Tool |
|
||||||
|
| Microsoft.MicrosoftSolitaireCollection | Solitaire |
|
||||||
|
| Microsoft.MicrosoftStickyNotes | Sticky Notes |
|
||||||
|
| MicrosoftTeams / MSTeams | Teams (personal) |
|
||||||
|
| Microsoft.Todos | To Do |
|
||||||
|
| Microsoft.WindowsSoundRecorder | Voice Recorder |
|
||||||
|
| Microsoft.Wallet | Wallet |
|
||||||
|
| Microsoft.BingWeather | Weather |
|
||||||
|
| Microsoft.WindowsTerminal | Windows Terminal |
|
||||||
|
| Microsoft.Xbox.TCUI | Xbox UI |
|
||||||
|
| Microsoft.XboxApp | Xbox |
|
||||||
|
| Microsoft.XboxGameOverlay | Xbox Game Overlay |
|
||||||
|
| Microsoft.XboxGamingOverlay | Xbox Gaming Overlay |
|
||||||
|
| Microsoft.XboxIdentityProvider | Xbox Identity |
|
||||||
|
| Microsoft.XboxSpeechToTextOverlay | Xbox Speech |
|
||||||
|
| Microsoft.GamingApp | Gaming App |
|
||||||
|
| Microsoft.YourPhone | Phone Link |
|
||||||
|
| Microsoft.ZuneMusic | Music |
|
||||||
|
| Microsoft.ZuneVideo | Movies and TV |
|
||||||
|
|
||||||
|
NOTE: Microsoft.WindowsCalculator is intentionally KEPT.
|
||||||
|
|
||||||
|
### 1b - Windows Capabilities
|
||||||
|
|
||||||
|
| Capability | Description |
|
||||||
|
|---|---|
|
||||||
|
| Print.Fax.Scan | Fax and Scan |
|
||||||
|
| Language.Handwriting | Handwriting |
|
||||||
|
| Browser.InternetExplorer | Internet Explorer |
|
||||||
|
| MathRecognizer | Math Input |
|
||||||
|
| OneCoreUAP.OneSync | OneSync |
|
||||||
|
| OpenSSH.Client | OpenSSH client |
|
||||||
|
| Microsoft.Windows.MSPaint | Paint (Win32) |
|
||||||
|
| Microsoft.Windows.PowerShell.ISE | PowerShell ISE |
|
||||||
|
| App.Support.QuickAssist | Quick Assist |
|
||||||
|
| Microsoft.Windows.SnippingTool | Snipping Tool |
|
||||||
|
| App.StepsRecorder | Steps Recorder |
|
||||||
|
| Hello.Face.* | Windows Hello face |
|
||||||
|
| Media.WindowsMediaPlayer | Windows Media Player |
|
||||||
|
| Microsoft.Windows.WordPad | WordPad |
|
||||||
|
|
||||||
|
### 1c - Windows Optional Features
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
|---|---|
|
||||||
|
| MediaPlayback | Media playback |
|
||||||
|
| MicrosoftWindowsPowerShellV2Root | PowerShell 2.0 |
|
||||||
|
| Microsoft-RemoteDesktopConnection | RDP client |
|
||||||
|
| Recall | Windows Recall (AI) |
|
||||||
|
| Microsoft-SnippingTool | Snipping Tool (feature) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 2 - Software installation (winget)
|
||||||
|
|
||||||
|
| Software | Winget ID | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| 7-Zip | `7zip.7zip` | OK |
|
||||||
|
| Adobe Acrobat Reader | `Adobe.Acrobat.Reader.64-bit` | OK, see note |
|
||||||
|
| OpenVPN Connect | `OpenVPNTechnologies.OpenVPNConnect` | OK |
|
||||||
|
| ... | ... | TODO: complete list |
|
||||||
|
|
||||||
|
> Adobe Acrobat Reader: After install, script sets .pdf -> AcroRd32 as default.
|
||||||
|
> Scheduled task PDF-DefaultApp restores this association on every logon as a guard
|
||||||
|
> against Edge overwriting it.
|
||||||
|
|
||||||
|
> BackInfo: NOT used. Replaced by custom PowerShell scheduled task DesktopInfo.
|
||||||
|
> See STEP 7.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 3 - System settings (HKLM - applies to whole system)
|
||||||
|
|
||||||
|
| Setting | Value | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| Disable NRO (bypass network check) | HKLM\...\OOBE\BypassNRO = 1 | |
|
||||||
|
| Disable auto-install of Teams | ConfigureChatAutoInstall = 0 | |
|
||||||
|
| Disable Cloud Optimized Content | DisableCloudOptimizedContent = 1 | |
|
||||||
|
| Disable Widgets (News and Interests) | HKLM\...\Dsh\AllowNewsAndInterests = 0 | |
|
||||||
|
| Edge - hide First Run Experience | HKLM\Policies\Edge\HideFirstRunExperience = 1 | |
|
||||||
|
| Passwords - no expiration | net accounts /maxpwage:UNLIMITED | |
|
||||||
|
| Time zone | Central Europe Standard Time | |
|
||||||
|
| OneDrive - remove | Delete OneDriveSetup.exe + Start Menu lnk | |
|
||||||
|
| Outlook (new) - disable auto-install | Delete UScheduler registry key | |
|
||||||
|
| Disable GameDVR | AppCaptureEnabled = 0 | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 4 - Default Profile (NTUSER.DAT)
|
||||||
|
|
||||||
|
Settings applied to C:\Users\Default\NTUSER.DAT - inherited by every new user on first logon.
|
||||||
|
|
||||||
|
Method: script loads Default hive (reg load), makes changes, unloads (reg unload).
|
||||||
|
|
||||||
|
| Setting | Key / Value | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| Taskbar - align left | TaskbarAl = 0 | Win11 default is center |
|
||||||
|
| Taskbar - hide Search box | SearchboxTaskbarMode = 0 | |
|
||||||
|
| Taskbar - hide Copilot button | ShowCopilotButton = 0 | |
|
||||||
|
| Taskbar - hide Task View button | ShowTaskViewButton = 0 | |
|
||||||
|
| Taskbar - hide Widgets | TaskbarDa = 0 | |
|
||||||
|
| Taskbar - hide Chat/Teams button | TaskbarMn = 0 | |
|
||||||
|
| Taskbar - show all tray icons | Scheduled task ShowAllTrayIcons | Runs on every logon |
|
||||||
|
| Taskbar - empty pinlist | TaskbarLayoutModification.xml | Removes default pinned apps |
|
||||||
|
| Explorer - show file extensions | HideFileExt = 0 | |
|
||||||
|
| Explorer - open to This PC | LaunchTo = 1 | Instead of Quick Access |
|
||||||
|
| Start menu - empty pins | ConfigureStartPins = {"pinnedList":[]} | Win11 |
|
||||||
|
| Start menu - disable Bing results | DisableSearchBoxSuggestions = 1 | |
|
||||||
|
| Copilot - disable | TurnOffWindowsCopilot = 1 | |
|
||||||
|
| GameDVR - disable | AppCaptureEnabled = 0 | |
|
||||||
|
| OneDrive - remove RunOnce key | Delete OneDriveSetup from Run | |
|
||||||
|
| Num Lock on startup - enable | InitialKeyboardIndicators = 2 | |
|
||||||
|
| Accent color on title bars | ColorPrevalence = 1 | |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 5 - Personalization (colors, wallpaper)
|
||||||
|
|
||||||
|
Applied to both Default Profile and currently logged-in user.
|
||||||
|
|
||||||
|
| Setting | Value |
|
||||||
|
|---|---|
|
||||||
|
| System theme (taskbar, Start) | Dark |
|
||||||
|
| App theme | Light |
|
||||||
|
| Accent color | #223B47 (dark blue-gray) |
|
||||||
|
| Accent color on Start and taskbar | Yes |
|
||||||
|
| Accent color on title bars | Yes |
|
||||||
|
| Transparency | Disabled |
|
||||||
|
| Wallpaper | Solid color #223B47 (no image) |
|
||||||
|
|
||||||
|
NOTE: DesktopInfo scheduled task (STEP 7) will overwrite the wallpaper with a system
|
||||||
|
info BMP. The solid color here is only a fallback if DesktopInfo is not running.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 6 - Scheduled Tasks
|
||||||
|
|
||||||
|
| Task | Trigger | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| ShowAllTrayIcons | Every logon, every 1 min | Show all icons in system tray (Win11) |
|
||||||
|
| UnlockStartLayout | Once after layout is applied | Unlock Start menu layout |
|
||||||
|
| PDF-DefaultApp | Every logon | Restore .pdf -> Adobe Reader if Edge overwrote it |
|
||||||
|
| DesktopInfo | Every logon | Render system info onto desktop wallpaper |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 7 - DesktopInfo (BackInfo replacement)
|
||||||
|
|
||||||
|
Custom PowerShell scheduled task. No external dependencies.
|
||||||
|
|
||||||
|
**What it displays:**
|
||||||
|
- Computer name (hostname)
|
||||||
|
- IP address
|
||||||
|
- Windows version and build
|
||||||
|
- Logged-in username
|
||||||
|
- Deployment date
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. PS script collects system info
|
||||||
|
2. Renders text onto bitmap via WPF / System.Drawing
|
||||||
|
3. Saves BMP to C:\Windows\Setup\Scripts\desktopinfo.bmp
|
||||||
|
4. Sets BMP as desktop wallpaper via SystemParametersInfo
|
||||||
|
5. Runs on every user logon via Scheduled Task
|
||||||
|
|
||||||
|
**Why not BackInfo:**
|
||||||
|
- BackInfo has Win11 rendering issues requiring registry hacks
|
||||||
|
- External EXE dependency is hard to distribute
|
||||||
|
- Custom PS solution = full control, no dependencies, works on Win10 and Win11
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 8 - Logging and output
|
||||||
|
|
||||||
|
- Every step writes to C:\Windows\Setup\Scripts\Deploy.log
|
||||||
|
- Format: [HH:mm:ss] Step description - OK / ERROR: ...
|
||||||
|
- At end: summary report (how many steps OK, how many failed)
|
||||||
|
- Log stays on disk for diagnostics
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Script switches
|
||||||
|
|
||||||
|
| Switch | Behavior |
|
||||||
|
|---|---|
|
||||||
|
| `-SkipBloatware` | Skip step 1 |
|
||||||
|
| `-SkipSoftware` | Skip step 2 |
|
||||||
|
| `-SkipDefaultProfile` | Skip step 4 |
|
||||||
|
| `-DryRun` | Run through steps without changes, log only |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
| # | Question | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | BackInfo replacement | DONE - custom PS scheduled task DesktopInfo |
|
||||||
|
| 2 | Complete SW list for winget | TODO |
|
||||||
|
| 3 | Per-client variability via config.json | FUTURE |
|
||||||
|
| 4 | Admin account adminx9 - script or manual? | DONE - script (00-admin-account.ps1) |
|
||||||
140
Setup-ClaudeCode.ps1
Normal file
140
Setup-ClaudeCode.ps1
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Claude Code - rychla instalace a nastaveni prostredi
|
||||||
|
.USAGE
|
||||||
|
# Windows PS:
|
||||||
|
.\Setup-ClaudeCode.ps1 -ApiKey "sk-ant-..." -RepoUrl "https://github.com/org/repo"
|
||||||
|
|
||||||
|
# S volitelnym cilove adresarem:
|
||||||
|
.\Setup-ClaudeCode.ps1 -ApiKey "sk-ant-..." -RepoUrl "https://github.com/org/repo" -WorkDir "C:\Projects"
|
||||||
|
#>
|
||||||
|
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string] $ApiKey,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string] $RepoUrl,
|
||||||
|
|
||||||
|
[string] $WorkDir = "$HOME\Projects"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Write-Step { param([string]$Msg) Write-Host "`n[SETUP] $Msg" -ForegroundColor Cyan }
|
||||||
|
function Write-OK { param([string]$Msg) Write-Host " OK: $Msg" -ForegroundColor Green }
|
||||||
|
function Write-Fail { param([string]$Msg) Write-Host " ERR: $Msg" -ForegroundColor Red; exit 1 }
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 1. NODE.JS
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Checking Node.js..."
|
||||||
|
|
||||||
|
$nodeOk = $false
|
||||||
|
try {
|
||||||
|
$nodeVer = node --version 2>$null
|
||||||
|
if ($nodeVer -match 'v(\d+)' -and [int]$Matches[1] -ge 18) {
|
||||||
|
Write-OK "Node.js $nodeVer already installed"
|
||||||
|
$nodeOk = $true
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (-not $nodeOk) {
|
||||||
|
Write-Step "Installing Node.js via winget..."
|
||||||
|
try {
|
||||||
|
winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements --silent
|
||||||
|
# Reload PATH
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
|
||||||
|
[System.Environment]::GetEnvironmentVariable("Path","User")
|
||||||
|
Write-OK "Node.js installed"
|
||||||
|
} catch {
|
||||||
|
Write-Fail "Node.js install failed: $_. Install manually from https://nodejs.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 2. CLAUDE CODE
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Checking Claude Code..."
|
||||||
|
|
||||||
|
$ccOk = $false
|
||||||
|
try {
|
||||||
|
$ccVer = claude --version 2>$null
|
||||||
|
Write-OK "Claude Code $ccVer already installed"
|
||||||
|
$ccOk = $true
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (-not $ccOk) {
|
||||||
|
Write-Step "Installing Claude Code..."
|
||||||
|
try {
|
||||||
|
npm install -g @anthropic-ai/claude-code
|
||||||
|
Write-OK "Claude Code installed"
|
||||||
|
} catch {
|
||||||
|
Write-Fail "Claude Code install failed: $_"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 3. API KEY
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Setting ANTHROPIC_API_KEY..."
|
||||||
|
|
||||||
|
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $ApiKey, "User")
|
||||||
|
$env:ANTHROPIC_API_KEY = $ApiKey
|
||||||
|
Write-OK "API key set (User environment variable)"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 4. GIT
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Checking Git..."
|
||||||
|
|
||||||
|
try {
|
||||||
|
git --version | Out-Null
|
||||||
|
Write-OK "Git available"
|
||||||
|
} catch {
|
||||||
|
Write-Step "Installing Git via winget..."
|
||||||
|
try {
|
||||||
|
winget install Git.Git --accept-package-agreements --accept-source-agreements --silent
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
|
||||||
|
[System.Environment]::GetEnvironmentVariable("Path","User")
|
||||||
|
Write-OK "Git installed"
|
||||||
|
} catch {
|
||||||
|
Write-Fail "Git install failed: $_. Install manually from https://git-scm.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 5. CLONE REPO
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Cloning repository..."
|
||||||
|
|
||||||
|
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
|
||||||
|
|
||||||
|
$repoName = ($RepoUrl -split '/')[-1] -replace '\.git$', ''
|
||||||
|
$targetPath = Join-Path $WorkDir $repoName
|
||||||
|
|
||||||
|
if (Test-Path $targetPath) {
|
||||||
|
Write-OK "Repo already exists at $targetPath — pulling latest..."
|
||||||
|
Push-Location $targetPath
|
||||||
|
git pull
|
||||||
|
Pop-Location
|
||||||
|
} else {
|
||||||
|
git clone $RepoUrl $targetPath
|
||||||
|
Write-OK "Cloned to $targetPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# 6. LAUNCH
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Host "`n========================================" -ForegroundColor Green
|
||||||
|
Write-Host " Setup complete!" -ForegroundColor Green
|
||||||
|
Write-Host " Repo: $targetPath" -ForegroundColor White
|
||||||
|
Write-Host " Run: cd '$targetPath' && claude" -ForegroundColor White
|
||||||
|
Write-Host "========================================`n" -ForegroundColor Green
|
||||||
|
|
||||||
|
$launch = Read-Host "Launch Claude Code now? (Y/n)"
|
||||||
|
if ($launch -ne 'n') {
|
||||||
|
Set-Location $targetPath
|
||||||
|
claude
|
||||||
|
}
|
||||||
BIN
assets/Backinfo/BackInfo.exe
Normal file
BIN
assets/Backinfo/BackInfo.exe
Normal file
Binary file not shown.
182
assets/Backinfo/BackInfo.ini
Normal file
182
assets/Backinfo/BackInfo.ini
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
;; This INI file should use the following format
|
||||||
|
;;
|
||||||
|
;; [General]
|
||||||
|
;; BackgroundColor = <COLORREF value> ; The background color to use (default = 0 (black))
|
||||||
|
;; AutoBackground = [0 | 1] ; Use background color of current desktop (default = 0)
|
||||||
|
;; BackgroundBitmap = <path to BMP file> ; Overrides AutoBackground and BackgroundColor values. Loads background bitmap from BMP file
|
||||||
|
;; XOffset = <horizontal offset in pixels> ; Horizontal offest of the entire text block from the bitmap's center. Can be negative. Default = 0
|
||||||
|
;; YOffset = <vertical offset in pixels> ; Vertical offest of the entire text block from the bitmap's center. Can be negative. Default = 0
|
||||||
|
;; Output = <file name> ; Name of output bitmap file (default = "", use popup message)
|
||||||
|
;; UpdateDesktop = [0 | 1] ; Update background desktop bitmap (default = 0)
|
||||||
|
;; ForceDesktopCenter = [0 | 1] ; Force the desktop to display the bitmap as cenetered (instead of tiled / streched). Default = 1
|
||||||
|
|
||||||
|
;; LineSpacing = <value> ; Line spacing (default = 3)
|
||||||
|
;; SuppressErrors = [0 | 1] ; If 1, errors are NOT displayed (default = 0)
|
||||||
|
;;
|
||||||
|
;; [LineN] ; Text settings for line N, where N between [1..20]
|
||||||
|
;; Type = [CompName | UserName | SysVer | ; Type of information to display on the line
|
||||||
|
;; SysInfo | NetInfo | FileVer |
|
||||||
|
;; RegValue | FreeText |
|
||||||
|
UpdateTime | Unused]
|
||||||
|
;; ; CompName - Computer name
|
||||||
|
;; ; UserName - User name
|
||||||
|
;; ; SysVer - Operating system version
|
||||||
|
;; ; SysInfo - Hardware information
|
||||||
|
;; ; NetInfo - Network information
|
||||||
|
;; ; FileVer - Version of a file specified in 'FileName' option
|
||||||
|
;; ; RegValue - Registry string value.
|
||||||
|
;; ; Reg root from 'RegRoot' (e.g. HKLM)
|
||||||
|
;; ; Reg path from 'RegPath' (e.g. SOFTWARE\Microsoft\Windows NT\CurrentVersion)
|
||||||
|
;; ; Reg value from 'RegValue' (e.g. CurrentType)
|
||||||
|
;; ; Reg title from 'RegTitle' (e.g. "The value of X is")
|
||||||
|
;; ; FreeText - Text specified in 'Text' will be displayed as is
|
||||||
|
;; ; UpdateTime - The date and time the bitmap was created
|
||||||
|
;; ; Unused - Line will not be displayed
|
||||||
|
;;
|
||||||
|
;; Font = <Face name> ; Font name (default = "Arial")
|
||||||
|
;; Size = <Font size> ; Font size (default = 22)
|
||||||
|
;; Color = <COLORREF value> ; Font color (default = WHITE)
|
||||||
|
;; Bold = [0 | 1] ; Font boldness (default = 0)
|
||||||
|
;; Italic = [0 | 1] ; Font italicness (default = 0)
|
||||||
|
;; Alignment = [Left | Right | Center] ; Font alignment (default = Left)
|
||||||
|
;;
|
||||||
|
;; ShadowX = <X offset value> ; Shadow X offset (positive only, 0 = No X shadow. Default = 0)
|
||||||
|
;; ShadowY = <Y offset value> ; Shadow Y offset (positive only, 0 = No Y shadow. Default = 0)
|
||||||
|
;; ShadowColor = <COLORREF value> ; Shadow Color (default = 0 (black))
|
||||||
|
;;
|
||||||
|
;; RegRoot = [HKLM | HKCU] ; Registry root to use for 'Type' = 'RegValue'
|
||||||
|
;; RegPath = <Registry path to read from> ; Registry path to use for 'Type' = 'RegValue'
|
||||||
|
;; RegValue = <Registry value to read from> ; Registry value to use for 'Type' = 'RegValue'. Must be of type REG_SZ
|
||||||
|
;; RegTitle = <Display title of read value> ; Registry value to use for 'Type' = 'RegValue'
|
||||||
|
;;
|
||||||
|
;; Text = <free text to display> ; Free text to display. Used if 'Type' = 'FreeText'
|
||||||
|
;;
|
||||||
|
;; FilePath = <full path to file> ; Path to file to display version for. Used if 'Type' = 'FileVer'
|
||||||
|
;; FileName = <display name of file> ; Display name of file specified in 'FilePath'. Used if 'Type' = 'FileVer'
|
||||||
|
;;
|
||||||
|
|
||||||
|
[General]
|
||||||
|
BackgroundColor = 2097152
|
||||||
|
AutoBackground = 1
|
||||||
|
Output = %temp%\backinfo.bmp
|
||||||
|
UpdateDesktop = 1
|
||||||
|
LineSpacing = 2
|
||||||
|
ForceDesktopCenter = 1
|
||||||
|
SuppressErrors = 1
|
||||||
|
|
||||||
|
[Line1]
|
||||||
|
Font = Trebuchet MS
|
||||||
|
Size = 42
|
||||||
|
Color = 16777215
|
||||||
|
Bold = 1
|
||||||
|
Italic = 0
|
||||||
|
Alignment = Center
|
||||||
|
ShadowX = 2
|
||||||
|
ShadowY = 2
|
||||||
|
ShadowColor = 4210752
|
||||||
|
Type = CompName
|
||||||
|
|
||||||
|
[Line2]
|
||||||
|
Font = Trebuchet MS
|
||||||
|
Size = 20
|
||||||
|
Color = 10526880
|
||||||
|
Bold = 0
|
||||||
|
Italic = 0
|
||||||
|
Alignment = Center
|
||||||
|
ShadowX = 0
|
||||||
|
ShadowY = 0
|
||||||
|
ShadowColor = 4210752
|
||||||
|
Type = UserName
|
||||||
|
|
||||||
|
[Line3]
|
||||||
|
Font = Trebuchet MS
|
||||||
|
Size = 20
|
||||||
|
Color = 10526880
|
||||||
|
Bold = 1
|
||||||
|
Italic = 0
|
||||||
|
Alignment = Center
|
||||||
|
ShadowX = 0
|
||||||
|
ShadowY = 0
|
||||||
|
ShadowColor = 4210752
|
||||||
|
Type = RegValue
|
||||||
|
RegRoot = HKLM
|
||||||
|
RegPath = SOFTWARE\BackInfo
|
||||||
|
RegValue = OSName
|
||||||
|
RegTitle = OS:
|
||||||
|
|
||||||
|
|
||||||
|
[Line4]
|
||||||
|
Font = Trebuchet MS
|
||||||
|
Size = 20
|
||||||
|
Color = 10526880
|
||||||
|
Bold = 0
|
||||||
|
Italic = 0
|
||||||
|
Alignment = Center
|
||||||
|
ShadowX = 0
|
||||||
|
ShadowY = 0
|
||||||
|
ShadowColor = 4210752
|
||||||
|
Type = SysInfo
|
||||||
|
|
||||||
|
[Line5]
|
||||||
|
Font = Trebuchet MS
|
||||||
|
Size = 20
|
||||||
|
Color = 10526880
|
||||||
|
Bold = 0
|
||||||
|
Italic = 0
|
||||||
|
Alignment = Center
|
||||||
|
ShadowX = 0
|
||||||
|
ShadowY = 0
|
||||||
|
ShadowColor = 4210752
|
||||||
|
Type = NetInfo
|
||||||
|
|
||||||
|
|
||||||
|
;;
|
||||||
|
;; SAMPLE - how to display free text lines
|
||||||
|
;;
|
||||||
|
; [Line6]
|
||||||
|
; Font = Trebuchet MS
|
||||||
|
; Size = 20
|
||||||
|
; Color = 10526880
|
||||||
|
; Bold = 0
|
||||||
|
; Italic = 0
|
||||||
|
; Alignment = Center
|
||||||
|
; ShadowX = 0
|
||||||
|
; ShadowY = 0
|
||||||
|
; ShadowColor = 4210752
|
||||||
|
; Type = FreeText
|
||||||
|
; Text = System path is %windir%
|
||||||
|
;;;
|
||||||
|
;; SAMPLE - how to display file version
|
||||||
|
;;
|
||||||
|
; [Line7]
|
||||||
|
; Font = Trebuchet MS
|
||||||
|
; Size = 20
|
||||||
|
; Color = 10526880
|
||||||
|
; Bold = 0
|
||||||
|
; Italic = 0
|
||||||
|
; Alignment = Center
|
||||||
|
; ShadowX = 0
|
||||||
|
; ShadowY = 0
|
||||||
|
; ShadowColor = 4210752
|
||||||
|
; Type = FileVer
|
||||||
|
; FilePath = %ProgramFiles%\backinfo\backinfo.exe
|
||||||
|
; FileName = backinfo.exe
|
||||||
|
;;
|
||||||
|
;; SAMPLE - how to display registry value
|
||||||
|
;;
|
||||||
|
; [Line8]
|
||||||
|
; Font = Trebuchet MS
|
||||||
|
; Size = 20
|
||||||
|
; Color = 10526880
|
||||||
|
; Bold = 0
|
||||||
|
; Italic = 0
|
||||||
|
; Alignment = Center
|
||||||
|
; ShadowX = 0
|
||||||
|
; ShadowY = 0
|
||||||
|
; ShadowColor = 4210752
|
||||||
|
; Type = RegValue
|
||||||
|
; RegRoot = HKLM
|
||||||
|
; RegPath = SOFTWARE\Microsoft\Windows NT\CurrentVersion
|
||||||
|
; RegValue = CurrentType
|
||||||
|
; RegTitle = OS type
|
||||||
|
|
||||||
60
assets/Backinfo/backinfo_W11.ps1
Normal file
60
assets/Backinfo/backinfo_W11.ps1
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# ================================
|
||||||
|
# BackInfo OS detection script
|
||||||
|
# Writes OS name for BGInfo/BackInfo
|
||||||
|
# ================================
|
||||||
|
|
||||||
|
Set-ExecutionPolicy Unrestricted
|
||||||
|
|
||||||
|
$cvPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
|
||||||
|
$cv = Get-ItemProperty -Path $cvPath
|
||||||
|
|
||||||
|
# --- Detect OS by build number ---
|
||||||
|
$build = [int]$cv.CurrentBuild
|
||||||
|
|
||||||
|
if ($build -ge 22000) {
|
||||||
|
$osName = "Windows 11"
|
||||||
|
} else {
|
||||||
|
$osName = "Windows 10"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Detect edition ---
|
||||||
|
switch ($cv.EditionID) {
|
||||||
|
"Professional" { $edition = "Pro" }
|
||||||
|
"ProfessionalN" { $edition = "Pro N" }
|
||||||
|
"Core" { $edition = "Home" }
|
||||||
|
"CoreN" { $edition = "Home N" }
|
||||||
|
"Enterprise" { $edition = "Enterprise" }
|
||||||
|
"Education" { $edition = "Education" }
|
||||||
|
default { $edition = $cv.EditionID }
|
||||||
|
}
|
||||||
|
|
||||||
|
$finalOSName = "$osName $edition"
|
||||||
|
|
||||||
|
# --- Registry paths for BackInfo (64bit + 32bit) ---
|
||||||
|
$regPaths = @(
|
||||||
|
"HKLM:\SOFTWARE\BackInfo",
|
||||||
|
"HKLM:\SOFTWARE\WOW6432Node\BackInfo"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($path in $regPaths) {
|
||||||
|
if (-not (Test-Path $path)) {
|
||||||
|
New-Item -Path $path -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
New-ItemProperty `
|
||||||
|
-Path $path `
|
||||||
|
-Name "OSName" `
|
||||||
|
-Value $finalOSName `
|
||||||
|
-PropertyType String `
|
||||||
|
-Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Optional output for logging ---
|
||||||
|
Write-Output "BackInfo OSName set to: $finalOSName"
|
||||||
|
|
||||||
|
$SourceFilePath = "C:\Program Files\BackInfo\BackInfo.exe"
|
||||||
|
$ShortcutPath = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\BackInfo.lnk"
|
||||||
|
$WScriptObj = New-Object -ComObject ("WScript.Shell")
|
||||||
|
$shortcut = $WscriptObj.CreateShortcut($ShortcutPath)
|
||||||
|
$shortcut.TargetPath = $SourceFilePath
|
||||||
|
$shortcut.Save()
|
||||||
BIN
assets/Logo/X9-logo-barevné-square.ico
Normal file
BIN
assets/Logo/X9-logo-barevné-square.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
BIN
assets/Logo/X9-logo-barevné-square.jpeg
Normal file
BIN
assets/Logo/X9-logo-barevné-square.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 292 KiB |
63
cmd/xetup/main.go
Normal file
63
cmd/xetup/main.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Command xetup is the GUI launcher for Windows deployment.
|
||||||
|
//
|
||||||
|
// All PowerShell scripts and assets are embedded in the binary and extracted
|
||||||
|
// to a temp directory at runtime. The GUI collects configuration, streams
|
||||||
|
// live log output while the scripts run, and shows a final summary.
|
||||||
|
//
|
||||||
|
// Cross-compile for Windows (requires MinGW):
|
||||||
|
//
|
||||||
|
// CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc \
|
||||||
|
// GOOS=windows GOARCH=amd64 \
|
||||||
|
// go build -ldflags="-s -w -H windowsgui" -o xetup.exe ./cmd/xetup
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
xetupembed "git.xetup.x9.cz/x9/xetup"
|
||||||
|
"git.xetup.x9.cz/x9/xetup/internal/config"
|
||||||
|
"git.xetup.x9.cz/x9/xetup/internal/gui"
|
||||||
|
"git.xetup.x9.cz/x9/xetup/internal/runner"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load config (falls back to defaults when config.json is missing)
|
||||||
|
cfgPath := config.ConfigPath()
|
||||||
|
cfg, err := config.Load(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Warning: config load failed (%v), using defaults\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temp working directory – cleaned up on exit
|
||||||
|
tmpDir, err := os.MkdirTemp("", "xetup-*")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Cannot create temp dir: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
// Extract embedded scripts and assets
|
||||||
|
if err := runner.ExtractScripts(xetupembed.Scripts, tmpDir); err != nil {
|
||||||
|
log.Fatalf("Failed to extract scripts: %v", err)
|
||||||
|
}
|
||||||
|
if err := runner.ExtractAssets(xetupembed.Assets, tmpDir); err != nil {
|
||||||
|
log.Fatalf("Failed to extract assets: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write runtime config JSON so PowerShell scripts can read it
|
||||||
|
cfgRuntimePath, err := runner.WriteConfig(cfg, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to write runtime config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
runCfg := runner.RunConfig{
|
||||||
|
ScriptsDir: filepath.Join(tmpDir, "scripts"),
|
||||||
|
ConfigPath: cfgRuntimePath,
|
||||||
|
LogFile: `C:\Windows\Setup\Scripts\Deploy.log`,
|
||||||
|
ProfileType: cfg.Deployment.ProfileType,
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.Run(cfg, runCfg, cfgPath)
|
||||||
|
}
|
||||||
632
config-editor.hta
Normal file
632
config-editor.hta
Normal file
|
|
@ -0,0 +1,632 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>X9 - Deployment Config Editor</title>
|
||||||
|
<HTA:APPLICATION
|
||||||
|
ID="ConfigEditor"
|
||||||
|
APPLICATIONNAME="X9 Config Editor"
|
||||||
|
SCROLL="no"
|
||||||
|
SINGLEINSTANCE="yes"
|
||||||
|
WINDOWSTATE="normal"
|
||||||
|
INNERBORDER="no"
|
||||||
|
SELECTION="no"
|
||||||
|
CONTEXTMENU="no"
|
||||||
|
/>
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=11">
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: Segoe UI, Arial, sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #1a2a33;
|
||||||
|
color: #d0dde3;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
background: #223B47;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-bottom: 2px solid #2e5568;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#header h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e8f4f8;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
#header .config-path {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #7aabbd;
|
||||||
|
margin-top: 3px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
#tabs {
|
||||||
|
display: flex;
|
||||||
|
background: #1a2a33;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
padding: 8px 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #7aabbd;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.tab:hover { color: #b0d4e0; }
|
||||||
|
.tab.active { color: #e8f4f8; border-bottom: 2px solid #4a9aba; background: #1e3340; }
|
||||||
|
#content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.tab-panel { display: none; padding: 16px 18px; }
|
||||||
|
.tab-panel.active { display: block; }
|
||||||
|
table { width: 100%; border-collapse: collapse; }
|
||||||
|
th {
|
||||||
|
background: #223B47;
|
||||||
|
color: #9ac8d8;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid #1e3340;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
tr:hover td { background: #1e3340; }
|
||||||
|
.step-num {
|
||||||
|
color: #4a9aba;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
.step-name { color: #d0dde3; }
|
||||||
|
.info-btn {
|
||||||
|
background: #2e5568;
|
||||||
|
border: none;
|
||||||
|
color: #7aabbd;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.info-btn:hover { background: #3a6f8a; color: #e8f4f8; }
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #4a9aba;
|
||||||
|
}
|
||||||
|
input[type="text"], select {
|
||||||
|
background: #1a2a33;
|
||||||
|
border: 1px solid #2e5568;
|
||||||
|
color: #d0dde3;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type="text"]:focus, select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9aba;
|
||||||
|
}
|
||||||
|
.label-cell {
|
||||||
|
width: 160px;
|
||||||
|
color: #9ac8d8;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding-top: 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
color: #4a9aba;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
}
|
||||||
|
.section-title:first-child { margin-top: 0; }
|
||||||
|
.btn {
|
||||||
|
background: #223B47;
|
||||||
|
border: 1px solid #2e5568;
|
||||||
|
color: #d0dde3;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.btn:hover { background: #2e5568; color: #e8f4f8; }
|
||||||
|
.btn-danger {
|
||||||
|
background: #3d1f1f;
|
||||||
|
border-color: #6b2c2c;
|
||||||
|
color: #d08080;
|
||||||
|
}
|
||||||
|
.btn-danger:hover { background: #5a2020; color: #f0a0a0; }
|
||||||
|
.btn-add {
|
||||||
|
background: #1a3a2a;
|
||||||
|
border-color: #2e6a4a;
|
||||||
|
color: #80c8a0;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.btn-add:hover { background: #235a38; color: #a0e0b8; }
|
||||||
|
#footer {
|
||||||
|
background: #223B47;
|
||||||
|
border-top: 2px solid #2e5568;
|
||||||
|
padding: 10px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#btn-save {
|
||||||
|
background: #1a5c3a;
|
||||||
|
border: 1px solid #2a8a58;
|
||||||
|
color: #80e0b0;
|
||||||
|
padding: 7px 22px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
#btn-save:hover { background: #206e46; color: #a0f0c8; }
|
||||||
|
#status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7aabbd;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#tooltip {
|
||||||
|
position: fixed;
|
||||||
|
background: #0f1e26;
|
||||||
|
border: 1px solid #3a7a9a;
|
||||||
|
color: #c0dde8;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 340px;
|
||||||
|
line-height: 1.5;
|
||||||
|
z-index: 9999;
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
.settings-grid { display: grid; grid-template-columns: 160px 1fr; gap: 8px 12px; align-items: center; }
|
||||||
|
.settings-grid .span2 { grid-column: 1 / -1; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="header">
|
||||||
|
<h1>X9 - Deployment Config</h1>
|
||||||
|
<div class="config-path" id="config-path-display">Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tabs">
|
||||||
|
<div class="tab active" onclick="showTab('steps')">Steps</div>
|
||||||
|
<div class="tab" onclick="showTab('software')">Software</div>
|
||||||
|
<div class="tab" onclick="showTab('settings')">Settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<!-- STEPS TAB -->
|
||||||
|
<div class="tab-panel active" id="tab-steps">
|
||||||
|
<table id="steps-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:40px;">On</th>
|
||||||
|
<th style="width:36px;">Step</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th style="width:32px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="steps-tbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SOFTWARE TAB -->
|
||||||
|
<div class="tab-panel" id="tab-software">
|
||||||
|
<table id="software-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Package Name</th>
|
||||||
|
<th>Winget ID</th>
|
||||||
|
<th style="width:70px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="software-tbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button class="btn btn-add" onclick="addSoftwareRow()">+ Add package</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SETTINGS TAB -->
|
||||||
|
<div class="tab-panel" id="tab-settings">
|
||||||
|
<div class="section-title">Deployment</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Timezone</div>
|
||||||
|
<div><input type="text" id="s-timezone" onchange="updateSetting('deployment','timezone',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Locale</div>
|
||||||
|
<div><input type="text" id="s-locale" onchange="updateSetting('deployment','locale',this.value)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Admin Account</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Username</div>
|
||||||
|
<div><input type="text" id="s-admin-user" onchange="updateSetting('adminAccount','username',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Password</div>
|
||||||
|
<div><input type="text" id="s-admin-pass" onchange="updateSetting('adminAccount','password',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Description</div>
|
||||||
|
<div><input type="text" id="s-admin-desc" onchange="updateSetting('adminAccount','description',this.value)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">PDF Default</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Force Adobe Reader</div>
|
||||||
|
<div><input type="checkbox" id="s-force-adobe" onchange="updateSettingBool('pdfDefault','forceAdobeReader',this.checked)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Scheduled Task</div>
|
||||||
|
<div><input type="checkbox" id="s-pdf-task" onchange="updateSettingBool('pdfDefault','scheduledTaskEnabled',this.checked)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Desktop Info</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Enabled</div>
|
||||||
|
<div><input type="checkbox" id="s-di-enabled" onchange="updateSettingBool('desktopInfo','enabled',this.checked)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Position</div>
|
||||||
|
<div>
|
||||||
|
<select id="s-di-position" onchange="updateSetting('desktopInfo','position',this.value)">
|
||||||
|
<option value="bottomRight">Bottom Right</option>
|
||||||
|
<option value="bottomLeft">Bottom Left</option>
|
||||||
|
<option value="topRight">Top Right</option>
|
||||||
|
<option value="topLeft">Top Left</option>
|
||||||
|
<option value="center">Center</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="label-cell">Font Size</div>
|
||||||
|
<div><input type="text" id="s-di-fontsize" onchange="updateSettingInt('desktopInfo','fontSize',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Font Color</div>
|
||||||
|
<div><input type="text" id="s-di-fontcolor" onchange="updateSetting('desktopInfo','fontColor',this.value)" placeholder="#FFFFFF"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Activation</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Product Key</div>
|
||||||
|
<div><input type="text" id="s-act-key" onchange="updateSetting('activation','productKey',this.value)" placeholder="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">KMS Server</div>
|
||||||
|
<div><input type="text" id="s-act-kms" onchange="updateSetting('activation','kmsServer',this.value)" placeholder="kms.example.com"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
<button id="btn-save" onclick="saveConfig()">Save config.json</button>
|
||||||
|
<div id="status">Ready.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tooltip"></div>
|
||||||
|
|
||||||
|
<script language="JScript">
|
||||||
|
|
||||||
|
var STEP_DEFS = [
|
||||||
|
{ key: "adminAccount", num: "00", label: "Admin account", info: "Creates local admin account for MSP remote access. Account name and generated password are saved to Deploy.log." },
|
||||||
|
{ key: "bloatware", num: "01", label: "Bloatware removal", info: "Removes 44 pre-installed UWP apps (Teams, Xbox, Cortana, Solitaire, Office Hub, Skype...) and unused Windows Capabilities and Features. Calculator is kept." },
|
||||||
|
{ key: "software", num: "02", label: "Software install", info: "Installs packages from the list below via winget. Also sets Adobe Reader as default PDF viewer via HKCR." },
|
||||||
|
{ key: "systemRegistry", num: "03", label: "System registry", info: "HKLM-wide tweaks: disables Widgets, Teams auto-install, Edge first run, OneDrive, Outlook auto-install, GameDVR, Recall. Sets timezone. Hides taskbar search box via policy (Win11)." },
|
||||||
|
{ key: "defaultProfile", num: "04", label: "Default profile", info: "Modifies C:\\Users\\Default\\NTUSER.DAT so ALL future users inherit: left-aligned taskbar, hidden search/Copilot/TaskView/Widgets buttons, file extensions visible, Explorer opens to This PC, Num Lock on." },
|
||||||
|
{ key: "personalization", num: "05", label: "Personalization", info: "Dark system theme, light app theme, accent color #223B47, transparency off, accent on title bars. Applied to Default profile and current user." },
|
||||||
|
{ key: "scheduledTasks", num: "06", label: "Scheduled tasks", info: "Registers 4 tasks: ShowAllTrayIcons (logon - clears systray cache + restarts Explorer), UnlockStartLayout (once), PDF-DefaultApp (logon as SYSTEM - restores HKCR association), DesktopInfo (logon - renders wallpaper)." },
|
||||||
|
{ key: "desktopInfo", num: "07", label: "Desktop info", info: "Custom desktop wallpaper showing: computer name, IP, OS version, username, deployment date. Rendered as BMP on every logon via scheduled task. Replaces BackInfo.exe." },
|
||||||
|
{ key: "activation", num: "08", label: "Windows activation", info: "Checks and applies Windows activation." }
|
||||||
|
];
|
||||||
|
|
||||||
|
var configPath = "";
|
||||||
|
var config = null;
|
||||||
|
var fso = null;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
try {
|
||||||
|
fso = new ActiveXObject("Scripting.FileSystemObject");
|
||||||
|
|
||||||
|
// Derive config path from HTA location
|
||||||
|
var htaPath = location.pathname.replace(/\//g, "\\");
|
||||||
|
// Remove leading backslash from pathname
|
||||||
|
if (htaPath.charAt(0) === "\\") {
|
||||||
|
htaPath = htaPath.substring(1);
|
||||||
|
}
|
||||||
|
var dir = fso.GetParentFolderName(htaPath);
|
||||||
|
configPath = dir + "\\config\\config.json";
|
||||||
|
|
||||||
|
document.getElementById("config-path-display").innerText = configPath;
|
||||||
|
|
||||||
|
loadConfig();
|
||||||
|
buildStepsTable();
|
||||||
|
resizeWindow();
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Init error: " + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeWindow() {
|
||||||
|
window.resizeTo(800, 720);
|
||||||
|
window.moveTo(
|
||||||
|
(screen.width - 800) / 2,
|
||||||
|
(screen.height - 720) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
if (!fso.FileExists(configPath)) {
|
||||||
|
setStatus("config.json not found: " + configPath, true);
|
||||||
|
config = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var f = fso.OpenTextFile(configPath, 1, false, -1);
|
||||||
|
var raw = f.ReadAll();
|
||||||
|
f.Close();
|
||||||
|
config = JSON.parse(raw);
|
||||||
|
setStatus("Loaded: " + configPath);
|
||||||
|
populateSettings();
|
||||||
|
buildSoftwareTable();
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Load error: " + e.message, true);
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStepsTable() {
|
||||||
|
var tbody = document.getElementById("steps-tbody");
|
||||||
|
var html = "";
|
||||||
|
for (var i = 0; i < STEP_DEFS.length; i++) {
|
||||||
|
var s = STEP_DEFS[i];
|
||||||
|
var checked = "";
|
||||||
|
// Check config.steps if loaded, default true
|
||||||
|
if (config && config.steps && config.steps[s.key] === false) {
|
||||||
|
checked = "";
|
||||||
|
} else {
|
||||||
|
checked = "checked";
|
||||||
|
}
|
||||||
|
html += "<tr>";
|
||||||
|
html += "<td><input type='checkbox' " + checked + " onclick=\"toggleStep('" + s.key + "', this.checked)\"></td>";
|
||||||
|
html += "<td class='step-num'>" + s.num + "</td>";
|
||||||
|
html += "<td class='step-name'>" + s.label + "</td>";
|
||||||
|
html += "<td><button class='info-btn' onmouseover=\"showTooltip(event, '" + escapeQ(s.info) + "')\" onmouseout='hideTooltip()'>i</button></td>";
|
||||||
|
html += "</tr>";
|
||||||
|
}
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSoftwareTable() {
|
||||||
|
var tbody = document.getElementById("software-tbody");
|
||||||
|
var html = "";
|
||||||
|
if (config && config.software && config.software.install) {
|
||||||
|
var list = config.software.install;
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
html += makeSoftwareRow(i, list[i].name, list[i].wingetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSoftwareRow(idx, name, wingetId) {
|
||||||
|
return "<tr id='sw-row-" + idx + "'>" +
|
||||||
|
"<td><input type='text' value='" + escapeQ(name) + "' onchange=\"updateSoftwareName(" + idx + ", this.value)\"></td>" +
|
||||||
|
"<td><input type='text' value='" + escapeQ(wingetId) + "' onchange=\"updateSoftwareId(" + idx + ", this.value)\"></td>" +
|
||||||
|
"<td><button class='btn btn-danger' onclick='removeSoftwareRow(" + idx + ")'>Remove</button></td>" +
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSoftwareRow() {
|
||||||
|
if (!config.software) { config.software = {}; }
|
||||||
|
if (!config.software.install) { config.software.install = []; }
|
||||||
|
config.software.install.push({ name: "", wingetId: "" });
|
||||||
|
buildSoftwareTable();
|
||||||
|
setStatus("Package added. Fill in name and Winget ID, then save.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSoftwareRow(idx) {
|
||||||
|
if (!config || !config.software || !config.software.install) { return; }
|
||||||
|
config.software.install.splice(idx, 1);
|
||||||
|
buildSoftwareTable();
|
||||||
|
setStatus("Package removed. Click Save to persist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSoftwareName(idx, val) {
|
||||||
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
||||||
|
config.software.install[idx].name = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSoftwareId(idx, val) {
|
||||||
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
||||||
|
config.software.install[idx].wingetId = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateSettings() {
|
||||||
|
if (!config) { return; }
|
||||||
|
|
||||||
|
if (config.deployment) {
|
||||||
|
setVal("s-timezone", config.deployment.timezone || "");
|
||||||
|
setVal("s-locale", config.deployment.locale || "");
|
||||||
|
}
|
||||||
|
if (config.adminAccount) {
|
||||||
|
setVal("s-admin-user", config.adminAccount.username || "");
|
||||||
|
setVal("s-admin-pass", config.adminAccount.password || "");
|
||||||
|
setVal("s-admin-desc", config.adminAccount.description || "");
|
||||||
|
}
|
||||||
|
if (config.pdfDefault) {
|
||||||
|
setChk("s-force-adobe", config.pdfDefault.forceAdobeReader !== false);
|
||||||
|
setChk("s-pdf-task", config.pdfDefault.scheduledTaskEnabled !== false);
|
||||||
|
}
|
||||||
|
if (config.desktopInfo) {
|
||||||
|
setChk("s-di-enabled", config.desktopInfo.enabled !== false);
|
||||||
|
setSelVal("s-di-position", config.desktopInfo.position || "bottomRight");
|
||||||
|
setVal("s-di-fontsize", config.desktopInfo.fontSize !== undefined ? String(config.desktopInfo.fontSize) : "12");
|
||||||
|
setVal("s-di-fontcolor", config.desktopInfo.fontColor || "#FFFFFF");
|
||||||
|
}
|
||||||
|
if (config.activation) {
|
||||||
|
setVal("s-act-key", config.activation.productKey || "");
|
||||||
|
setVal("s-act-kms", config.activation.kmsServer || "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVal(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) { el.value = val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChk(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) { el.checked = !!val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelVal(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (!el) { return; }
|
||||||
|
for (var i = 0; i < el.options.length; i++) {
|
||||||
|
if (el.options[i].value === val) {
|
||||||
|
el.selectedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleStep(key, enabled) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config.steps) { config.steps = {}; }
|
||||||
|
config.steps[key] = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSetting(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
config[section][key] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSettingBool(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
config[section][key] = !!val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSettingInt(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
var n = parseInt(val, 10);
|
||||||
|
config[section][key] = isNaN(n) ? val : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
if (!config) { setStatus("No config loaded.", true); return; }
|
||||||
|
try {
|
||||||
|
// Sync steps checkboxes before save (in case table was rebuilt)
|
||||||
|
syncStepsFromTable();
|
||||||
|
|
||||||
|
var json = JSON.stringify(config, null, 2);
|
||||||
|
var f = fso.CreateTextFile(configPath, true, true);
|
||||||
|
f.Write(json);
|
||||||
|
f.Close();
|
||||||
|
setStatus("Saved: " + configPath + " [" + now() + "]");
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Save error: " + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncStepsFromTable() {
|
||||||
|
if (!config.steps) { config.steps = {}; }
|
||||||
|
var rows = document.getElementById("steps-tbody").getElementsByTagName("tr");
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
var chk = rows[i].getElementsByTagName("input")[0];
|
||||||
|
if (chk && STEP_DEFS[i]) {
|
||||||
|
config.steps[STEP_DEFS[i].key] = chk.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTab(name) {
|
||||||
|
var panels = document.getElementsByClassName("tab-panel");
|
||||||
|
for (var i = 0; i < panels.length; i++) {
|
||||||
|
panels[i].className = "tab-panel";
|
||||||
|
}
|
||||||
|
var tabs = document.getElementsByClassName("tab");
|
||||||
|
for (var i = 0; i < tabs.length; i++) {
|
||||||
|
tabs[i].className = "tab";
|
||||||
|
}
|
||||||
|
document.getElementById("tab-" + name).className = "tab-panel active";
|
||||||
|
var allTabs = document.getElementById("tabs").getElementsByClassName("tab");
|
||||||
|
var nameMap = { steps: 0, software: 1, settings: 2 };
|
||||||
|
if (nameMap[name] !== undefined) {
|
||||||
|
allTabs[nameMap[name]].className = "tab active";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltip(evt, text) {
|
||||||
|
var t = document.getElementById("tooltip");
|
||||||
|
t.innerText = text;
|
||||||
|
t.style.display = "block";
|
||||||
|
positionTooltip(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionTooltip(evt) {
|
||||||
|
var t = document.getElementById("tooltip");
|
||||||
|
var x = evt.clientX + 12;
|
||||||
|
var y = evt.clientY + 12;
|
||||||
|
if (x + 350 > document.body.clientWidth) { x = evt.clientX - 350; }
|
||||||
|
if (y + 80 > document.body.clientHeight) { y = evt.clientY - 80; }
|
||||||
|
t.style.left = x + "px";
|
||||||
|
t.style.top = y + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideTooltip() {
|
||||||
|
document.getElementById("tooltip").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(msg, isError) {
|
||||||
|
var el = document.getElementById("status");
|
||||||
|
el.innerText = msg;
|
||||||
|
el.style.color = isError ? "#d08080" : "#7aabbd";
|
||||||
|
}
|
||||||
|
|
||||||
|
function now() {
|
||||||
|
var d = new Date();
|
||||||
|
return d.getHours() + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(n) { return n < 10 ? "0" + n : String(n); }
|
||||||
|
|
||||||
|
function escapeQ(s) {
|
||||||
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() { init(); };
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
49
config/config.json
Normal file
49
config/config.json
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"steps": {
|
||||||
|
"adminAccount": true,
|
||||||
|
"bloatware": true,
|
||||||
|
"software": true,
|
||||||
|
"systemRegistry": true,
|
||||||
|
"defaultProfile": true,
|
||||||
|
"personalization": true,
|
||||||
|
"scheduledTasks": true,
|
||||||
|
"desktopInfo": true,
|
||||||
|
"activation": true
|
||||||
|
},
|
||||||
|
"deployment": {
|
||||||
|
"timezone": "Central Europe Standard Time",
|
||||||
|
"locale": "cs-CZ"
|
||||||
|
},
|
||||||
|
"adminAccount": {
|
||||||
|
"username": "adminx9",
|
||||||
|
"password": "AdminX9.AdminX9",
|
||||||
|
"description": "X9 MSP admin account"
|
||||||
|
},
|
||||||
|
"activation": {
|
||||||
|
"productKey": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
|
||||||
|
"kmsServer": ""
|
||||||
|
},
|
||||||
|
"software": {
|
||||||
|
"install": [
|
||||||
|
{ "name": "7-Zip", "wingetId": "7zip.7zip" },
|
||||||
|
{ "name": "Adobe Acrobat Reader","wingetId": "Adobe.Acrobat.Reader.64-bit" },
|
||||||
|
{ "name": "OpenVPN Connect", "wingetId": "OpenVPNTechnologies.OpenVPNConnect" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bloatware": {
|
||||||
|
"keepPackages": [
|
||||||
|
"Microsoft.WindowsCalculator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"desktopInfo": {
|
||||||
|
"enabled": true,
|
||||||
|
"position": "bottomRight",
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontColor": "#FFFFFF",
|
||||||
|
"backgroundColor": "transparent"
|
||||||
|
},
|
||||||
|
"pdfDefault": {
|
||||||
|
"forceAdobeReader": true,
|
||||||
|
"scheduledTaskEnabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
72
docker-compose.yml
Normal file
72
docker-compose.yml
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
services:
|
||||||
|
forgejo:
|
||||||
|
image: codeberg.org/forgejo/forgejo:9
|
||||||
|
container_name: xetup-forgejo
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- USER_UID=1000
|
||||||
|
- USER_GID=1000
|
||||||
|
# Forgejo config via env
|
||||||
|
- FORGEJO__server__ROOT_URL=https://git.xetup.x9.cz
|
||||||
|
- FORGEJO__server__DOMAIN=git.xetup.x9.cz
|
||||||
|
- FORGEJO__server__SSH_DOMAIN=git.xetup.x9.cz
|
||||||
|
- FORGEJO__server__SSH_PORT=2222
|
||||||
|
- FORGEJO__server__LFS_START_SERVER=true
|
||||||
|
- FORGEJO__database__DB_TYPE=sqlite3
|
||||||
|
- FORGEJO__service__DISABLE_REGISTRATION=true
|
||||||
|
- FORGEJO__service__REQUIRE_SIGNIN_VIEW=false
|
||||||
|
- FORGEJO__ui__DEFAULT_THEME=forgejo-dark
|
||||||
|
- FORGEJO__repository__DEFAULT_BRANCH=main
|
||||||
|
- FORGEJO__actions__ENABLED=true
|
||||||
|
- FORGEJO__indexer__REPO_INDEXER_ENABLED=true
|
||||||
|
- FORGEJO__cors__ENABLED=true
|
||||||
|
- FORGEJO__cors__ALLOW_DOMAIN=xetup.x9.cz
|
||||||
|
- FORGEJO__cors__METHODS=GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
|
||||||
|
- FORGEJO__cors__HEADERS=Authorization,Content-Type
|
||||||
|
- FORGEJO__cors__MAX_AGE=10m
|
||||||
|
volumes:
|
||||||
|
- forgejo-data:/data
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
ports:
|
||||||
|
- "3100:3000" # Web UI (behind reverse proxy)
|
||||||
|
- "2222:22" # Git SSH
|
||||||
|
networks:
|
||||||
|
- xetup
|
||||||
|
|
||||||
|
runner:
|
||||||
|
image: code.forgejo.org/forgejo/runner:6.3.1
|
||||||
|
container_name: xetup-runner
|
||||||
|
restart: unless-stopped
|
||||||
|
entrypoint: ["/bin/sh", "-c", "forgejo-runner daemon --config /etc/runner/config.yml"]
|
||||||
|
user: "0:996" # root:docker - needed for /var/run/docker.sock access
|
||||||
|
depends_on:
|
||||||
|
- forgejo
|
||||||
|
environment:
|
||||||
|
- DOCKER_HOST=unix:///var/run/docker.sock
|
||||||
|
volumes:
|
||||||
|
- runner-data:/data
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./runner-config.yml:/etc/runner/config.yml:ro
|
||||||
|
networks:
|
||||||
|
- xetup
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: xetup-web
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./web:/usr/share/nginx/html:ro
|
||||||
|
- ./web/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
ports:
|
||||||
|
- "3200:80" # Web (xetup.x9.cz via reverse proxy)
|
||||||
|
networks:
|
||||||
|
- xetup
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
forgejo-data:
|
||||||
|
runner-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
xetup:
|
||||||
|
name: xetup
|
||||||
111
docs/xetup-review.md
Normal file
111
docs/xetup-review.md
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
# Xetup - Review vysledek
|
||||||
|
|
||||||
|
Datum: 15. 4. 2026
|
||||||
|
|
||||||
|
## 1. Priprava prostredi
|
||||||
|
|
||||||
|
[ ] **Adresar C:\X9 + ikona slozky** (CEKA)
|
||||||
|
Vytvoreni adresarove struktury C:\X9 se slozkami install, vlastni ikonou a logem.
|
||||||
|
|
||||||
|
[ ] **Nextcloud - stahovani souboru** (CEKA)
|
||||||
|
Stahovani Backinfo, Atera, Flash2, X9-ikona.ico, X9-logo.jpg z Nextcloudu do C:\X9\install.
|
||||||
|
> nextcloud tam netřeba, Backinfo máme ve vlastní složce
|
||||||
|
|
||||||
|
|
||||||
|
[?] **Admin ucet (adminx9)** (K DISKUSI)
|
||||||
|
Vytvoreni skryteho lokalniho admin uctu adminx9 pro MSP spravce.
|
||||||
|
> adminX9 bude bez hesla
|
||||||
|
|
||||||
|
[ ] **Aktivace Windows** (CEKA)
|
||||||
|
Aktivace Windows pomoci klice z configu nebo GVLK (KMS). Spec kolegy toto nezminuje.
|
||||||
|
|
||||||
|
## 2. Odstranovani bloatware
|
||||||
|
|
||||||
|
[ ] **AppX balicky (UWP aplikace)** (CEKA)
|
||||||
|
Odebirame ~65 preinstalovanych aplikaci (Solitaire, Xbox, Teams, Copilot, Mail...). Kalkulacka zustava.
|
||||||
|
|
||||||
|
[ ] **Windows Capabilities** (CEKA)
|
||||||
|
Odebirame: Fax, IE, WordPad, PowerShell ISE, Steps Recorder, WMP, Handwriting...
|
||||||
|
|
||||||
|
[ ] **Optional Features** (CEKA)
|
||||||
|
Vypnuti: MediaPlayback, PowerShell 2.0, Recall (AI), SnippingTool.
|
||||||
|
|
||||||
|
[?] **Flash2 integrace** (K DISKUSI)
|
||||||
|
Flash2 je nastroj kolegy pro debloating. Integrovat, nebo pouzit nase kroky 2a-2c?
|
||||||
|
> flash2 je v podstatě tento setup, minulá verze :-) takže je hlavně pro inspiraci a zahrnutí
|
||||||
|
|
||||||
|
|
||||||
|
[?] **OneDrive - NEMAZAT** (K DISKUSI)
|
||||||
|
Nas skript agresivne maze OneDrive vcetne instalatoru. Spec kolegy OneDrive neresi = nechat!
|
||||||
|
> nevím jestli v poslední verzi už to bylo opravené, ale v první verzi se onedrive zabíjel nějakým regeditem nebo scheduled taskem
|
||||||
|
|
||||||
|
[?] **RDP/RDS - NEODEBIRAT** (K DISKUSI)
|
||||||
|
SPEC mel RDP klient k odebirani. Kolega to nechce - RDP musi zustat funkcni.
|
||||||
|
> nevím jestli v poslední verzi už to bylo opravené, ale v první verzi se rdp problematizovalo nějakým regeditem nebo scheduled taskem
|
||||||
|
|
||||||
|
## 3. Instalace software
|
||||||
|
|
||||||
|
[ ] **Winget balicky (7-Zip, Adobe, OpenVPN)** (CEKA)
|
||||||
|
Silent instalace 7-Zip, Adobe Acrobat Reader, OpenVPN Connect pres winget.
|
||||||
|
|
||||||
|
[?] **Atera Agent (MSI)** (K DISKUSI)
|
||||||
|
Silent instalace Atera monitoring agenta z C:\X9\install\atera-agent\.
|
||||||
|
> použít tohle a nejlíp najít parametr, u kterého ATERA nebude chtí MFA kod z mailu:-)
|
||||||
|
curl -L -o setup.msi "https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337" && msiexec /i setup.msi /qn
|
||||||
|
|
||||||
|
[ ] **BackInfo (info na plose)** (CEKA)
|
||||||
|
BackInfo.exe zobrazi hostname, user, OS, HW, sit uprostred plochy. Konfigurovatelny pres INI.
|
||||||
|
|
||||||
|
## 4. Vzhled a personalizace
|
||||||
|
|
||||||
|
[ ] **Barvy a motiv** (CEKA)
|
||||||
|
Tmavy system, svetle aplikace, accent #223B47, plna barva pozadi.
|
||||||
|
|
||||||
|
[ ] **Ikona Tento pocitac na plose** (CEKA)
|
||||||
|
Zobrazit ikonu Tento pocitac na plose.
|
||||||
|
|
||||||
|
[?] **Avatar uctu (X9 logo)** (K DISKUSI)
|
||||||
|
Nastaveni X9-logo.jpg jako profiloveho obrazku admin uctu.
|
||||||
|
> přidal jsem logo a ico do rootu do složky LOGO
|
||||||
|
|
||||||
|
## 5. Hlavni panel a Start menu
|
||||||
|
|
||||||
|
[ ] **Taskbar - zarovnani, skryti prvku** (CEKA)
|
||||||
|
Zarovnani vlevo, skryti Search, Task View, Widgets, Chat, Copilot.
|
||||||
|
|
||||||
|
[ ] **System tray - zobrazit vsechny ikony** (CEKA)
|
||||||
|
EnableAutoTray=0 + mazani icon cache + scheduled task ShowAllTrayIcons.
|
||||||
|
|
||||||
|
[ ] **Prazdny taskbar pinlist + Start menu** (CEKA)
|
||||||
|
Prazdny LayoutModification.xml (zadne pripnute apps), prazdne Start menu pins.
|
||||||
|
|
||||||
|
## 6. Systemova nastaveni
|
||||||
|
|
||||||
|
[ ] **HKLM registry tweaky** (CEKA)
|
||||||
|
BypassNRO, vypnuti Teams/Widgets/Copilot/GameDVR/Recall, hesla bez expirace, casova zona.
|
||||||
|
|
||||||
|
[ ] **Default Profile (NTUSER.DAT)** (CEKA)
|
||||||
|
Nastaveni pro vsechny budouci uzivatele: Explorer, Num Lock, GameDVR, Copilot...
|
||||||
|
|
||||||
|
[ ] **Napajeni (powercfg)** (CEKA)
|
||||||
|
Spanek nikdy na siti, obrazovka 60min/15min, spanek baterie 60min.
|
||||||
|
|
||||||
|
[ ] **Scheduled tasks** (CEKA)
|
||||||
|
ShowAllTrayIcons, PDF-DefaultApp, UnlockStartLayout.
|
||||||
|
|
||||||
|
## 7. Sit a Edge
|
||||||
|
|
||||||
|
[ ] **Proxy - vypnout auto-detect** (CEKA)
|
||||||
|
Vypnuti automatickeho zjistovani proxy serveru.
|
||||||
|
|
||||||
|
[ ] **MS Edge - rozsirene nastaveni** (CEKA)
|
||||||
|
Striktni tracking protection, Google vyhledavac, panel oblibenych, toolbar tlacitka.
|
||||||
|
|
||||||
|
## 8. Finalizace
|
||||||
|
|
||||||
|
[ ] **Prejmenování PC** (CEKA)
|
||||||
|
Rename-Computer na nazev z parametru -ComputerName. Vyzaduje restart.
|
||||||
|
|
||||||
|
[ ] **Bootstrap spoustec (irm | iex)** (CEKA)
|
||||||
|
Jednoradkovy spoustec z webu: irm https://xetup.x9.cz/setup.ps1 | iex
|
||||||
|
|
||||||
163
docs/xetup-win-setup-novinky.md
Normal file
163
docs/xetup-win-setup-novinky.md
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
# Xetup – Novinky oproti původní specifikaci (W11.pdf v3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A. Hlavní panel – pinnované aplikace (diferenciace admin vs user)
|
||||||
|
|
||||||
|
Nový požadavek: různé sady připnutých aplikací podle typu profilu.
|
||||||
|
|
||||||
|
**Admin profil – připnout na taskbar:**
|
||||||
|
- Nastavení (`ms-settings:`)
|
||||||
|
- Správa počítače (`compmgmt.msc`)
|
||||||
|
- Služby (`services.msc`)
|
||||||
|
- PowerShell (`pwsh.exe` nebo `powershell.exe`)
|
||||||
|
- Průzkumník Windows (`explorer.exe`)
|
||||||
|
- MS Edge (`msedge.exe`)
|
||||||
|
|
||||||
|
**User profil – připnout na taskbar:**
|
||||||
|
- Průzkumník Windows (`explorer.exe`)
|
||||||
|
- MS Edge (`msedge.exe`)
|
||||||
|
|
||||||
|
**Implementace:**
|
||||||
|
|
||||||
|
Přímé pinnování přes registry/PS je v moderním Win11 neoficiální – Microsoft odstranil `Pin-Application` cmdlet. Nejspolehlivější postup je XML layout policy:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- TaskbarLayoutModification.xml – admin varianta -->
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LayoutModificationTemplate xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
|
||||||
|
xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout" Version="1">
|
||||||
|
<CustomTaskbarLayoutCollection PinListPlacement="Replace">
|
||||||
|
<defaultlayout:TaskbarLayout xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout">
|
||||||
|
<taskbar:TaskbarPinList>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\Windows PowerShell\Windows PowerShell.lnk"/>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\File Explorer.lnk"/>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/>
|
||||||
|
</taskbar:TaskbarPinList>
|
||||||
|
</defaultlayout:TaskbarLayout>
|
||||||
|
</CustomTaskbarLayoutCollection>
|
||||||
|
</LayoutModificationTemplate>
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Aplikovat layout policy (HKLM = platí pro všechny uživatele)
|
||||||
|
# Admin varianta
|
||||||
|
$xmlPathAdmin = "C:\X9\TaskbarAdmin.xml"
|
||||||
|
$xmlPathUser = "C:\X9\TaskbarUser.xml"
|
||||||
|
|
||||||
|
# Pro Default Profile (user) – zapsat před prvním přihlášením uživatele
|
||||||
|
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
|
/v "LayoutXMLPath" /t REG_SZ /d $xmlPathUser /f
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Upozornění:** `LayoutXMLPath` policy je dostupná ve Win11 22H2+. Před nasazením ověřit verzi. Shortcuty pro `compmgmt.msc` a `services.msc` je nutné vytvořit ručně jako `.lnk` soubory, protože XML přijímá pouze `.lnk` cesty.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B. Průzkumník Windows – nastavení
|
||||||
|
|
||||||
|
Tři změny oproti původní specifikaci:
|
||||||
|
|
||||||
|
| Nastavení | Hodnota |
|
||||||
|
|---|---|
|
||||||
|
| Otevřít Průzkumník pro | Tento počítač (místo Rychlý přístup) |
|
||||||
|
| Nedávné soubory / složky / Office.com | Vše vypnuto |
|
||||||
|
| Zobrazit úplnou cestu v záhlaví | Zapnuto |
|
||||||
|
|
||||||
|
**Registry (aplikovat do Default Profile hive):**
|
||||||
|
|
||||||
|
```registry
|
||||||
|
; Otevřít pro "Tento počítač" místo Rychlého přístupu
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
|
||||||
|
- LaunchTo = 1 (DWORD) ; 1 = Tento počítač, 2 = Rychlý přístup
|
||||||
|
|
||||||
|
; Vypnout historii posledních souborů a složek
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer
|
||||||
|
- ShowRecent = 0 (DWORD)
|
||||||
|
- ShowFrequent = 0 (DWORD)
|
||||||
|
|
||||||
|
; Zobrazit úplnou cestu v záhlaví
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState
|
||||||
|
- FullPath = 1 (DWORD)
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Blok pro vložení do sekce reg load/unload (Default Profile)
|
||||||
|
$hive = "HKU\DefaultUser"
|
||||||
|
|
||||||
|
Set-ItemProperty "Registry::$hive\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "LaunchTo" -Value 1 -Type DWord
|
||||||
|
|
||||||
|
Set-ItemProperty "Registry::$hive\Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
|
-Name "ShowRecent" -Value 0 -Type DWord
|
||||||
|
Set-ItemProperty "Registry::$hive\Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
|
-Name "ShowFrequent" -Value 0 -Type DWord
|
||||||
|
|
||||||
|
New-Item -Path "Registry::$hive\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState" `
|
||||||
|
-Force | Out-Null
|
||||||
|
Set-ItemProperty "Registry::$hive\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState" `
|
||||||
|
-Name "FullPath" -Value 1 -Type DWord
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C. Síťové zjišťování – zapnout ping, přepnout na privátní síť
|
||||||
|
|
||||||
|
Nový krok: po přejmenování/připojení do domény zapnout zjišťování sítě a přepnout profil sítě na privátní (bez toho nefunguje ping na stanici).
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Zapnout Network Discovery a File Sharing
|
||||||
|
netsh advfirewall firewall set rule group="Network Discovery" new enable=Yes
|
||||||
|
netsh advfirewall firewall set rule group="File and Printer Sharing" new enable=Yes
|
||||||
|
|
||||||
|
# Přepnout aktuální síťový profil na Private (= "Ne, chci síť změnit na privátní")
|
||||||
|
# Funguje pro první aktivní síťové rozhraní
|
||||||
|
$adapter = Get-NetConnectionProfile | Select-Object -First 1
|
||||||
|
Set-NetConnectionProfile -InterfaceIndex $adapter.InterfaceIndex -NetworkCategory Private
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Scope:** Toto nastavení se aplikuje na aktuální síťové připojení v době spuštění skriptu – nelze předem uložit do Default Profile, protože se váže na konkrétní síťový adaptér/GUID. Skript musí být spuštěn po připojení k síti.
|
||||||
|
|
||||||
|
> **Závislost:** Pokud se stanice teprve připojuje do domény, spustit tento blok až po restartu a domain-join.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## D. Popis účtu adminx9
|
||||||
|
|
||||||
|
Nový krok: nastavit pole "Jméno a příjmení" u lokálního účtu `adminx9` na hodnotu `X9.cz s.r.o.`
|
||||||
|
|
||||||
|
Provádí se přes Správu počítače → Místní uživatelé a skupiny → Uživatelé → adminx9 → Vlastnosti → záložka Obecné.
|
||||||
|
|
||||||
|
**Automatizace:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Nastavit Full Name pro lokální účet adminx9
|
||||||
|
$user = [ADSI]"WinNT://$env:COMPUTERNAME/adminx9,user"
|
||||||
|
$user.FullName = "X9.cz s.r.o."
|
||||||
|
$user.SetInfo()
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Scope:** Platí pouze pro aktuální (admin) účet na tomto stroji – není součástí Default Profile. Spustit před předáním stanice klientovi.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integrace do hlavního skriptu
|
||||||
|
|
||||||
|
Nové kroky přidat do architektury spuštění:
|
||||||
|
|
||||||
|
```
|
||||||
|
Deploy-Windows.ps1
|
||||||
|
├── ... (existující kroky)
|
||||||
|
├── NEW: Popis účtu adminx9 = "X9.cz s.r.o."
|
||||||
|
├── NEW: Průzkumník – registry do Default Profile hive
|
||||||
|
├── NEW: Taskbar XML layout (admin nebo user varianta dle parametru)
|
||||||
|
├── 9. Přejmenování PC + restart
|
||||||
|
└── NEW: Po restartu – zapnout síťové zjišťování + privátní profil
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nový parametr skriptu:**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
[ValidateSet("admin","user")]
|
||||||
|
[string]$ProfileType = "user" # řídí TaskbarLayout XML i zarovnání panelu
|
||||||
|
```
|
||||||
347
docs/xetup-win-setup-spec.md
Normal file
347
docs/xetup-win-setup-spec.md
Normal file
|
|
@ -0,0 +1,347 @@
|
||||||
|
# Xetup – Specifikace automatizovaného nastavení Windows 10/11
|
||||||
|
|
||||||
|
> **Účel:** Eliminovat ~3 hodiny ručního nastavování nových Windows stanic pro klienty X9.cz
|
||||||
|
> **Cíl:** Jeden PowerShell skript spustitelný z admina, výsledek uložen v Default Profile (aplikuje se všem budoucím uživatelům)
|
||||||
|
> **Repo:** `C:\x9\xetup`
|
||||||
|
> **Vstup:** Parametrizovaný spouštěč – název PC, doména (volitelná), typ profilu (admin/user)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Struktura adresářů
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\X9\
|
||||||
|
├── install\
|
||||||
|
│ ├── Backinfo\
|
||||||
|
│ ├── flash2\
|
||||||
|
│ ├── atera-agent\
|
||||||
|
│ ├── 7zip.exe
|
||||||
|
│ ├── AdobeReader.exe
|
||||||
|
│ └── OpenVPNConnect.exe
|
||||||
|
├── X9-ikona.ico
|
||||||
|
└── X9-logo.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kroky:**
|
||||||
|
- [ ] Vytvořit `C:\X9` a `C:\X9\install`
|
||||||
|
- [ ] Stáhnout z Nextcloudu: Backinfo, Atera agent, Flash2, ikonu X9, logo X9
|
||||||
|
- [ ] Stáhnout z internetu: 7-zip, Adobe Reader, OpenVPN Connect
|
||||||
|
- [ ] Roztřídit soubory do správných adresářů dle výše
|
||||||
|
|
||||||
|
> **Automatizace:** `Invoke-WebRequest` pro stažení + `Copy-Item` pro přesun. Nextcloud vyžaduje autentizaci – credentials parametrem nebo uloženým tokenem.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Přizpůsobení – Pozadí a barvy (Default Profile)
|
||||||
|
|
||||||
|
Všechna nastavení ukládat do `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes` + Default Profile hive (`C:\Users\Default\NTUSER.DAT`).
|
||||||
|
|
||||||
|
| Nastavení | Hodnota |
|
||||||
|
|---|---|
|
||||||
|
| Typ pozadí | Plná barva |
|
||||||
|
| Barva pozadí | `#223B47` |
|
||||||
|
| Barevný režim | Vlastní |
|
||||||
|
| Režim Windows | Tmavý |
|
||||||
|
| Režim aplikací | Světlý |
|
||||||
|
| Barva motivu | Ruční – `#223B47` |
|
||||||
|
| Barva v Start menu | Zapnuto |
|
||||||
|
| Barva v záhlavích oken | Zapnuto |
|
||||||
|
|
||||||
|
**Registry klíče (NTUSER.DAT / Default Profile):**
|
||||||
|
```
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize
|
||||||
|
- AppsUseLightTheme = 1 (DWORD)
|
||||||
|
- SystemUsesLightTheme = 0 (DWORD)
|
||||||
|
- ColorPrevalence = 1 (DWORD)
|
||||||
|
|
||||||
|
HKCU\Control Panel\Desktop
|
||||||
|
- Wallpaper = "" (prázdný string = plná barva)
|
||||||
|
- WallpaperStyle = 0
|
||||||
|
|
||||||
|
HKCU\Control Panel\Colors
|
||||||
|
- Background = "34 59 71" (RGB hodnota #223B47)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Poznámka:** Barvu motivu (`AccentColor`) nastavit přes `HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent` – hodnota `AccentColorMenu` = `0xFF3B2322` (ABGR formát).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Přizpůsobení – Motivy (ikony na ploše)
|
||||||
|
|
||||||
|
- [ ] Zobrazit ikonu **Tento počítač** na ploše
|
||||||
|
|
||||||
|
```registry
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel
|
||||||
|
- {20D04FE0-3AEA-1069-A2D8-08002B30309D} = 0 (DWORD) ; Tento počítač
|
||||||
|
```
|
||||||
|
|
||||||
|
> Aplikovat do Default Profile hive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Hlavní panel (Taskbar)
|
||||||
|
|
||||||
|
| Nastavení | Hodnota |
|
||||||
|
|---|---|
|
||||||
|
| Vyhledávací pole | Skryto |
|
||||||
|
| Zobrazení úkolů (Task View) | Vypnuto |
|
||||||
|
| Widgety | Vypnuto |
|
||||||
|
| Zarovnání (uživatel) | Vlevo |
|
||||||
|
| Zarovnání (admin) | Na střed |
|
||||||
|
| Systémové ikony (overflow) | Vše viditelné |
|
||||||
|
|
||||||
|
**Registry (Win11):**
|
||||||
|
```registry
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Search
|
||||||
|
- SearchboxTaskbarMode = 0 (DWORD) ; skrýt hledání
|
||||||
|
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
|
||||||
|
- ShowTaskViewButton = 0 (DWORD)
|
||||||
|
- TaskbarAl = 0 (DWORD) ; 0=vlevo, 1=střed
|
||||||
|
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People
|
||||||
|
- PeopleBand = 0 (DWORD)
|
||||||
|
|
||||||
|
HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Feeds
|
||||||
|
- EnableFeeds = 0 (DWORD) ; widgety
|
||||||
|
```
|
||||||
|
|
||||||
|
**Viditelnost systémových ikon (overflow area):**
|
||||||
|
```registry
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer
|
||||||
|
- EnableAutoTray = 0 (DWORD) ; zobrazit vše, nevracet do přetečení
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Problém:** Automatické zobrazení VŠECH budoucích ikon (nový SW po instalaci) nelze 100% garantovat přes registry – `EnableAutoTray=0` je nejbližší řešení. Individuální ikony aplikací závisí na tom, co si každá aplikace sama zapíše.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Připnutí složky X9 na hlavní panel
|
||||||
|
|
||||||
|
- [ ] Složce `C:\X9` nastavit vlastní ikonu (`X9-ikona.ico`)
|
||||||
|
- [ ] Připnout na panel rychlého spuštění (Quick Access / Taskbar)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Nastavení vlastní ikony složky (desktop.ini)
|
||||||
|
$iniPath = "C:\X9\desktop.ini"
|
||||||
|
Set-Content $iniPath "[.ShellClassInfo]`nIconResource=C:\X9\X9-ikona.ico,0`n[ViewState]`nMode=`nVid=`nFolderType=Generic"
|
||||||
|
attrib +s +h $iniPath
|
||||||
|
attrib +s "C:\X9"
|
||||||
|
|
||||||
|
# Připnutí na taskbar – vyžaduje Shell COM objekt nebo workaround přes VBScript
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Upozornění:** Připnutí složky na taskbar je v moderním Win11 omezené – Microsoft tuto možnost odstranil. Alternativa: připnout jako Quick Access v Průzkumníku nebo vytvořit shortcut na ploše.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Profil účtu – avatar
|
||||||
|
|
||||||
|
- [ ] Nahrát `X9-logo.jpg` jako profilový obrázek admin účtu
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$accountPicPath = "$env:APPDATA\Microsoft\Windows\AccountPictures"
|
||||||
|
New-Item -ItemType Directory -Force -Path $accountPicPath
|
||||||
|
Copy-Item "C:\X9\X9-logo.jpg" "$accountPicPath\X9-logo.jpg"
|
||||||
|
# Nastavit jako výchozí profilový obrázek přes registry
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Scope:** Toto platí jen pro aktuální (admin) účet, nikoliv Default Profile – je to záměr.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Napájení
|
||||||
|
|
||||||
|
| Nastavení | Hodnota |
|
||||||
|
|---|---|
|
||||||
|
| Spánek při napájení ze sítě | Nikdy |
|
||||||
|
| Vypnutí obrazovky (síť) | 1 hodina |
|
||||||
|
| Vypnutí obrazovky (baterie) | 15 minut |
|
||||||
|
| Spánek na baterii | 1 hodina |
|
||||||
|
| Zavření víka | Neautomatizovat |
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powercfg /change standby-timeout-ac 0 # nikdy - síť
|
||||||
|
powercfg /change monitor-timeout-ac 60 # 60 min - síť
|
||||||
|
powercfg /change monitor-timeout-dc 15 # 15 min - baterie
|
||||||
|
powercfg /change standby-timeout-dc 60 # 60 min - baterie
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Síť – Proxy server
|
||||||
|
|
||||||
|
- [ ] Vypnout automatické zjišťování nastavení proxy
|
||||||
|
|
||||||
|
```registry
|
||||||
|
HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings
|
||||||
|
- AutoDetect = 0 (DWORD)
|
||||||
|
|
||||||
|
HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings
|
||||||
|
- AutoDetect = 0 (DWORD)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Přejmenování počítače
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
param([string]$NewName)
|
||||||
|
Rename-Computer -NewName $NewName -Force -Restart
|
||||||
|
```
|
||||||
|
|
||||||
|
> Restart je nutný. Připojení do domény probíhá zvlášť přes stejné rozhraní – není součástí tohoto skriptu (volitelný parametr `-Domain`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Odinstalace bloatware
|
||||||
|
|
||||||
|
Aktuálně řešeno přes **Flash2** – integrovat volání Flash2 instalačky nebo zachovat jako samostatný krok.
|
||||||
|
|
||||||
|
Alternativně vlastní seznam přes winget/AppX:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Příklady AppX balíčků k odebrání (rozšířit dle potřeby)
|
||||||
|
$bloatware = @(
|
||||||
|
"Microsoft.BingWeather",
|
||||||
|
"Microsoft.GetHelp",
|
||||||
|
"Microsoft.Getstarted",
|
||||||
|
"Microsoft.MicrosoftSolitaireCollection",
|
||||||
|
"Microsoft.People",
|
||||||
|
"Microsoft.WindowsFeedbackHub",
|
||||||
|
"Microsoft.Xbox.TCUI",
|
||||||
|
"Microsoft.XboxApp",
|
||||||
|
"Microsoft.ZuneMusic",
|
||||||
|
"Microsoft.ZuneVideo"
|
||||||
|
)
|
||||||
|
foreach ($app in $bloatware) {
|
||||||
|
Get-AppxPackage -Name $app -AllUsers | Remove-AppxPackage -AllUsers -ErrorAction SilentlyContinue
|
||||||
|
Get-AppxProvisionedPackage -Online | Where-Object DisplayName -like $app | Remove-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> `Remove-AppxProvisionedPackage` zajistí, že se bloatware neobjeví ani novým uživatelům (Default Profile).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Nastavení uživatelského profilu (Default Profile)
|
||||||
|
|
||||||
|
Klíčový mechanismus: **načíst `C:\Users\Default\NTUSER.DAT` jako dočasný hive**, aplikovat registry změny, odpojit hive.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
reg load "HKU\DefaultUser" "C:\Users\Default\NTUSER.DAT"
|
||||||
|
# ... zde všechny Set-ItemProperty operace s cestou HKU:\DefaultUser\...
|
||||||
|
reg unload "HKU\DefaultUser"
|
||||||
|
```
|
||||||
|
|
||||||
|
> Tímto způsobem se veškerá nastavení (barvy, taskbar, Edge, atd.) aplikují všem budoucím uživatelům při prvním přihlášení.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Instalace SW
|
||||||
|
|
||||||
|
| Aplikace | Zdroj | Metoda |
|
||||||
|
|---|---|---|
|
||||||
|
| 7-Zip | Internet / winget | `winget install 7zip.7zip` |
|
||||||
|
| Adobe Reader | Internet / winget | `winget install Adobe.Acrobat.Reader.64-bit` |
|
||||||
|
| OpenVPN Connect | Internet / winget | `winget install OpenVPNTechnologies.OpenVPNConnect` |
|
||||||
|
| Atera Agent | Nextcloud (`C:\X9\install`) | MSI silent install |
|
||||||
|
| Backinfo | Nextcloud (`C:\X9\install`) | PS skript |
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
winget install --id 7zip.7zip --silent --accept-package-agreements --accept-source-agreements
|
||||||
|
winget install --id Adobe.Acrobat.Reader.64-bit --silent --accept-package-agreements --accept-source-agreements
|
||||||
|
winget install --id OpenVPNTechnologies.OpenVPNConnect --silent --accept-package-agreements --accept-source-agreements
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Backinfo
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Copy-Item "C:\X9\install\Backinfo" "C:\Program Files\Backinfo" -Recurse
|
||||||
|
Set-ExecutionPolicy Unrestricted -Force
|
||||||
|
& "C:\Program Files\Backinfo\backinfo_W11.ps1"
|
||||||
|
Set-ExecutionPolicy Restricted -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
> Backinfo zapíše do registru verzi W11 a nastaví autostart po přihlášení.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. MS Edge – nastavení
|
||||||
|
|
||||||
|
Nastavit přes registry (platí pro všechny uživatele pokud aplikováno do Default Profile nebo HKLM).
|
||||||
|
|
||||||
|
| Nastavení | Hodnota |
|
||||||
|
|---|---|
|
||||||
|
| Ochrana sledování | Striktní (`2`) |
|
||||||
|
| Panel oblíbených | Vždy zobrazit |
|
||||||
|
| Výchozí vyhledávač | Google |
|
||||||
|
| Tlačítka panelu nástrojů | Historie, Aplikace, Stažené soubory, Výkon |
|
||||||
|
|
||||||
|
```registry
|
||||||
|
HKLM\SOFTWARE\Policies\Microsoft\Edge
|
||||||
|
- TrackingPrevention = 3 (DWORD) ; Striktní
|
||||||
|
- FavoritesBarEnabled = 1 (DWORD) ; Oblíbené vždy
|
||||||
|
- DefaultSearchProviderEnabled = 1
|
||||||
|
- DefaultSearchProviderName = "Google"
|
||||||
|
- DefaultSearchProviderSearchURL = "https://www.google.com/search?q={searchTerms}"
|
||||||
|
|
||||||
|
; Toolbar tlačítka – individuální nastavení přes Edge policy nebo NTUSER.DAT
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Poznámka:** Edge nastavení přes GPO/registry mají přednost před uživatelskými preferencemi. Pro Default Profile alternativně upravit `%LOCALAPPDATA%\Microsoft\Edge\User Data\Default\Preferences` šablonou – méně spolehlivé.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Spouštěč – parametry hlavního skriptu
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Deploy-Windows.ps1
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$ComputerName,
|
||||||
|
|
||||||
|
[string]$Domain = "", # prázdné = pracovní skupina
|
||||||
|
|
||||||
|
[ValidateSet("admin","user")]
|
||||||
|
[string]$TaskbarAlign = "user", # admin=střed, user=vlevo
|
||||||
|
|
||||||
|
[string]$NextcloudUrl = "",
|
||||||
|
[string]$NextcloudUser = "",
|
||||||
|
[string]$NextcloudPass = ""
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Otevřené otázky / TODO
|
||||||
|
|
||||||
|
- [ ] Jak řešit stahování z Nextcloudu bez interaktivního přihlášení? (token vs. credentials parametr)
|
||||||
|
- [ ] Flash2 – integrovat nebo volat jako subprocess?
|
||||||
|
- [ ] Atera Agent – silent install parametry MSI?
|
||||||
|
- [ ] Přejmenování PC + připojení do domény jako jeden průchod nebo dva samostatné kroky?
|
||||||
|
- [ ] Testování: Win10 vs Win11 – některé registry klíče se liší (hlavně Taskbar)
|
||||||
|
- [ ] Složka X9 na Taskbaru – Win11 nepodporuje připnutí složky; nahradit shortcutem na ploše?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architektura spuštění (navržená)
|
||||||
|
|
||||||
|
```
|
||||||
|
irm https://xetup.x9.cz/setup.ps1 | iex
|
||||||
|
└── Stáhne Deploy-Windows.ps1 z repa
|
||||||
|
└── Vyzve na parametry (nebo převezme z CLI)
|
||||||
|
└── Spustí Deploy-Windows.ps1 -ExecutionPolicy Bypass
|
||||||
|
├── 1. Vytvoří adresáře
|
||||||
|
├── 2. Stáhne soubory (Nextcloud + web)
|
||||||
|
├── 3. Odinstaluje bloatware (AppX + Flash2)
|
||||||
|
├── 4. Nainstaluje SW (winget)
|
||||||
|
├── 5. Aplikuje registry do Default Profile (reg load/unload)
|
||||||
|
├── 6. Nastaví napájení
|
||||||
|
├── 7. Nastaví proxy
|
||||||
|
├── 8. Spustí Backinfo
|
||||||
|
├── 9. Přejmenuje PC
|
||||||
|
└── 10. Restart
|
||||||
|
```
|
||||||
16
embed.go
Normal file
16
embed.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Package xetup exposes embedded PowerShell scripts and assets for use by
|
||||||
|
// cmd/xetup. Placing the embed declarations here (at the module root) gives
|
||||||
|
// the //go:embed directives a clear, stable relative path to the content.
|
||||||
|
package xetup
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
// Scripts holds all PowerShell scripts from the scripts/ directory.
|
||||||
|
//
|
||||||
|
//go:embed scripts
|
||||||
|
var Scripts embed.FS
|
||||||
|
|
||||||
|
// Assets holds all deployment assets from the assets/ directory.
|
||||||
|
//
|
||||||
|
//go:embed assets
|
||||||
|
var Assets embed.FS
|
||||||
40
go.mod
Normal file
40
go.mod
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
module git.xetup.x9.cz/x9/xetup
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require fyne.io/fyne/v2 v2.7.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
fyne.io/systray v1.12.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/fredbi/uri v1.1.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/fyne-io/gl-js v0.2.0 // indirect
|
||||||
|
github.com/fyne-io/glfw-js v0.3.0 // indirect
|
||||||
|
github.com/fyne-io/image v0.1.1 // indirect
|
||||||
|
github.com/fyne-io/oksvg v0.2.0 // indirect
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
||||||
|
github.com/go-text/render v0.2.0 // indirect
|
||||||
|
github.com/go-text/typesetting v0.3.3 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||||
|
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rymdport/portal v0.4.2 // indirect
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
|
github.com/yuin/goldmark v1.7.8 // indirect
|
||||||
|
golang.org/x/image v0.24.0 // indirect
|
||||||
|
golang.org/x/net v0.35.0 // indirect
|
||||||
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
|
golang.org/x/text v0.23.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
80
go.sum
Normal file
80
go.sum
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
fyne.io/fyne/v2 v2.7.3 h1:xBT/iYbdnNHONWO38fZMBrVBiJG8rV/Jypmy4tVfRWE=
|
||||||
|
fyne.io/fyne/v2 v2.7.3/go.mod h1:gu+dlIcZWSzKZmnrY8Fbnj2Hirabv2ek+AKsfQ2bBlw=
|
||||||
|
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
|
||||||
|
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||||
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
|
github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko=
|
||||||
|
github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
|
||||||
|
github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||||
|
github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk=
|
||||||
|
github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
||||||
|
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
||||||
|
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
||||||
|
github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8=
|
||||||
|
github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||||
|
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||||
|
github.com/go-text/typesetting v0.3.3 h1:ihGNJU9KzdK2QRDy1Bm7FT5RFQoYb+3n3EIhI/4eaQc=
|
||||||
|
github.com/go-text/typesetting v0.3.3/go.mod h1:vIRUT25mLQaSh4C8H/lIsKppQz/Gdb8Pu/tNwpi52ts=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||||
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
|
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
|
||||||
|
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
||||||
|
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
||||||
|
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||||
|
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
|
||||||
|
github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
|
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||||
|
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||||
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
145
internal/config/config.go
Normal file
145
internal/config/config.go
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config mirrors config.json structure.
|
||||||
|
type Config struct {
|
||||||
|
Deployment Deployment `json:"deployment"`
|
||||||
|
AdminAccount AdminAccount `json:"adminAccount"`
|
||||||
|
Activation Activation `json:"activation"`
|
||||||
|
Software Software `json:"software"`
|
||||||
|
Steps map[string]bool `json:"steps"`
|
||||||
|
Features Features `json:"features"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Deployment struct {
|
||||||
|
PCName string `json:"pcName"`
|
||||||
|
PCDescription string `json:"pcDescription"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
ProfileType string `json:"profileType"` // default | admin | user
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminAccount struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Activation struct {
|
||||||
|
ProductKey string `json:"productKey"`
|
||||||
|
KMSServer string `json:"kmsServer"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SoftwareItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
WingetID string `json:"wingetId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Software struct {
|
||||||
|
Install []SoftwareItem `json:"install"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Features holds per-step, per-feature toggle flags.
|
||||||
|
// Keys: stepID -> featureID -> enabled.
|
||||||
|
// A missing key defaults to true (feature enabled).
|
||||||
|
type Features map[string]map[string]bool
|
||||||
|
|
||||||
|
// DefaultConfig returns a config with sensible defaults.
|
||||||
|
func DefaultConfig() Config {
|
||||||
|
return Config{
|
||||||
|
Deployment: Deployment{
|
||||||
|
Timezone: "Central Europe Standard Time",
|
||||||
|
ProfileType: "default",
|
||||||
|
},
|
||||||
|
AdminAccount: AdminAccount{
|
||||||
|
Username: "adminx9",
|
||||||
|
},
|
||||||
|
Activation: Activation{
|
||||||
|
ProductKey: "",
|
||||||
|
},
|
||||||
|
Software: Software{
|
||||||
|
Install: []SoftwareItem{
|
||||||
|
{Name: "7-Zip", WingetID: "7zip.7zip"},
|
||||||
|
{Name: "Adobe Acrobat Reader 64-bit", WingetID: "Adobe.Acrobat.Reader.64-bit"},
|
||||||
|
{Name: "OpenVPN Connect", WingetID: "OpenVPNTechnologies.OpenVPNConnect"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Steps: map[string]bool{
|
||||||
|
"adminAccount": true,
|
||||||
|
"bloatware": true,
|
||||||
|
"software": true,
|
||||||
|
"systemRegistry": true,
|
||||||
|
"defaultProfile": true,
|
||||||
|
"personalization": true,
|
||||||
|
"scheduledTasks": true,
|
||||||
|
"backinfo": true,
|
||||||
|
"activation": true,
|
||||||
|
"dellUpdate": true,
|
||||||
|
"network": true,
|
||||||
|
"pcIdentity": true,
|
||||||
|
},
|
||||||
|
Features: Features{
|
||||||
|
"software": {
|
||||||
|
"wingetInstalls": true,
|
||||||
|
"pdfDefault": true,
|
||||||
|
"ateraAgent": true,
|
||||||
|
},
|
||||||
|
"systemRegistry": {
|
||||||
|
"systemTweaks": true,
|
||||||
|
"edgePolicies": true,
|
||||||
|
"oneDriveUninstall": true,
|
||||||
|
"powercfg": true,
|
||||||
|
"proxyDisable": true,
|
||||||
|
},
|
||||||
|
"defaultProfile": {
|
||||||
|
"taskbarTweaks": true,
|
||||||
|
"startMenuTweaks": true,
|
||||||
|
"explorerTweaks": true,
|
||||||
|
},
|
||||||
|
"dellUpdate": {
|
||||||
|
"drivers": true,
|
||||||
|
"bios": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads config.json from the given path.
|
||||||
|
// If the file does not exist, returns DefaultConfig without error.
|
||||||
|
func Load(path string) (Config, error) {
|
||||||
|
cfg := DefaultConfig()
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save writes config to the given path (creates directories if needed).
|
||||||
|
func Save(cfg Config, path string) error {
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigPath returns the default config.json path (next to the executable).
|
||||||
|
func ConfigPath() string {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "config.json"
|
||||||
|
}
|
||||||
|
return filepath.Join(filepath.Dir(exe), "config.json")
|
||||||
|
}
|
||||||
363
internal/gui/gui.go
Normal file
363
internal/gui/gui.go
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
// Package gui implements the Fyne-based graphical interface for xetup.
|
||||||
|
//
|
||||||
|
// Three phases, one window:
|
||||||
|
// 1. Config form – PC name, product key, profile, step selection,
|
||||||
|
// load/save config buttons for per-client presets
|
||||||
|
// 2. Live run – real-time log streamed from PowerShell scripts
|
||||||
|
// 3. Summary – per-step OK / ERROR / SKIPPED with elapsed time
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"fyne.io/fyne/v2"
|
||||||
|
"fyne.io/fyne/v2/app"
|
||||||
|
"fyne.io/fyne/v2/container"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
|
"fyne.io/fyne/v2/storage"
|
||||||
|
"fyne.io/fyne/v2/theme"
|
||||||
|
"fyne.io/fyne/v2/widget"
|
||||||
|
|
||||||
|
"git.xetup.x9.cz/x9/xetup/internal/config"
|
||||||
|
"git.xetup.x9.cz/x9/xetup/internal/runner"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run opens the xetup window and blocks until the user closes it.
|
||||||
|
// cfgPath is the default config.json path (next to the exe).
|
||||||
|
func Run(cfg config.Config, runCfg runner.RunConfig, cfgPath string) {
|
||||||
|
// Force software (CPU/GDI) rendering so the app works on VMs and machines
|
||||||
|
// without proper OpenGL support (VMware SVGA, Hyper-V basic display, etc.).
|
||||||
|
// The UI is simple enough that GPU acceleration gives no benefit.
|
||||||
|
os.Setenv("FYNE_RENDERER", "software") //nolint:errcheck
|
||||||
|
|
||||||
|
a := app.New()
|
||||||
|
a.Settings().SetTheme(theme.DarkTheme())
|
||||||
|
|
||||||
|
w := a.NewWindow("xetup — Windows deployment")
|
||||||
|
w.Resize(fyne.NewSize(740, 680))
|
||||||
|
w.SetMaster() // closing this window quits the app
|
||||||
|
|
||||||
|
showForm(w, cfg, runCfg, cfgPath)
|
||||||
|
w.ShowAndRun()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Phase 1 – Config form
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func showForm(w fyne.Window, cfg config.Config, runCfg runner.RunConfig, cfgPath string) {
|
||||||
|
// ── Text inputs ─────────────────────────────────────────────────────────
|
||||||
|
pcName := widget.NewEntry()
|
||||||
|
pcName.SetPlaceHolder("napr. NB-KLIENT-01 (prazdne = neprejmenovat)")
|
||||||
|
pcName.SetText(cfg.Deployment.PCName)
|
||||||
|
|
||||||
|
pcDesc := widget.NewEntry()
|
||||||
|
pcDesc.SetPlaceHolder("napr. PC recepce")
|
||||||
|
pcDesc.SetText(cfg.Deployment.PCDescription)
|
||||||
|
|
||||||
|
productKey := widget.NewEntry()
|
||||||
|
productKey.SetPlaceHolder("prazdne = OA3 / GVLK fallback")
|
||||||
|
productKey.SetText(cfg.Activation.ProductKey)
|
||||||
|
|
||||||
|
profileSel := widget.NewSelect([]string{"default", "admin", "user"}, nil)
|
||||||
|
profileSel.SetSelected(cfg.Deployment.ProfileType)
|
||||||
|
|
||||||
|
// ── Step checkboxes ─────────────────────────────────────────────────────
|
||||||
|
items := runner.AllSelectableItems()
|
||||||
|
checks := make([]*widget.Check, len(items))
|
||||||
|
checkObjs := make([]fyne.CanvasObject, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
c := widget.NewCheck(item.Label, nil)
|
||||||
|
c.SetChecked(itemEnabled(cfg, item))
|
||||||
|
checks[i] = c
|
||||||
|
checkObjs[i] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
stepsScroll := container.NewVScroll(container.NewVBox(checkObjs...))
|
||||||
|
stepsScroll.SetMinSize(fyne.NewSize(0, 290))
|
||||||
|
|
||||||
|
// ── collectCfg reads current form state into a Config ───────────────────
|
||||||
|
collectCfg := func() config.Config {
|
||||||
|
out := cfg // start from loaded config (preserves fields not shown in form)
|
||||||
|
out.Deployment.PCName = pcName.Text
|
||||||
|
out.Deployment.PCDescription = pcDesc.Text
|
||||||
|
out.Activation.ProductKey = productKey.Text
|
||||||
|
out.Deployment.ProfileType = profileSel.Selected
|
||||||
|
|
||||||
|
selected := make(map[string]bool, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
selected[item.Key] = checks[i].Checked
|
||||||
|
}
|
||||||
|
_, features := buildStepsAndFeatures(selected)
|
||||||
|
out.Features = features
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Toolbar: Load / Save config ─────────────────────────────────────────
|
||||||
|
jsonFilter := storage.NewExtensionFileFilter([]string{".json"})
|
||||||
|
|
||||||
|
loadBtn := widget.NewButton("Nacist config...", func() {
|
||||||
|
d := dialog.NewFileOpen(func(rc fyne.URIReadCloser, err error) {
|
||||||
|
if err != nil || rc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
data, err := io.ReadAll(rc)
|
||||||
|
if err != nil {
|
||||||
|
dialog.ShowError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newCfg := config.DefaultConfig()
|
||||||
|
if err := json.Unmarshal(data, &newCfg); err != nil {
|
||||||
|
dialog.ShowError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Reload the entire form with the new config
|
||||||
|
showForm(w, newCfg, runCfg, rc.URI().Path())
|
||||||
|
}, w)
|
||||||
|
d.SetFilter(jsonFilter)
|
||||||
|
d.Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
saveBtn := widget.NewButton("Ulozit config...", func() {
|
||||||
|
d := dialog.NewFileSave(func(wc fyne.URIWriteCloser, err error) {
|
||||||
|
if err != nil || wc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer wc.Close()
|
||||||
|
data, err := json.MarshalIndent(collectCfg(), "", " ")
|
||||||
|
if err != nil {
|
||||||
|
dialog.ShowError(err, w)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := wc.Write(data); err != nil {
|
||||||
|
dialog.ShowError(err, w)
|
||||||
|
}
|
||||||
|
}, w)
|
||||||
|
d.SetFilter(jsonFilter)
|
||||||
|
d.SetFileName("config.json")
|
||||||
|
d.Show()
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── SPUSTIT ─────────────────────────────────────────────────────────────
|
||||||
|
startBtn := widget.NewButton(" SPUSTIT ", func() {
|
||||||
|
finalCfg := collectCfg()
|
||||||
|
runCfg.ProfileType = finalCfg.Deployment.ProfileType
|
||||||
|
|
||||||
|
selected := make(map[string]bool, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
selected[item.Key] = checks[i].Checked
|
||||||
|
}
|
||||||
|
steps, features := buildStepsAndFeatures(selected)
|
||||||
|
finalCfg.Features = features
|
||||||
|
|
||||||
|
_ = config.Save(finalCfg, cfgPath) // auto-save to default path
|
||||||
|
|
||||||
|
showRun(w, runCfg, steps)
|
||||||
|
})
|
||||||
|
startBtn.Importance = widget.HighImportance
|
||||||
|
|
||||||
|
// ── Layout ───────────────────────────────────────────────────────────────
|
||||||
|
form := widget.NewForm(
|
||||||
|
widget.NewFormItem("PC jmeno", pcName),
|
||||||
|
widget.NewFormItem("Popis PC", pcDesc),
|
||||||
|
widget.NewFormItem("Product Key", productKey),
|
||||||
|
widget.NewFormItem("Profil", profileSel),
|
||||||
|
)
|
||||||
|
|
||||||
|
toolbar := container.NewHBox(loadBtn, saveBtn)
|
||||||
|
|
||||||
|
w.SetContent(container.NewBorder(
|
||||||
|
form,
|
||||||
|
container.NewVBox(
|
||||||
|
widget.NewSeparator(),
|
||||||
|
container.NewBorder(nil, nil, toolbar, container.NewCenter(startBtn)),
|
||||||
|
),
|
||||||
|
nil, nil,
|
||||||
|
container.NewVBox(
|
||||||
|
widget.NewSeparator(),
|
||||||
|
widget.NewLabel("Kroky a nastaveni (odskrtnete co nechcete spustit):"),
|
||||||
|
stepsScroll,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Phase 2 – Live run view
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func showRun(w fyne.Window, runCfg runner.RunConfig, steps []runner.Step) {
|
||||||
|
statusLabel := widget.NewLabel("Spoustim...")
|
||||||
|
|
||||||
|
// Virtualised list – efficient for thousands of log lines
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
logLines []string
|
||||||
|
)
|
||||||
|
|
||||||
|
logList := widget.NewList(
|
||||||
|
func() int {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
return len(logLines)
|
||||||
|
},
|
||||||
|
func() fyne.CanvasObject {
|
||||||
|
return widget.NewLabel("")
|
||||||
|
},
|
||||||
|
func(id widget.ListItemID, obj fyne.CanvasObject) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if id < len(logLines) {
|
||||||
|
obj.(*widget.Label).SetText(logLines[id])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
var cancelFn context.CancelFunc
|
||||||
|
|
||||||
|
stopBtn := widget.NewButton(" ZASTAVIT ", func() {
|
||||||
|
if cancelFn != nil {
|
||||||
|
cancelFn()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stopBtn.Importance = widget.DangerImportance
|
||||||
|
|
||||||
|
w.SetContent(container.NewBorder(
|
||||||
|
container.NewVBox(statusLabel, widget.NewSeparator()),
|
||||||
|
container.NewCenter(container.NewPadded(stopBtn)),
|
||||||
|
nil, nil,
|
||||||
|
logList,
|
||||||
|
))
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancelFn = cancel
|
||||||
|
|
||||||
|
r := runner.New(
|
||||||
|
runCfg,
|
||||||
|
func(l runner.LogLine) {
|
||||||
|
mu.Lock()
|
||||||
|
logLines = append(logLines, l.Text)
|
||||||
|
mu.Unlock()
|
||||||
|
logList.Refresh()
|
||||||
|
logList.ScrollToBottom()
|
||||||
|
},
|
||||||
|
func(res runner.Result) {
|
||||||
|
statusLabel.SetText(fmt.Sprintf(
|
||||||
|
"Krok %s – %s: %s", res.Step.Num, res.Step.Name, res.Status,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
results := r.Run(ctx, steps)
|
||||||
|
showDone(w, results)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Phase 3 – Summary
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func showDone(w fyne.Window, results []runner.Result) {
|
||||||
|
ok, errs, skipped := 0, 0, 0
|
||||||
|
rows := make([]fyne.CanvasObject, 0, len(results))
|
||||||
|
|
||||||
|
for _, res := range results {
|
||||||
|
var icon string
|
||||||
|
switch res.Status {
|
||||||
|
case "OK":
|
||||||
|
ok++
|
||||||
|
icon = "OK "
|
||||||
|
case "ERROR":
|
||||||
|
errs++
|
||||||
|
icon = "ERR "
|
||||||
|
default:
|
||||||
|
skipped++
|
||||||
|
icon = "– "
|
||||||
|
}
|
||||||
|
text := icon + res.Step.Num + " – " + res.Step.Name
|
||||||
|
if res.Elapsed > 0 {
|
||||||
|
text += fmt.Sprintf(" (%s)", res.Elapsed.Round(time.Second))
|
||||||
|
}
|
||||||
|
rows = append(rows, widget.NewLabel(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
summary := widget.NewLabel(fmt.Sprintf(
|
||||||
|
"OK: %d CHYBY: %d PRESKOCENO: %d", ok, errs, skipped,
|
||||||
|
))
|
||||||
|
summary.TextStyle = fyne.TextStyle{Bold: true}
|
||||||
|
|
||||||
|
closeBtn := widget.NewButton(" ZAVRIT ", func() {
|
||||||
|
fyne.CurrentApp().Quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
w.SetContent(container.NewBorder(
|
||||||
|
widget.NewLabelWithStyle("Hotovo", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||||
|
container.NewVBox(
|
||||||
|
widget.NewSeparator(),
|
||||||
|
container.NewCenter(summary),
|
||||||
|
container.NewCenter(container.NewPadded(closeBtn)),
|
||||||
|
),
|
||||||
|
nil, nil,
|
||||||
|
container.NewVScroll(container.NewVBox(rows...)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// itemEnabled returns the initial checked state for a checkbox row,
|
||||||
|
// reading from the loaded config (defaults to true / enabled when absent).
|
||||||
|
func itemEnabled(cfg config.Config, item runner.SelectableItem) bool {
|
||||||
|
if item.FeatureID == "" {
|
||||||
|
if v, ok := cfg.Steps[item.StepID]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if feats, ok := cfg.Features[item.StepID]; ok {
|
||||||
|
if v, ok2 := feats[item.FeatureID]; ok2 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildStepsAndFeatures converts the flat checkbox map into the structures
|
||||||
|
// that runner.Runner and config.Config expect.
|
||||||
|
func buildStepsAndFeatures(selected map[string]bool) ([]runner.Step, config.Features) {
|
||||||
|
items := runner.AllSelectableItems()
|
||||||
|
features := make(config.Features)
|
||||||
|
stepOn := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
if item.FeatureID == "" {
|
||||||
|
// Simple step: enabled iff its own checkbox is checked
|
||||||
|
stepOn[item.StepID] = selected[item.Key]
|
||||||
|
} else {
|
||||||
|
// Feature checkbox: at least one checked feature enables the step
|
||||||
|
if features[item.StepID] == nil {
|
||||||
|
features[item.StepID] = make(map[string]bool)
|
||||||
|
}
|
||||||
|
features[item.StepID][item.FeatureID] = selected[item.Key]
|
||||||
|
if selected[item.Key] {
|
||||||
|
stepOn[item.StepID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allSteps := runner.AllSteps()
|
||||||
|
steps := make([]runner.Step, len(allSteps))
|
||||||
|
for i, s := range allSteps {
|
||||||
|
s.Enabled = stepOn[s.ID]
|
||||||
|
steps[i] = s
|
||||||
|
}
|
||||||
|
return steps, features
|
||||||
|
}
|
||||||
336
internal/runner/runner.go
Normal file
336
internal/runner/runner.go
Normal file
|
|
@ -0,0 +1,336 @@
|
||||||
|
// Package runner executes PowerShell deployment scripts and streams log output.
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Step describes a single deployment step.
|
||||||
|
type Step struct {
|
||||||
|
ID string // e.g. "adminAccount"
|
||||||
|
Num string // display number e.g. "00"
|
||||||
|
Name string
|
||||||
|
ScriptName string // e.g. "00-admin-account.ps1"
|
||||||
|
Enabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllSteps returns the ordered list of deployment steps.
|
||||||
|
func AllSteps() []Step {
|
||||||
|
return []Step{
|
||||||
|
{ID: "adminAccount", Num: "00", Name: "Admin ucet", ScriptName: "00-admin-account.ps1"},
|
||||||
|
{ID: "bloatware", Num: "01", Name: "Bloatware removal", ScriptName: "01-bloatware.ps1"},
|
||||||
|
{ID: "software", Num: "02", Name: "Software (winget)", ScriptName: "02-software.ps1"},
|
||||||
|
{ID: "systemRegistry", Num: "03", Name: "System Registry (HKLM)", ScriptName: "03-system-registry.ps1"},
|
||||||
|
{ID: "defaultProfile", Num: "04", Name: "Default Profile", ScriptName: "04-default-profile.ps1"},
|
||||||
|
{ID: "personalization", Num: "05", Name: "Personalizace", ScriptName: "05-personalization.ps1"},
|
||||||
|
{ID: "scheduledTasks", Num: "06", Name: "Scheduled Tasks", ScriptName: "06-scheduled-tasks.ps1"},
|
||||||
|
{ID: "backinfo", Num: "07", Name: "BackInfo", ScriptName: "07-backinfo.ps1"},
|
||||||
|
{ID: "activation", Num: "08", Name: "Windows aktivace", ScriptName: "08-activation.ps1"},
|
||||||
|
{ID: "dellUpdate", Num: "11", Name: "Dell Command | Update", ScriptName: "11-dell-update.ps1"},
|
||||||
|
{ID: "network", Num: "09", Name: "Network discovery", ScriptName: "10-network.ps1"},
|
||||||
|
{ID: "pcIdentity", Num: "10", Name: "PC identita", ScriptName: "09-pc-identity.ps1"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feature is a single toggleable sub-item within a deployment step.
|
||||||
|
type Feature struct {
|
||||||
|
ID string
|
||||||
|
Label string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepFeatures returns per-step feature lists. Steps absent from this map
|
||||||
|
// have no sub-features and are controlled at the step level only.
|
||||||
|
func StepFeatures() map[string][]Feature {
|
||||||
|
return map[string][]Feature{
|
||||||
|
"software": {
|
||||||
|
{ID: "wingetInstalls", Label: "Instalace SW ze seznamu (winget)"},
|
||||||
|
{ID: "pdfDefault", Label: "Adobe Reader jako vychozi PDF"},
|
||||||
|
{ID: "ateraAgent", Label: "Atera RMM agent"},
|
||||||
|
},
|
||||||
|
"systemRegistry": {
|
||||||
|
{ID: "systemTweaks", Label: "Windows tweaky (Widgets, GameDVR, Recall...)"},
|
||||||
|
{ID: "edgePolicies", Label: "Edge policies (tlacitka, vyhledavac, telemetrie)"},
|
||||||
|
{ID: "oneDriveUninstall", Label: "OneDrive uninstall (consumer pre-install)"},
|
||||||
|
{ID: "powercfg", Label: "Nastaveni napajeni (timeout AC/DC)"},
|
||||||
|
{ID: "proxyDisable", Label: "Zakaz WPAD proxy auto-detect"},
|
||||||
|
},
|
||||||
|
"defaultProfile": {
|
||||||
|
{ID: "taskbarTweaks", Label: "Taskbar – zarovnani, tlacitka, layout XML"},
|
||||||
|
{ID: "startMenuTweaks", Label: "Start menu – cisteni pinu, Bing, Copilot"},
|
||||||
|
{ID: "explorerTweaks", Label: "Explorer – pripony, LaunchTo, ShowRecent"},
|
||||||
|
},
|
||||||
|
"dellUpdate": {
|
||||||
|
{ID: "drivers", Label: "Dell drivery + firmware"},
|
||||||
|
{ID: "bios", Label: "Dell BIOS update"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectableItem is a single toggleable row in the TUI checklist.
|
||||||
|
// It represents either a whole step (FeatureID == "") or a specific feature.
|
||||||
|
type SelectableItem struct {
|
||||||
|
Key string // "stepID" or "stepID.featureID"
|
||||||
|
StepID string
|
||||||
|
FeatureID string // empty for step-level items
|
||||||
|
Label string
|
||||||
|
Num string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllSelectableItems returns the flat ordered list of all TUI toggle rows.
|
||||||
|
// Steps with features are expanded to individual feature rows.
|
||||||
|
// Steps without features appear as a single step-level row.
|
||||||
|
func AllSelectableItems() []SelectableItem {
|
||||||
|
steps := AllSteps()
|
||||||
|
features := StepFeatures()
|
||||||
|
var items []SelectableItem
|
||||||
|
for _, s := range steps {
|
||||||
|
feats, hasFeatures := features[s.ID]
|
||||||
|
if !hasFeatures {
|
||||||
|
items = append(items, SelectableItem{
|
||||||
|
Key: s.ID,
|
||||||
|
StepID: s.ID,
|
||||||
|
Label: s.Num + " – " + s.Name,
|
||||||
|
Num: s.Num,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for _, f := range feats {
|
||||||
|
items = append(items, SelectableItem{
|
||||||
|
Key: s.ID + "." + f.ID,
|
||||||
|
StepID: s.ID,
|
||||||
|
FeatureID: f.ID,
|
||||||
|
Label: s.Num + " – " + f.Label,
|
||||||
|
Num: s.Num,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunConfig holds runtime parameters passed to each script.
|
||||||
|
type RunConfig struct {
|
||||||
|
ScriptsDir string
|
||||||
|
ConfigPath string
|
||||||
|
LogFile string
|
||||||
|
ProfileType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result is the outcome of a single step.
|
||||||
|
type Result struct {
|
||||||
|
Step Step
|
||||||
|
Status string // "OK", "ERROR", "SKIPPED"
|
||||||
|
Elapsed time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogLine is a single output line from a running script.
|
||||||
|
type LogLine struct {
|
||||||
|
StepID string
|
||||||
|
Text string
|
||||||
|
Level string // INFO, OK, ERROR, WARN, STEP - parsed from [LEVEL] prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runner executes deployment steps sequentially.
|
||||||
|
type Runner struct {
|
||||||
|
cfg RunConfig
|
||||||
|
onLog func(LogLine)
|
||||||
|
onResult func(Result)
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a Runner. onLog is called for each output line, onResult after each step.
|
||||||
|
func New(cfg RunConfig, onLog func(LogLine), onResult func(Result)) *Runner {
|
||||||
|
return &Runner{cfg: cfg, onLog: onLog, onResult: onResult}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes enabled steps sequentially. Blocks until done or context cancelled.
|
||||||
|
func (r *Runner) Run(ctx context.Context, steps []Step) []Result {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
r.cancel = cancel
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Write config JSON to temp file so scripts can read it
|
||||||
|
cfgArg := r.cfg.ConfigPath
|
||||||
|
|
||||||
|
var results []Result
|
||||||
|
for _, step := range steps {
|
||||||
|
if !step.Enabled {
|
||||||
|
res := Result{Step: step, Status: "SKIPPED"}
|
||||||
|
r.onResult(res)
|
||||||
|
results = append(results, res)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
err := r.runScript(ctx, step, cfgArg)
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
|
||||||
|
status := "OK"
|
||||||
|
if err != nil {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
status = "CANCELLED"
|
||||||
|
} else {
|
||||||
|
status = "ERROR"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res := Result{Step: step, Status: status, Elapsed: elapsed}
|
||||||
|
r.onResult(res)
|
||||||
|
results = append(results, res)
|
||||||
|
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop cancels the running deployment.
|
||||||
|
func (r *Runner) Stop() {
|
||||||
|
if r.cancel != nil {
|
||||||
|
r.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error {
|
||||||
|
scriptPath := filepath.Join(r.cfg.ScriptsDir, step.ScriptName)
|
||||||
|
|
||||||
|
// Build argument list
|
||||||
|
args := []string{
|
||||||
|
"-NonInteractive",
|
||||||
|
"-ExecutionPolicy", "Bypass",
|
||||||
|
"-File", scriptPath,
|
||||||
|
"-LogFile", r.cfg.LogFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass config object as JSON string (script reads it inline)
|
||||||
|
if cfgArg != "" {
|
||||||
|
args = append(args, "-Config", fmt.Sprintf("(Get-Content '%s' | ConvertFrom-Json)", cfgArg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfileType for step 04
|
||||||
|
if step.ID == "defaultProfile" && r.cfg.ProfileType != "" {
|
||||||
|
args = append(args, "-ProfileType", r.cfg.ProfileType)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "powershell.exe", args...)
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.Stderr = cmd.Stdout // merge stderr into stdout
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
r.onLog(LogLine{
|
||||||
|
StepID: step.ID,
|
||||||
|
Text: line,
|
||||||
|
Level: parseLevel(line),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseLevel extracts the log level from lines formatted as "[HH:mm:ss] [LEVEL] message".
|
||||||
|
func parseLevel(line string) string {
|
||||||
|
if strings.Contains(line, "] [OK]") {
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "] [ERROR]") {
|
||||||
|
return "ERROR"
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "] [WARN]") {
|
||||||
|
return "WARN"
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "] [STEP]") {
|
||||||
|
return "STEP"
|
||||||
|
}
|
||||||
|
return "INFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractScripts unpacks embedded scripts to a temp directory.
|
||||||
|
// Returns the directory path. Caller is responsible for cleanup.
|
||||||
|
func ExtractScripts(fs interface{ ReadDir(string) ([]os.DirEntry, error); ReadFile(string) ([]byte, error) }, tmpDir string) error {
|
||||||
|
entries, err := fs.ReadDir("scripts")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read embedded scripts: %w", err)
|
||||||
|
}
|
||||||
|
scriptsDir := filepath.Join(tmpDir, "scripts")
|
||||||
|
if err := os.MkdirAll(scriptsDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
if e.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// embed.FS always uses forward slashes regardless of OS
|
||||||
|
data, err := fs.ReadFile(path.Join("scripts", e.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(scriptsDir, e.Name()), data, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractAssets unpacks embedded assets to tmpDir/assets.
|
||||||
|
func ExtractAssets(fs interface{ ReadDir(string) ([]os.DirEntry, error); ReadFile(string) ([]byte, error) }, tmpDir string) error {
|
||||||
|
return extractDir(fs, "assets", tmpDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractDir(fs interface{ ReadDir(string) ([]os.DirEntry, error); ReadFile(string) ([]byte, error) }, src, dstBase string) error {
|
||||||
|
entries, err := fs.ReadDir(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dst := filepath.Join(dstBase, filepath.FromSlash(src))
|
||||||
|
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, e := range entries {
|
||||||
|
// embed.FS always uses forward slashes regardless of OS
|
||||||
|
srcPath := path.Join(src, e.Name())
|
||||||
|
dstPath := filepath.Join(dstBase, filepath.FromSlash(srcPath))
|
||||||
|
if e.IsDir() {
|
||||||
|
if err := extractDir(fs, srcPath, dstBase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, err := fs.ReadFile(srcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(dstPath, data, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteConfig serialises cfg to a temp JSON file and returns its path.
|
||||||
|
func WriteConfig(cfg interface{}, tmpDir string) (string, error) {
|
||||||
|
path := filepath.Join(tmpDir, "config-runtime.json")
|
||||||
|
data, err := json.MarshalIndent(cfg, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return path, os.WriteFile(path, data, 0644)
|
||||||
|
}
|
||||||
834
review.html
Normal file
834
review.html
Normal file
|
|
@ -0,0 +1,834 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cs">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Xetup - Review v2</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #0f1117;
|
||||||
|
--card: #1a1d27;
|
||||||
|
--border: #2a2d3a;
|
||||||
|
--text: #e0e0e0;
|
||||||
|
--muted: #888;
|
||||||
|
--accent: #223B47;
|
||||||
|
--green: #2ea043;
|
||||||
|
--green-bg: rgba(46,160,67,.12);
|
||||||
|
--red: #da3633;
|
||||||
|
--red-bg: rgba(218,54,51,.12);
|
||||||
|
--yellow: #d29922;
|
||||||
|
--yellow-bg: rgba(210,153,34,.12);
|
||||||
|
--blue: #58a6ff;
|
||||||
|
--blue-bg: rgba(88,166,255,.12);
|
||||||
|
--purple: #a371f7;
|
||||||
|
--purple-bg: rgba(163,113,247,.12);
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
h1 { font-size: 1.6rem; margin-bottom: .3rem; color: #fff; }
|
||||||
|
.subtitle { color: var(--muted); margin-bottom: 1.5rem; font-size: .9rem; }
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: flex; gap: 1rem; margin-bottom: 2rem; flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.stat {
|
||||||
|
background: var(--card); border: 1px solid var(--border); border-radius: 8px;
|
||||||
|
padding: .6rem 1rem; font-size: .85rem; min-width: 100px; text-align: center;
|
||||||
|
}
|
||||||
|
.stat .num { font-size: 1.4rem; font-weight: 700; display: block; }
|
||||||
|
.stat.s-pending .num { color: var(--muted); }
|
||||||
|
.stat.s-approved .num { color: var(--green); }
|
||||||
|
.stat.s-rejected .num { color: var(--red); }
|
||||||
|
.stat.s-discuss .num { color: var(--yellow); }
|
||||||
|
|
||||||
|
.group { margin-bottom: 2rem; }
|
||||||
|
.group-header {
|
||||||
|
display: flex; align-items: center; gap: .6rem;
|
||||||
|
margin-bottom: .7rem; padding-bottom: .4rem; border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.group-icon {
|
||||||
|
font-size: 1rem; width: 26px; height: 26px;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
border-radius: 6px; background: rgba(255,255,255,.05); font-weight: 700;
|
||||||
|
}
|
||||||
|
.group-title { font-size: 1.05rem; font-weight: 600; color: #fff; }
|
||||||
|
.group-count { color: var(--muted); font-size: .85rem; }
|
||||||
|
|
||||||
|
.step {
|
||||||
|
background: var(--card); border: 1px solid var(--border); border-radius: 10px;
|
||||||
|
margin-bottom: .6rem; overflow: hidden; transition: border-color .2s;
|
||||||
|
}
|
||||||
|
.step.status-approved { border-left: 3px solid var(--green); }
|
||||||
|
.step.status-rejected { border-left: 3px solid var(--red); }
|
||||||
|
.step.status-discuss { border-left: 3px solid var(--yellow); }
|
||||||
|
|
||||||
|
.step-header {
|
||||||
|
display: flex; align-items: center; padding: .7rem 1rem;
|
||||||
|
cursor: pointer; gap: .6rem; user-select: none;
|
||||||
|
}
|
||||||
|
.step-header:hover { background: rgba(255,255,255,.03); }
|
||||||
|
.step-title { font-weight: 600; flex: 1; font-size: .92rem; }
|
||||||
|
.step-source {
|
||||||
|
font-size: .65rem; padding: .12rem .4rem; border-radius: 3px; white-space: nowrap;
|
||||||
|
}
|
||||||
|
.source-impl { background: var(--blue-bg); color: var(--blue); }
|
||||||
|
.source-spec { background: var(--yellow-bg); color: var(--yellow); }
|
||||||
|
.source-new { background: var(--purple-bg); color: var(--purple); }
|
||||||
|
.source-both { background: var(--green-bg); color: var(--green); }
|
||||||
|
.source-problem { background: var(--red-bg); color: var(--red); }
|
||||||
|
.source-resolved { background: rgba(255,255,255,.06); color: var(--muted); }
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
font-size: .65rem; padding: .1rem .35rem; border-radius: 3px; font-weight: 600; display: none;
|
||||||
|
}
|
||||||
|
.step.status-approved .status-badge { display: inline; background: var(--green-bg); color: var(--green); }
|
||||||
|
.step.status-rejected .status-badge { display: inline; background: var(--red-bg); color: var(--red); }
|
||||||
|
.step.status-discuss .status-badge { display: inline; background: var(--yellow-bg); color: var(--yellow); }
|
||||||
|
|
||||||
|
.chevron {
|
||||||
|
color: var(--muted); transition: transform .2s; font-size: .75rem;
|
||||||
|
}
|
||||||
|
.step.open .chevron { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
.step-body {
|
||||||
|
display: none; padding: 0 1rem 1rem 1rem; font-size: .86rem;
|
||||||
|
}
|
||||||
|
.step.open .step-body { display: block; }
|
||||||
|
.step-body p { margin-bottom: .4rem; }
|
||||||
|
.detail-label {
|
||||||
|
color: var(--muted); font-size: .75rem; text-transform: uppercase;
|
||||||
|
letter-spacing: .05em; margin-top: .7rem; margin-bottom: .25rem;
|
||||||
|
}
|
||||||
|
.step-body ul { margin-left: 1.2rem; margin-bottom: .4rem; }
|
||||||
|
.step-body li { margin-bottom: .2rem; }
|
||||||
|
.step-body code {
|
||||||
|
background: rgba(255,255,255,.08); padding: .08rem .3rem; border-radius: 3px;
|
||||||
|
font-size: .8rem; font-family: 'SF Mono', 'Fira Code', monospace;
|
||||||
|
}
|
||||||
|
.step-body pre {
|
||||||
|
background: rgba(0,0,0,.3); padding: .5rem .7rem; border-radius: 6px;
|
||||||
|
overflow-x: auto; font-size: .78rem; margin: .4rem 0;
|
||||||
|
font-family: 'SF Mono', 'Fira Code', monospace; line-height: 1.4;
|
||||||
|
}
|
||||||
|
.issue {
|
||||||
|
background: var(--red-bg); border: 1px solid rgba(218,54,51,.3);
|
||||||
|
border-radius: 6px; padding: .45rem .65rem; margin: .4rem 0; font-size: .83rem;
|
||||||
|
}
|
||||||
|
.issue strong { color: var(--red); }
|
||||||
|
.note {
|
||||||
|
background: var(--blue-bg); border: 1px solid rgba(88,166,255,.3);
|
||||||
|
border-radius: 6px; padding: .45rem .65rem; margin: .4rem 0; font-size: .83rem;
|
||||||
|
}
|
||||||
|
.note strong { color: var(--blue); }
|
||||||
|
.resolved {
|
||||||
|
background: rgba(255,255,255,.04); border: 1px solid var(--border);
|
||||||
|
border-radius: 6px; padding: .45rem .65rem; margin: .4rem 0; font-size: .83rem;
|
||||||
|
}
|
||||||
|
.resolved strong { color: var(--green); }
|
||||||
|
.prev-comment {
|
||||||
|
background: var(--yellow-bg); border: 1px solid rgba(210,153,34,.3);
|
||||||
|
border-radius: 6px; padding: .45rem .65rem; margin: .4rem 0; font-size: .83rem;
|
||||||
|
}
|
||||||
|
.prev-comment strong { color: var(--yellow); }
|
||||||
|
|
||||||
|
.actions { display: flex; gap: .4rem; margin-top: .7rem; flex-wrap: wrap; }
|
||||||
|
.btn {
|
||||||
|
padding: .3rem .65rem; border: 1px solid var(--border); border-radius: 6px;
|
||||||
|
background: transparent; color: var(--text); cursor: pointer;
|
||||||
|
font-size: .78rem; transition: all .15s;
|
||||||
|
}
|
||||||
|
.btn:hover { background: rgba(255,255,255,.06); }
|
||||||
|
.btn-approve { border-color: var(--green); color: var(--green); }
|
||||||
|
.btn-approve:hover, .btn-approve.active { background: var(--green-bg); }
|
||||||
|
.btn-reject { border-color: var(--red); color: var(--red); }
|
||||||
|
.btn-reject:hover, .btn-reject.active { background: var(--red-bg); }
|
||||||
|
.btn-discuss { border-color: var(--yellow); color: var(--yellow); }
|
||||||
|
.btn-discuss:hover, .btn-discuss.active { background: var(--yellow-bg); }
|
||||||
|
|
||||||
|
.comment-area { margin-top: .5rem; display: none; }
|
||||||
|
.comment-area.visible { display: block; }
|
||||||
|
.comment-area textarea {
|
||||||
|
width: 100%; background: rgba(0,0,0,.3); border: 1px solid var(--border);
|
||||||
|
border-radius: 6px; color: var(--text); padding: .45rem; font-size: .83rem;
|
||||||
|
font-family: inherit; resize: vertical; min-height: 55px;
|
||||||
|
}
|
||||||
|
.comment-area textarea:focus { outline: none; border-color: var(--blue); }
|
||||||
|
|
||||||
|
.export-bar {
|
||||||
|
position: sticky; bottom: 0; background: var(--card);
|
||||||
|
border: 1px solid var(--border); border-radius: 10px;
|
||||||
|
padding: .7rem 1rem; margin-top: 1.5rem;
|
||||||
|
display: flex; align-items: center; gap: .8rem;
|
||||||
|
justify-content: space-between; flex-wrap: wrap; z-index: 10;
|
||||||
|
}
|
||||||
|
.btn-export {
|
||||||
|
padding: .45rem 1rem; background: var(--accent); color: #fff; border: none;
|
||||||
|
border-radius: 6px; cursor: pointer; font-size: .85rem; font-weight: 600;
|
||||||
|
}
|
||||||
|
.btn-export:hover { opacity: .85; }
|
||||||
|
.btn-minor {
|
||||||
|
padding: .35rem .7rem; background: transparent; border: 1px solid var(--border);
|
||||||
|
color: var(--muted); border-radius: 6px; cursor: pointer; font-size: .78rem;
|
||||||
|
}
|
||||||
|
.btn-minor:hover { color: var(--text); border-color: var(--text); }
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body { padding: 1rem; }
|
||||||
|
.stats { gap: .5rem; }
|
||||||
|
.stat { min-width: 70px; padding: .4rem .5rem; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Xetup - Windows Deployment Review v2</h1>
|
||||||
|
<p class="subtitle">Vcetne novinek od kolegy + zapracovane komentare z review v1. Schval / zamitni / okomentuj.</p>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat s-pending"><span class="num" id="cnt-pending">0</span>Ceka</div>
|
||||||
|
<div class="stat s-approved"><span class="num" id="cnt-approved">0</span>OK</div>
|
||||||
|
<div class="stat s-rejected"><span class="num" id="cnt-rejected">0</span>Ne</div>
|
||||||
|
<div class="stat s-discuss"><span class="num" id="cnt-discuss">0</span>Diskuse</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<div class="export-bar">
|
||||||
|
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
|
||||||
|
<button class="btn-minor" onclick="toggleAll()">Rozbalit/sbalit vse</button>
|
||||||
|
<button class="btn-minor" onclick="expandDetails()">Otevrit detaily</button>
|
||||||
|
<button class="btn-minor" onclick="clearAll()">Reset vsech hlasu</button>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
|
||||||
|
<button class="btn-export" onclick="copyToClipboard()">Kopirovat Markdown</button>
|
||||||
|
<button class="btn-export" onclick="exportMarkdown()" style="background:#333;border:1px solid var(--border)">Stahnout .md</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const groups = [
|
||||||
|
{
|
||||||
|
id: "prep",
|
||||||
|
icon: "1",
|
||||||
|
title: "Priprava prostredi",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-dirs",
|
||||||
|
title: "Adresar C:\\X9 + ikona slozky",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "Vytvoreni C:\\X9, kopirovani assets (BackInfo, Logo, ikona). Vlastni ikona slozky pres desktop.ini.",
|
||||||
|
detail: `<p class="detail-label">Struktura</p>
|
||||||
|
<pre>C:\\X9\\
|
||||||
|
+-- install\\ (staging pro Atera MSI atd.)
|
||||||
|
+-- X9-ikona.ico (ikona slozky)
|
||||||
|
+-- X9-logo.jpg (avatar uctu)</pre>
|
||||||
|
<p class="detail-label">Ikona slozky</p>
|
||||||
|
<pre>[.ShellClassInfo]
|
||||||
|
IconResource=C:\\X9\\X9-ikona.ico,0
|
||||||
|
|
||||||
|
attrib +s +h "C:\\X9\\desktop.ini"
|
||||||
|
attrib +s "C:\\X9"</pre>
|
||||||
|
<div class="resolved"><strong>Z review v1:</strong> Nextcloud neni potreba - Backinfo mame v assets/ repa, logo taky. Stahovat se bude jen Atera MSI (viz krok 3b).</div>
|
||||||
|
<div class="note"><strong>Pozn:</strong> Pripnuti slozky na taskbar Win11 nepodporuje. Shortcut na plose nebo Quick Access.</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-admin",
|
||||||
|
title: "Admin ucet (adminx9)",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "Vytvoreni skryteho admin uctu adminx9 BEZ HESLA. FullName = \"X9.cz s.r.o.\"",
|
||||||
|
detail: `<p class="detail-label">Co delame</p>
|
||||||
|
<ul>
|
||||||
|
<li>Vytvoreni <code>adminx9</code></li>
|
||||||
|
<li><strong>Bez hesla</strong> (zmena oproti v1, kde bylo heslo z config.json)</li>
|
||||||
|
<li>Pridani do Administrators</li>
|
||||||
|
<li>Heslo nevyprsi, uzivatel nesmi menit</li>
|
||||||
|
<li>Skryti z login screen (SpecialAccounts\\UserList = 0)</li>
|
||||||
|
</ul>
|
||||||
|
<div class="prev-comment"><strong>Review v1:</strong> "adminX9 bude bez hesla"</div>
|
||||||
|
<p class="detail-label">NOVINKA: Popis uctu</p>
|
||||||
|
<pre>\$user = [ADSI]"WinNT://\$env:COMPUTERNAME/adminx9,user"
|
||||||
|
\$user.FullName = "X9.cz s.r.o."
|
||||||
|
\$user.SetInfo()</pre>
|
||||||
|
<p>Nastavi pole "Jmeno a prijmeni" na <code>X9.cz s.r.o.</code> - viditelne ve Sprave pocitace.</p>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/00-admin-account.ps1</code> (upravit: odstranit heslo, pridat FullName)</p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-activation",
|
||||||
|
title: "Aktivace Windows",
|
||||||
|
source: "impl", sourceLabel: "Nase implementace",
|
||||||
|
summary: "Aktivace Windows pomoci klice z configu nebo GVLK (KMS). Spec kolegy toto nezminuje.",
|
||||||
|
detail: `<p class="detail-label">Co delame</p>
|
||||||
|
<ul>
|
||||||
|
<li>Kontrola stavu aktivace</li>
|
||||||
|
<li>Pouziti klice z <code>config.json</code> nebo GVLK fallback</li>
|
||||||
|
<li>Volitelne nastaveni KMS serveru</li>
|
||||||
|
<li><code>slmgr.vbs /ipk</code> + <code>/ato</code></li>
|
||||||
|
</ul>
|
||||||
|
<div class="note"><strong>Pozn:</strong> Spec kolegy aktivaci vubec neresi. Ponechat? Nebo vyradit a resit rucne?</div>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/08-activation.ps1</code></p>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "debloat",
|
||||||
|
icon: "2",
|
||||||
|
title: "Odstranovani bloatware",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-appx",
|
||||||
|
title: "AppX balicky (UWP aplikace)",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "Odebirame ~65 preinstalovanych aplikaci. Kalkulacka zustava.",
|
||||||
|
detail: `<p class="detail-label">Hlavni balicky</p>
|
||||||
|
<pre>BingSearch, Camera, Clipchamp, Alarms, Copilot, Cortana,
|
||||||
|
DevHome, Family, FeedbackHub, GetHelp, Getstarted, Mail,
|
||||||
|
Maps, BingNews, OfficeHub, OneNote, Outlook, Paint, People,
|
||||||
|
Photos, PowerAutomate, QuickAssist, Skype, ScreenSketch,
|
||||||
|
Solitaire, StickyNotes, Teams, Todos, SoundRecorder, Wallet,
|
||||||
|
BingWeather, Terminal, Xbox (6x), GamingApp, YourPhone,
|
||||||
|
ZuneMusic, ZuneVideo, LinkedIn</pre>
|
||||||
|
<p class="detail-label">Zachovavame</p>
|
||||||
|
<ul><li><code>Microsoft.WindowsCalculator</code></li></ul>
|
||||||
|
<div class="resolved"><strong>Z review v1:</strong> "Flash2 je v podstate tento setup, minula verze - hlavne pro inspiraci a zahrnuti." = Flash2 neintegrujeme, nas seznam je kompletnejsi.</div>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/01-bloatware.ps1</code></p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-caps",
|
||||||
|
title: "Windows Capabilities",
|
||||||
|
source: "impl", sourceLabel: "Implementovano",
|
||||||
|
summary: "Odebirame: Fax, IE, WordPad, PowerShell ISE, Steps Recorder, WMP, Handwriting...",
|
||||||
|
detail: `<pre>Print.Fax.Scan, Language.Handwriting, Browser.InternetExplorer,
|
||||||
|
MathRecognizer, OneCoreUAP.OneSync, OpenSSH.Client,
|
||||||
|
Microsoft.Windows.MSPaint, PowerShell.ISE,
|
||||||
|
QuickAssist, SnippingTool, StepsRecorder,
|
||||||
|
Hello.Face.*, WindowsMediaPlayer, WordPad</pre>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-features",
|
||||||
|
title: "Optional Features",
|
||||||
|
source: "impl", sourceLabel: "Implementovano",
|
||||||
|
summary: "Vypnuti: MediaPlayback, PowerShell 2.0, Recall (AI), SnippingTool. RDP se NEODEBIRA.",
|
||||||
|
detail: `<pre>MediaPlayback, MicrosoftWindowsPowerShellV2Root,
|
||||||
|
Recall (Windows AI), Microsoft-SnippingTool</pre>
|
||||||
|
<div class="resolved"><strong>Vyreseno:</strong> RDP klient (<code>Microsoft-RemoteDesktopConnection</code>) se NEODEBIRA. Puvodne byl ve SPEC, kolega potvrdil ze musi zustat.</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-onedrive",
|
||||||
|
title: "OneDrive - oprava",
|
||||||
|
source: "problem", sourceLabel: "OPRAVIT",
|
||||||
|
summary: "Nas skript agresivne maze OneDrive. Nutno opravit - nechat OneDrive instalovatelny.",
|
||||||
|
detail: `<p class="detail-label">Co aktualne spatne delame</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>OneDriveSetup.exe /uninstall</code> + mazani exe</li>
|
||||||
|
<li>Mazani Start menu shortcutu</li>
|
||||||
|
<li>Mazani RunOnce klicu a Explorer namespace z Default Profile</li>
|
||||||
|
</ul>
|
||||||
|
<div class="prev-comment"><strong>Review v1:</strong> "nevim jestli v posledni verzi uz to bylo opravene, ale v prvni verzi se onedrive zabijel nejakym regeditem nebo scheduled taskem"</div>
|
||||||
|
<p class="detail-label">Stav v kodu</p>
|
||||||
|
<p>V aktualnim <code>03-system-registry.ps1</code> (radky 244-273) + <code>04-default-profile.ps1</code> (radky 240-261) je OneDrive stale agresivne mazany.</p>
|
||||||
|
<p class="detail-label">Reseni</p>
|
||||||
|
<p>Odstranit cely OneDrive blok z obou skriptu. OneDrive musi zustat instalovatelny pro M365.</p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-rdp",
|
||||||
|
title: "RDP/RDS - overeni",
|
||||||
|
source: "problem", sourceLabel: "OVERIT",
|
||||||
|
summary: "RDP se v kodu neodebira (neni v seznamu). Overit, ze zadny registry tweak neblokuje RDS.",
|
||||||
|
detail: `<div class="prev-comment"><strong>Review v1:</strong> "nevim jestli v posledni verzi uz to bylo opravene, ale v prvni verzi se rdp problematizovalo nejakym regeditem nebo scheduled taskem"</div>
|
||||||
|
<p class="detail-label">Stav</p>
|
||||||
|
<p>V aktualnim kodu <code>01-bloatware.ps1</code> RDP NENI v seznamu k odebirani. Ale je mozne, ze nektery registry tweak v <code>03-system-registry.ps1</code> neprimo ovlivnuje RDS.</p>
|
||||||
|
<p class="detail-label">TODO</p>
|
||||||
|
<p>Prozkoumat vsechny HKLM zmeny a overit, ze zadna neblokuje Remote Desktop Services. Otestovat na VM.</p>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "software",
|
||||||
|
icon: "3",
|
||||||
|
title: "Instalace software",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-winget",
|
||||||
|
title: "Winget balicky (7-Zip, Adobe, OpenVPN)",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "Silent instalace 7-Zip, Adobe Acrobat Reader, OpenVPN Connect pres winget.",
|
||||||
|
detail: `<table style="width:100%;font-size:.83rem;border-collapse:collapse;">
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem .4rem"><code>7zip.7zip</code></td><td>7-Zip</td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem .4rem"><code>Adobe.Acrobat.Reader.64-bit</code></td><td>Adobe Reader</td></tr>
|
||||||
|
<tr><td style="padding:.25rem .4rem"><code>OpenVPNTechnologies.OpenVPNConnect</code></td><td>OpenVPN Connect</td></tr>
|
||||||
|
</table>
|
||||||
|
<p class="detail-label">Adobe jako vychozi PDF</p>
|
||||||
|
<ul>
|
||||||
|
<li>Po instalaci: <code>.pdf -> AcroRd32</code> pres HKCR</li>
|
||||||
|
<li>Scheduled task <code>PDF-DefaultApp</code> obnovi asociaci pri kazdem prihlaseni</li>
|
||||||
|
</ul>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/02-software.ps1</code></p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-atera",
|
||||||
|
title: "Atera Agent",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "Stahnout MSI z Atera API a provest silent install. MFA problem k reseni.",
|
||||||
|
detail: `<div class="prev-comment"><strong>Review v1 - kolega dodal presny postup:</strong></div>
|
||||||
|
<pre>curl -L -o setup.msi "https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337"
|
||||||
|
msiexec /i setup.msi /qn</pre>
|
||||||
|
<p class="detail-label">PowerShell ekvivalent</p>
|
||||||
|
<pre>\$ateraUrl = "https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337"
|
||||||
|
\$ateraMsi = "C:\\X9\\install\\atera-setup.msi"
|
||||||
|
Invoke-WebRequest -Uri \$ateraUrl -OutFile \$ateraMsi
|
||||||
|
Start-Process msiexec -ArgumentList "/i \$ateraMsi /qn" -Wait</pre>
|
||||||
|
<div class="note"><strong>Otevrena otazka:</strong> Kolega rika "nejlip najit parametr, u ktereho ATERA nebude chtit MFA kod z mailu". Overit, jestli URL s aeid parametrem to obchazi.</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-backinfo",
|
||||||
|
title: "BackInfo (info na plose)",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "BackInfo.exe - hostname, user, OS, HW, sit uprostred plochy. Mame v assets/, jen zkopirovat a spustit.",
|
||||||
|
detail: `<p class="detail-label">Instalace</p>
|
||||||
|
<ol>
|
||||||
|
<li>Zkopirovat <code>assets/Backinfo/</code> do <code>C:\\Program Files\\Backinfo\\</code></li>
|
||||||
|
<li>Spustit <code>backinfo_W11.ps1</code>:
|
||||||
|
<ul>
|
||||||
|
<li>Detekuje Win10/Win11 + edici</li>
|
||||||
|
<li>Zapise <code>HKLM:\\SOFTWARE\\BackInfo\\OSName</code></li>
|
||||||
|
<li>Vytvori shortcut do Startup (<code>StartUp\\BackInfo.lnk</code>)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>BackInfo.exe se pak spousti automaticky po kazdem prihlaseni</li>
|
||||||
|
</ol>
|
||||||
|
<p class="detail-label">INI</p>
|
||||||
|
<pre>AutoBackground=1, ForceDesktopCenter=1
|
||||||
|
Line1: CompName (42pt, bold, white, center)
|
||||||
|
Line2: UserName (20pt, gray, center)
|
||||||
|
Line3: OS z registru (20pt, bold, gray)
|
||||||
|
Line4: HW SysInfo (20pt, gray)
|
||||||
|
Line5: Network NetInfo (20pt, gray)</pre>
|
||||||
|
<div class="resolved"><strong>Vyreseno:</strong> BackInfo uz je v <code>assets/Backinfo/</code>. Nahrazuje nas custom 07-desktop-info.ps1 (smazat).</div>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "appearance",
|
||||||
|
icon: "4",
|
||||||
|
title: "Vzhled a personalizace",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-theme",
|
||||||
|
title: "Barvy a motiv",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "Tmavy system, svetle aplikace, accent #223B47, plna barva pozadi.",
|
||||||
|
detail: `<table style="width:100%;font-size:.83rem;border-collapse:collapse;">
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Rezim Windows</td><td><strong>Tmavy</strong></td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Rezim aplikaci</td><td><strong>Svetly</strong></td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Accent barva</td><td><strong>#223B47</strong> <span style="display:inline-block;width:13px;height:13px;background:#223B47;border-radius:3px;vertical-align:middle;border:1px solid #555"></span></td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Accent v Start/taskbar</td><td>Ano</td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Accent v zahlavi oken</td><td>Ano</td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Pruhlednost</td><td>Vypnuta</td></tr>
|
||||||
|
<tr><td style="padding:.25rem">Pozadi</td><td><strong>Plna barva #223B47</strong> (BackInfo prepise BMP)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/05-personalization.ps1</code></p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-desktop",
|
||||||
|
title: "Ikona Tento pocitac na plose",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "Zobrazit ikonu Tento pocitac na plose (Default Profile + HKCU).",
|
||||||
|
detail: `<pre>HKCU\\...\\HideDesktopIcons\\NewStartPanel
|
||||||
|
{20D04FE0-3AEA-1069-A2D8-08002B30309D} = 0</pre>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-avatar",
|
||||||
|
title: "Avatar uctu (X9 logo)",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "X9-logo.jpg jako profilovy obrazek admin uctu. Logo mame v assets/Logo/.",
|
||||||
|
detail: `<pre>\$accountPicPath = "\$env:APPDATA\\Microsoft\\Windows\\AccountPictures"
|
||||||
|
Copy-Item "C:\\X9\\X9-logo.jpg" "\$accountPicPath\\X9-logo.jpg"</pre>
|
||||||
|
<div class="resolved"><strong>Vyreseno:</strong> Logo (ico + jpeg) presunuto do <code>assets/Logo/</code>. Plati jen pro admin ucet.</div>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "taskbar",
|
||||||
|
icon: "5",
|
||||||
|
title: "Hlavni panel a Start menu",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-tb-layout",
|
||||||
|
title: "Taskbar - zarovnani, skryti prvku",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "Skryti Search, Task View, Widgets, Chat, Copilot. Zarovnani dle parametru -ProfileType.",
|
||||||
|
detail: `<p class="detail-label">Skryte prvky</p>
|
||||||
|
<ul>
|
||||||
|
<li>Search box (SearchboxTaskbarMode=0)</li>
|
||||||
|
<li>Task View (ShowTaskViewButton=0)</li>
|
||||||
|
<li>Widgets (TaskbarDa=0)</li>
|
||||||
|
<li>Chat/Teams (TaskbarMn=0)</li>
|
||||||
|
<li>Copilot (ShowCopilotButton=0)</li>
|
||||||
|
</ul>
|
||||||
|
<p class="detail-label">Zarovnani</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>-ProfileType user</code> = vlevo (TaskbarAl=0)</li>
|
||||||
|
<li><code>-ProfileType admin</code> = na stred (TaskbarAl=1)</li>
|
||||||
|
</ul>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/04-default-profile.ps1</code></p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-tb-pins",
|
||||||
|
title: "NOVINKA: Taskbar pinnovane aplikace (admin vs user)",
|
||||||
|
source: "new", sourceLabel: "NOVINKA v2",
|
||||||
|
summary: "Ruzne sady pripnutych aplikaci podle -ProfileType. Admin: Settings, ComputerMgmt, Services, PS, Explorer, Edge. User: Explorer, Edge.",
|
||||||
|
detail: `<p class="detail-label">Admin profil - pripnout</p>
|
||||||
|
<ul>
|
||||||
|
<li>Nastaveni (<code>ms-settings:</code>)</li>
|
||||||
|
<li>Sprava pocitace (<code>compmgmt.msc</code>)</li>
|
||||||
|
<li>Sluzby (<code>services.msc</code>)</li>
|
||||||
|
<li>PowerShell</li>
|
||||||
|
<li>Pruzkumnik Windows</li>
|
||||||
|
<li>MS Edge</li>
|
||||||
|
</ul>
|
||||||
|
<p class="detail-label">User profil - pripnout</p>
|
||||||
|
<ul>
|
||||||
|
<li>Pruzkumnik Windows</li>
|
||||||
|
<li>MS Edge</li>
|
||||||
|
</ul>
|
||||||
|
<p class="detail-label">Implementace</p>
|
||||||
|
<p>XML layout policy (<code>LayoutXMLPath</code>). Pro <code>compmgmt.msc</code> a <code>services.msc</code> nutno vytvorit .lnk soubory, XML prijima jen .lnk cesty.</p>
|
||||||
|
<pre><CustomTaskbarLayoutCollection PinListPlacement="Replace">
|
||||||
|
<taskbar:TaskbarPinList>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="...\\PowerShell.lnk"/>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="...\\File Explorer.lnk"/>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="...\\Microsoft Edge.lnk"/>
|
||||||
|
</taskbar:TaskbarPinList>
|
||||||
|
</CustomTaskbarLayoutCollection></pre>
|
||||||
|
<div class="note"><strong>Pozn:</strong> <code>LayoutXMLPath</code> policy je dostupna ve Win11 22H2+. Nutno overit verzi pred nasazenim.</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-tray",
|
||||||
|
title: "System tray - zobrazit vsechny ikony",
|
||||||
|
source: "both", sourceLabel: "Implementovano",
|
||||||
|
summary: "EnableAutoTray=0 + mazani icon cache + scheduled task ShowAllTrayIcons.",
|
||||||
|
detail: `<ul>
|
||||||
|
<li><code>EnableAutoTray = 0</code> (Win10)</li>
|
||||||
|
<li>Mazani TrayNotify icon streams (Win11 workaround)</li>
|
||||||
|
<li>Scheduled task <code>ShowAllTrayIcons</code> pri kazdem prihlaseni</li>
|
||||||
|
</ul>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-start",
|
||||||
|
title: "Start menu - prazdne piny, bez Bing",
|
||||||
|
source: "impl", sourceLabel: "Implementovano",
|
||||||
|
summary: "Prazdny LayoutModification.xml, prazdne Start pins, vypnuty Bing suggestions.",
|
||||||
|
detail: `<ul>
|
||||||
|
<li><code>ConfigureStartPins = {"pinnedList":[]}</code></li>
|
||||||
|
<li><code>DisableSearchBoxSuggestions = 1</code></li>
|
||||||
|
<li>Scheduled task <code>UnlockStartLayout</code> po 5 min odemkne pro uzivatele</li>
|
||||||
|
</ul>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "explorer",
|
||||||
|
icon: "6",
|
||||||
|
title: "Pruzkumnik Windows",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-explorer-base",
|
||||||
|
title: "Zakladni nastaveni (implementovano)",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "Otevirat do Tento pocitac, zobrazit pripony souboru.",
|
||||||
|
detail: `<pre>LaunchTo = 1 (Tento pocitac misto Rychly pristup)
|
||||||
|
HideFileExt = 0 (zobrazit pripony)</pre>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/04-default-profile.ps1</code></p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-explorer-new",
|
||||||
|
title: "NOVINKA: Dalsi nastaveni Pruzkumniku",
|
||||||
|
source: "new", sourceLabel: "NOVINKA v2",
|
||||||
|
summary: "Vypnout nedavne soubory/slozky, zobrazit uplnou cestu v zahlavi.",
|
||||||
|
detail: `<p class="detail-label">Nova nastaveni</p>
|
||||||
|
<table style="width:100%;font-size:.83rem;border-collapse:collapse;">
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Nedavne soubory</td><td><strong>Vypnuto</strong> (ShowRecent=0)</td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Caste slozky</td><td><strong>Vypnuto</strong> (ShowFrequent=0)</td></tr>
|
||||||
|
<tr><td style="padding:.25rem">Uplna cesta v zahlavi</td><td><strong>Zapnuto</strong> (FullPath=1)</td></tr>
|
||||||
|
</table>
|
||||||
|
<p class="detail-label">Registry</p>
|
||||||
|
<pre>HKCU\\...\\Explorer
|
||||||
|
ShowRecent = 0 (DWORD)
|
||||||
|
ShowFrequent = 0 (DWORD)
|
||||||
|
|
||||||
|
HKCU\\...\\Explorer\\CabinetState
|
||||||
|
FullPath = 1 (DWORD)</pre>
|
||||||
|
<p>Aplikovat do Default Profile hive + HKCU.</p>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "system",
|
||||||
|
icon: "7",
|
||||||
|
title: "Systemova nastaveni",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-registry",
|
||||||
|
title: "HKLM registry tweaky",
|
||||||
|
source: "both", sourceLabel: "Implementovano + Spec",
|
||||||
|
summary: "BypassNRO, vypnuti Teams/Widgets/Copilot/GameDVR/Recall, hesla bez expirace, casova zona.",
|
||||||
|
detail: `<ul>
|
||||||
|
<li>Bypass NRO (OOBE sit)</li>
|
||||||
|
<li>Vypnuti auto-instalace Teams</li>
|
||||||
|
<li>Vypnuti Cloud Optimized Content</li>
|
||||||
|
<li>Vypnuti Widgets</li>
|
||||||
|
<li>Edge: HideFirstRunExperience, zadny desktop shortcut</li>
|
||||||
|
<li>Hesla bez expirace</li>
|
||||||
|
<li>Casova zona: Central Europe Standard Time</li>
|
||||||
|
<li>Vypnuti GameDVR + Recall</li>
|
||||||
|
<li>Skryti Search (HKLM policy)</li>
|
||||||
|
<li>Skryti Recommended v Start menu</li>
|
||||||
|
<li>Vypnuti auto-instalace Outlooku</li>
|
||||||
|
</ul>
|
||||||
|
<p class="detail-label">Skript</p>
|
||||||
|
<p><code>scripts/03-system-registry.ps1</code></p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-defprofile",
|
||||||
|
title: "Default Profile (NTUSER.DAT)",
|
||||||
|
source: "both", sourceLabel: "Implementovano",
|
||||||
|
summary: "Nastaveni pro budouci uzivatele: Explorer, Num Lock, GameDVR, Copilot...",
|
||||||
|
detail: `<pre>reg load "HKU\\DefaultProfile" "C:\\Users\\Default\\NTUSER.DAT"
|
||||||
|
... zmeny ...
|
||||||
|
reg unload "HKU\\DefaultProfile"</pre>
|
||||||
|
<ul>
|
||||||
|
<li>Explorer: pripony, Tento pocitac, ShowRecent/Frequent off, FullPath</li>
|
||||||
|
<li>Num Lock zapnut</li>
|
||||||
|
<li>Copilot vypnut</li>
|
||||||
|
<li>GameDVR vypnut</li>
|
||||||
|
<li>Start menu: prazdne piny, bez Bing</li>
|
||||||
|
</ul>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-power",
|
||||||
|
title: "Napajeni (powercfg)",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "Spanek nikdy na siti, obrazovka 60min/15min, spanek baterie 60min.",
|
||||||
|
detail: `<pre>powercfg /change standby-timeout-ac 0 # spanek sit: nikdy
|
||||||
|
powercfg /change monitor-timeout-ac 60 # obrazovka sit: 60 min
|
||||||
|
powercfg /change monitor-timeout-dc 15 # obrazovka bat: 15 min
|
||||||
|
powercfg /change standby-timeout-dc 60 # spanek bat: 60 min</pre>
|
||||||
|
<div class="note"><strong>Pozn:</strong> Zavreni vika - nechat vychozi.</div>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-tasks",
|
||||||
|
title: "Scheduled tasks",
|
||||||
|
source: "both", sourceLabel: "Implementovano",
|
||||||
|
summary: "ShowAllTrayIcons, PDF-DefaultApp, UnlockStartLayout.",
|
||||||
|
detail: `<table style="width:100%;font-size:.83rem;border-collapse:collapse;">
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem"><strong>ShowAllTrayIcons</strong></td><td>Logon: systray ikony</td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem"><strong>PDF-DefaultApp</strong></td><td>Logon: .pdf -> Adobe (SYSTEM)</td></tr>
|
||||||
|
<tr><td style="padding:.25rem"><strong>UnlockStartLayout</strong></td><td>5 min po startu, pak se smaze</td></tr>
|
||||||
|
</table>
|
||||||
|
<p class="detail-label">Skripty</p>
|
||||||
|
<p><code>C:\\Windows\\Setup\\Scripts\\</code></p>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "network",
|
||||||
|
icon: "8",
|
||||||
|
title: "Sit a Edge",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-proxy",
|
||||||
|
title: "Proxy - vypnout auto-detect",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "Vypnuti automatickeho zjistovani proxy serveru.",
|
||||||
|
detail: `<pre>HKCU\\...\\Internet Settings\\AutoDetect = 0
|
||||||
|
HKLM\\...\\Internet Settings\\AutoDetect = 0</pre>
|
||||||
|
<p>Aplikovat do Default Profile i HKCU.</p>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-edge",
|
||||||
|
title: "MS Edge - rozsirene nastaveni",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "Striktni tracking protection, Google vyhledavac, panel oblibenych.",
|
||||||
|
detail: `<table style="width:100%;font-size:.83rem;border-collapse:collapse;">
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Tracking</td><td><strong>Striktni</strong> (TrackingPrevention=3)</td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Oblibene</td><td><strong>Vzdy videt</strong> (FavoritesBarEnabled=1)</td></tr>
|
||||||
|
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.25rem">Vyhledavac</td><td><strong>Google</strong></td></tr>
|
||||||
|
<tr><td style="padding:.25rem">Toolbar</td><td>Historie, Aplikace, Stazene, Vykon</td></tr>
|
||||||
|
</table>
|
||||||
|
<pre>HKLM\\SOFTWARE\\Policies\\Microsoft\\Edge
|
||||||
|
TrackingPrevention = 3
|
||||||
|
FavoritesBarEnabled = 1
|
||||||
|
DefaultSearchProviderEnabled = 1
|
||||||
|
DefaultSearchProviderName = "Google"
|
||||||
|
DefaultSearchProviderSearchURL = "https://www.google.com/search?q={searchTerms}"</pre>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-network",
|
||||||
|
title: "NOVINKA: Sitove zjistovani + privatni sit",
|
||||||
|
source: "new", sourceLabel: "NOVINKA v2",
|
||||||
|
summary: "Zapnout Network Discovery, File Sharing, prepnout sit na Private (aby fungoval ping).",
|
||||||
|
detail: `<pre># Zapnout Network Discovery a File Sharing
|
||||||
|
netsh advfirewall firewall set rule group="Network Discovery" new enable=Yes
|
||||||
|
netsh advfirewall firewall set rule group="File and Printer Sharing" new enable=Yes
|
||||||
|
|
||||||
|
# Prepnout sitovy profil na Private
|
||||||
|
\$adapter = Get-NetConnectionProfile | Select-Object -First 1
|
||||||
|
Set-NetConnectionProfile -InterfaceIndex \$adapter.InterfaceIndex -NetworkCategory Private</pre>
|
||||||
|
<div class="note"><strong>Dulezite:</strong> Vaze se na aktualni sitovy adapter - nelze ulozit do Default Profile. Spustit az po pripojeni k siti. Pokud domain-join, az po restartu.</div>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "finish",
|
||||||
|
icon: "9",
|
||||||
|
title: "Finalizace",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
id: "v2-rename",
|
||||||
|
title: "Prejmenování PC",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "Rename-Computer na nazev z parametru -ComputerName. Posledni krok pred restartem.",
|
||||||
|
detail: `<pre>Rename-Computer -NewName $ComputerName -Force -Restart</pre>
|
||||||
|
<ul>
|
||||||
|
<li>Restart je nutny</li>
|
||||||
|
<li>Pripojeni do domeny = volitelne, nezavisle</li>
|
||||||
|
</ul>`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "v2-bootstrap",
|
||||||
|
title: "Bootstrap spoustec (irm | iex)",
|
||||||
|
source: "spec", sourceLabel: "Spec kolegy",
|
||||||
|
summary: "Jednoradkovy spoustec: irm https://xetup.x9.cz/setup.ps1 | iex",
|
||||||
|
detail: `<pre>irm https://xetup.x9.cz/setup.ps1 | iex</pre>
|
||||||
|
<p class="detail-label">Parametry</p>
|
||||||
|
<pre>-ComputerName (povinny)
|
||||||
|
-Domain (volitelny)
|
||||||
|
-ProfileType "admin"|"user" (default "user")
|
||||||
|
ridi: taskbar zarovnani + pinnovane apps</pre>
|
||||||
|
<div class="note"><strong>Novy parametr -ProfileType</strong> (z novinek v2): nahrazuje puvodni -TaskbarAlign. Ridi zarovnani taskbaru I sadu pinnovanych aplikaci.</div>`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// --- Rendering ---
|
||||||
|
const STORAGE_PREFIX = 'xr2-';
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const app = document.getElementById('app');
|
||||||
|
app.innerHTML = '';
|
||||||
|
groups.forEach(g => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'group';
|
||||||
|
div.innerHTML = `
|
||||||
|
<div class="group-header">
|
||||||
|
<div class="group-icon">${g.icon}</div>
|
||||||
|
<span class="group-title">${g.title}</span>
|
||||||
|
<span class="group-count">(${g.steps.length})</span>
|
||||||
|
</div>`;
|
||||||
|
g.steps.forEach(s => {
|
||||||
|
const saved = load(s.id);
|
||||||
|
const sc = saved.status ? `status-${saved.status}` : '';
|
||||||
|
const badge = {approved:'OK',rejected:'NE',discuss:'?'}[saved.status]||'';
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.className = `step ${sc}`;
|
||||||
|
el.dataset.id = s.id;
|
||||||
|
el.innerHTML = `
|
||||||
|
<div class="step-header" onclick="toggle(this)">
|
||||||
|
<span class="step-title">${s.title}</span>
|
||||||
|
<span class="status-badge">${badge}</span>
|
||||||
|
<span class="step-source source-${s.source}">${s.sourceLabel}</span>
|
||||||
|
<span class="chevron">▶</span>
|
||||||
|
</div>
|
||||||
|
<div class="step-body">
|
||||||
|
<p>${s.summary}</p>
|
||||||
|
<details style="margin-top:.5rem">
|
||||||
|
<summary style="cursor:pointer;color:var(--blue);font-size:.83rem;user-select:none">Zobrazit detaily</summary>
|
||||||
|
<div style="margin-top:.4rem">${s.detail}</div>
|
||||||
|
</details>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-approve ${saved.status==='approved'?'active':''}" onclick="setStatus('${s.id}','approved',this)">Schvalit</button>
|
||||||
|
<button class="btn btn-reject ${saved.status==='rejected'?'active':''}" onclick="setStatus('${s.id}','rejected',this)">Zamitnout</button>
|
||||||
|
<button class="btn btn-discuss ${saved.status==='discuss'?'active':''}" onclick="setStatus('${s.id}','discuss',this)">K diskusi</button>
|
||||||
|
</div>
|
||||||
|
<div class="comment-area ${saved.comment?'visible':''}">
|
||||||
|
<textarea placeholder="Poznamka / komentar..." oninput="saveComment('${s.id}',this.value)">${saved.comment||''}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
div.appendChild(el);
|
||||||
|
});
|
||||||
|
app.appendChild(div);
|
||||||
|
});
|
||||||
|
updateCounts();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle(h){h.closest('.step').classList.toggle('open')}
|
||||||
|
let allOpen=false;
|
||||||
|
function toggleAll(){allOpen=!allOpen;document.querySelectorAll('.step').forEach(s=>s.classList.toggle('open',allOpen))}
|
||||||
|
function expandDetails(){document.querySelectorAll('.step-body details').forEach(d=>d.open=true);document.querySelectorAll('.step').forEach(s=>s.classList.add('open'));allOpen=true}
|
||||||
|
|
||||||
|
function setStatus(id,status,btn){
|
||||||
|
const st=load(id);
|
||||||
|
st.status=st.status===status?'':status;
|
||||||
|
save(id,st);
|
||||||
|
const step=btn.closest('.step');
|
||||||
|
step.className=`step ${st.status?'status-'+st.status:''} open`;
|
||||||
|
step.querySelector('.status-badge').textContent={approved:'OK',rejected:'NE',discuss:'?'}[st.status]||'';
|
||||||
|
step.querySelectorAll('.actions .btn').forEach(b=>b.classList.remove('active'));
|
||||||
|
if(st.status)btn.classList.add('active');
|
||||||
|
const ca=step.querySelector('.comment-area');
|
||||||
|
if(st.status==='discuss'||st.status==='rejected'){ca.classList.add('visible');ca.querySelector('textarea').focus()}
|
||||||
|
updateCounts();
|
||||||
|
}
|
||||||
|
function saveComment(id,v){const s=load(id);s.comment=v;save(id,s)}
|
||||||
|
function load(id){try{return JSON.parse(localStorage.getItem(STORAGE_PREFIX+id))||{}}catch{return{}}}
|
||||||
|
function save(id,s){localStorage.setItem(STORAGE_PREFIX+id,JSON.stringify(s))}
|
||||||
|
function allSteps(){return groups.flatMap(g=>g.steps)}
|
||||||
|
function updateCounts(){
|
||||||
|
let c={pending:0,approved:0,rejected:0,discuss:0};
|
||||||
|
allSteps().forEach(s=>{const st=load(s.id).status;if(st&&c[st]!==undefined)c[st]++;else c.pending++});
|
||||||
|
document.getElementById('cnt-pending').textContent=c.pending;
|
||||||
|
document.getElementById('cnt-approved').textContent=c.approved;
|
||||||
|
document.getElementById('cnt-rejected').textContent=c.rejected;
|
||||||
|
document.getElementById('cnt-discuss').textContent=c.discuss;
|
||||||
|
}
|
||||||
|
function clearAll(){if(!confirm('Opravdu smazat vsechny hlasy a komentare?'))return;allSteps().forEach(s=>localStorage.removeItem(STORAGE_PREFIX+s.id));render()}
|
||||||
|
|
||||||
|
function genMd(){
|
||||||
|
let md=`# Xetup - Review v2 vysledek\n\nDatum: ${new Date().toLocaleDateString('cs-CZ')}\n\n`;
|
||||||
|
const ic={approved:'[OK]',rejected:'[X]',discuss:'[?]',pending:'[ ]'};
|
||||||
|
const lb={approved:'SCHVALENO',rejected:'ZAMITNUTO',discuss:'K DISKUSI',pending:'CEKA'};
|
||||||
|
groups.forEach(g=>{
|
||||||
|
md+=`## ${g.icon}. ${g.title}\n\n`;
|
||||||
|
g.steps.forEach(s=>{
|
||||||
|
const st=load(s.id);const status=st.status||'pending';
|
||||||
|
md+=`${ic[status]} **${s.title}** (${lb[status]})\n`;
|
||||||
|
md+=`${s.summary}\n`;
|
||||||
|
if(st.comment)md+=`> ${st.comment}\n`;
|
||||||
|
md+=`\n`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
function exportMarkdown(){const b=new Blob([genMd()],{type:'text/markdown'});const a=document.createElement('a');a.href=URL.createObjectURL(b);a.download='xetup-review-v2.md';a.click()}
|
||||||
|
function copyToClipboard(){navigator.clipboard.writeText(genMd()).then(()=>{const b=event.target;const o=b.textContent;b.textContent='Skopirovano!';setTimeout(()=>b.textContent=o,1500)})}
|
||||||
|
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
21
runner-config.yml
Normal file
21
runner-config.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
|
||||||
|
runner:
|
||||||
|
file: /data/.runner
|
||||||
|
capacity: 1
|
||||||
|
timeout: 3h
|
||||||
|
fetch_timeout: 5s
|
||||||
|
fetch_interval: 2s
|
||||||
|
report_interval: 1s
|
||||||
|
|
||||||
|
cache:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
container:
|
||||||
|
network: xetup
|
||||||
|
privileged: false
|
||||||
|
valid_volumes:
|
||||||
|
- '**'
|
||||||
|
docker_host: "-"
|
||||||
|
force_pull: false
|
||||||
125
scripts/00-admin-account.ps1
Normal file
125
scripts/00-admin-account.ps1
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Creates the adminx9 local administrator account for MSP use.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Creates a hidden local administrator account 'adminx9' used by X9.cz technicians
|
||||||
|
for remote management and on-site administration. The account has no password by
|
||||||
|
design - it is invisible to regular users and only accessible to technicians who
|
||||||
|
know it exists. FullName is set to "X9.cz s.r.o." so it is identifiable in
|
||||||
|
system tools. Password policy is set so it never expires.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
vytvorit-lokalni-ucet-adminx9: Creates the account via [ADSI] WinNT provider. No password by design - the account is hidden from users and used only by MSP technicians for remote administration.
|
||||||
|
pridat-do-skupiny-administrators: Adds adminx9 to the local Administrators group via net localgroup. Required for full system management rights.
|
||||||
|
skryt-z-login-obrazovky-specialaccounts-: Sets HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList\adminx9 = 0. Removes the user tile from Windows login and lock screen completely.
|
||||||
|
heslo-nevypirsi-uzivatel-nesmeni-heslo: Sets ADS_UF_DONT_EXPIRE_PASSWD and ADS_UF_PASSWD_CANT_CHANGE flags via ADSI userFlags. The account never locks out or requires password maintenance.
|
||||||
|
zadne-heslo-aktualne-nastavovano-z-confi: Account created with empty password. Previous version used config.json password - removed because plaintext passwords in config files are a security risk.
|
||||||
|
fullname-x9-cz-s-r-o-via-adsi: Sets FullName property via [ADSI] so the account shows as "X9.cz s.r.o." in User Accounts panel, Event Viewer, and audit logs.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Account config - no password by design
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$accountName = "adminx9"
|
||||||
|
$accountDesc = "X9 MSP admin account"
|
||||||
|
$accountFullName = "X9.cz s.r.o."
|
||||||
|
|
||||||
|
if ($Config -and $Config.adminAccount) {
|
||||||
|
if ($Config.adminAccount.username) { $accountName = $Config.adminAccount.username }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Creating admin account: $accountName" -Level INFO
|
||||||
|
|
||||||
|
# Empty password - account is hidden from login screen, no password needed
|
||||||
|
$emptyPass = [System.Security.SecureString]::new()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Create or update account
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$existing = Get-LocalUser -Name $accountName -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
Write-Log " Account already exists - clearing password" -Level INFO
|
||||||
|
try {
|
||||||
|
Set-LocalUser -Name $accountName -Password $emptyPass -PasswordNeverExpires $true
|
||||||
|
Enable-LocalUser -Name $accountName
|
||||||
|
Write-Log " Account updated: $accountName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to update account: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
New-LocalUser -Name $accountName `
|
||||||
|
-Password $emptyPass `
|
||||||
|
-Description $accountDesc `
|
||||||
|
-PasswordNeverExpires `
|
||||||
|
-UserMayNotChangePassword `
|
||||||
|
-ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Account created: $accountName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to create account: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Set FullName via ADSI
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
$adsiUser = [ADSI]"WinNT://./$accountName,user"
|
||||||
|
$adsiUser.FullName = $accountFullName
|
||||||
|
$adsiUser.SetInfo()
|
||||||
|
Write-Log " FullName set to: $accountFullName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set FullName: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Add to Administrators group
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
$adminsGroup = (Get-LocalGroup | Where-Object { $_.SID -eq "S-1-5-32-544" }).Name
|
||||||
|
$members = Get-LocalGroupMember -Group $adminsGroup -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Name -like "*$accountName" }
|
||||||
|
if (-not $members) {
|
||||||
|
Add-LocalGroupMember -Group $adminsGroup -Member $accountName -ErrorAction Stop
|
||||||
|
Write-Log " Added to $adminsGroup" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Already in $adminsGroup" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to add to Administrators: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Hide account from login screen
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
$specialPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
|
||||||
|
if (-not (Test-Path $specialPath)) {
|
||||||
|
New-Item -Path $specialPath -Force | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $specialPath -Name $accountName -Value 0 -Type DWord -Force
|
||||||
|
Write-Log " Account hidden from login screen" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to hide account from login screen: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 0a - Admin account complete" -Level OK
|
||||||
199
scripts/01-bloatware.ps1
Normal file
199
scripts/01-bloatware.ps1
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Removes pre-installed bloatware: AppX packages, Capabilities, and Optional Features.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Removes Microsoft-bundled apps and features not needed in a business MSP deployment.
|
||||||
|
Removal is done for all users (-AllUsers) and from the provisioning store so new
|
||||||
|
users do not get them either. Calculator is intentionally kept.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
appx-balicky-odstraneni-pro-vsechny-uziv: Uses Remove-AppxPackage -AllUsers and Remove-AppxProvisionedPackage. The provisioned removal prevents apps from reinstalling for new user profiles. Covers ~35 apps including Cortana, Copilot, Teams personal, Xbox, Skype, News, Weather, Maps.
|
||||||
|
zachovano-microsoft-windowscalculator: Calculator is explicitly excluded. Lightweight utility frequently used by technicians and end users. Removing it would require manual reinstall from Store.
|
||||||
|
windows-capabilities-fax-ie-openssh-wmp-: Removed via Remove-WindowsCapability: Fax & Scan, Internet Explorer mode, OpenSSH client, Windows Media Player (legacy), WordPad, Handwriting recognition, Steps Recorder, Math Input Panel, Quick Assist.
|
||||||
|
windows-optional-features-ps-2-0-mediapl: Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 1a - AppX packages
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$AppxToRemove = @(
|
||||||
|
"Microsoft.Microsoft3DViewer"
|
||||||
|
"Microsoft.BingSearch"
|
||||||
|
"Microsoft.WindowsCamera"
|
||||||
|
"Clipchamp.Clipchamp"
|
||||||
|
"Microsoft.WindowsAlarms"
|
||||||
|
"Microsoft.Copilot"
|
||||||
|
"Microsoft.549981C3F5F10"
|
||||||
|
"Microsoft.Windows.DevHome"
|
||||||
|
"MicrosoftCorporationII.MicrosoftFamily"
|
||||||
|
"Microsoft.WindowsFeedbackHub"
|
||||||
|
"Microsoft.Edge.GameAssist"
|
||||||
|
"Microsoft.GetHelp"
|
||||||
|
"Microsoft.Getstarted"
|
||||||
|
"microsoft.windowscommunicationsapps"
|
||||||
|
"Microsoft.WindowsMaps"
|
||||||
|
"Microsoft.MixedReality.Portal"
|
||||||
|
"Microsoft.BingNews"
|
||||||
|
"Microsoft.MicrosoftOfficeHub"
|
||||||
|
"Microsoft.Office.OneNote"
|
||||||
|
"Microsoft.OutlookForWindows"
|
||||||
|
"Microsoft.Paint"
|
||||||
|
"Microsoft.MSPaint"
|
||||||
|
"Microsoft.People"
|
||||||
|
"Microsoft.Windows.Photos"
|
||||||
|
"Microsoft.PowerAutomateDesktop"
|
||||||
|
"MicrosoftCorporationII.QuickAssist"
|
||||||
|
"Microsoft.SkypeApp"
|
||||||
|
"Microsoft.ScreenSketch"
|
||||||
|
"Microsoft.MicrosoftSolitaireCollection"
|
||||||
|
"Microsoft.MicrosoftStickyNotes"
|
||||||
|
"MicrosoftTeams"
|
||||||
|
"MSTeams"
|
||||||
|
"Microsoft.Todos"
|
||||||
|
"Microsoft.WindowsSoundRecorder"
|
||||||
|
"Microsoft.Wallet"
|
||||||
|
"Microsoft.BingWeather"
|
||||||
|
"Microsoft.WindowsTerminal"
|
||||||
|
"Microsoft.Xbox.TCUI"
|
||||||
|
"Microsoft.XboxApp"
|
||||||
|
"Microsoft.XboxGameOverlay"
|
||||||
|
"Microsoft.XboxGamingOverlay"
|
||||||
|
"Microsoft.XboxIdentityProvider"
|
||||||
|
"Microsoft.XboxSpeechToTextOverlay"
|
||||||
|
"Microsoft.GamingApp"
|
||||||
|
"Microsoft.YourPhone"
|
||||||
|
"Microsoft.ZuneMusic"
|
||||||
|
"Microsoft.ZuneVideo"
|
||||||
|
"7EE7776C.LinkedInforWindows"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Packages to always keep
|
||||||
|
$KeepPackages = @("Microsoft.WindowsCalculator")
|
||||||
|
if ($Config -and $Config.bloatware -and $Config.bloatware.keepPackages) {
|
||||||
|
$KeepPackages += $Config.bloatware.keepPackages
|
||||||
|
}
|
||||||
|
$KeepPackages = $KeepPackages | Select-Object -Unique
|
||||||
|
|
||||||
|
Write-Log "1a - Removing AppX packages" -Level STEP
|
||||||
|
|
||||||
|
foreach ($pkg in $AppxToRemove) {
|
||||||
|
if ($KeepPackages -contains $pkg) {
|
||||||
|
Write-Log " KEEP $pkg" -Level INFO
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installed packages (current user + all users)
|
||||||
|
$installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue
|
||||||
|
if ($installed) {
|
||||||
|
try {
|
||||||
|
$installed | Remove-AppxPackage -AllUsers -ErrorAction Stop
|
||||||
|
Write-Log " Removed AppxPackage: $pkg" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove AppxPackage $pkg - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Provisioned packages (for new users)
|
||||||
|
$provisioned = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.DisplayName -eq $pkg }
|
||||||
|
if ($provisioned) {
|
||||||
|
try {
|
||||||
|
$provisioned | Remove-AppxProvisionedPackage -Online -ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Removed provisioned: $pkg" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove provisioned $pkg - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $installed -and -not $provisioned) {
|
||||||
|
Write-Log " Not found (already removed): $pkg" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 1b - Windows Capabilities
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$CapabilitiesToRemove = @(
|
||||||
|
"Print.Fax.Scan"
|
||||||
|
"Language.Handwriting"
|
||||||
|
"Browser.InternetExplorer"
|
||||||
|
"MathRecognizer"
|
||||||
|
"OneCoreUAP.OneSync"
|
||||||
|
"OpenSSH.Client"
|
||||||
|
"Microsoft.Windows.MSPaint"
|
||||||
|
"Microsoft.Windows.PowerShell.ISE"
|
||||||
|
"App.Support.QuickAssist"
|
||||||
|
"Microsoft.Windows.SnippingTool"
|
||||||
|
"App.StepsRecorder"
|
||||||
|
"Hello.Face"
|
||||||
|
"Media.WindowsMediaPlayer"
|
||||||
|
"Microsoft.Windows.WordPad"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Log "1b - Removing Windows Capabilities" -Level STEP
|
||||||
|
|
||||||
|
$installedCaps = Get-WindowsCapability -Online -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
foreach ($cap in $CapabilitiesToRemove) {
|
||||||
|
# Match by prefix (e.g. Hello.Face matches Hello.Face.20134.0.0.0)
|
||||||
|
$matches = $installedCaps | Where-Object {
|
||||||
|
$_.Name -like "$cap*" -and $_.State -eq "Installed"
|
||||||
|
}
|
||||||
|
if ($matches) {
|
||||||
|
foreach ($c in $matches) {
|
||||||
|
try {
|
||||||
|
Remove-WindowsCapability -Online -Name $c.Name -ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Removed capability: $($c.Name)" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove capability $($c.Name) - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log " Not found or not installed: $cap" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 1c - Windows Optional Features
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$FeaturesToDisable = @(
|
||||||
|
"MediaPlayback"
|
||||||
|
"MicrosoftWindowsPowerShellV2Root"
|
||||||
|
"Recall"
|
||||||
|
"Microsoft-SnippingTool"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Log "1c - Disabling Windows Optional Features" -Level STEP
|
||||||
|
|
||||||
|
foreach ($feat in $FeaturesToDisable) {
|
||||||
|
$feature = Get-WindowsOptionalFeature -Online -FeatureName $feat -ErrorAction SilentlyContinue
|
||||||
|
if ($feature -and $feature.State -eq "Enabled") {
|
||||||
|
try {
|
||||||
|
Disable-WindowsOptionalFeature -Online -FeatureName $feat -NoRestart -ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Disabled feature: $feat" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to disable feature $feat - $_" -Level WARN
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log " Not enabled or not found: $feat" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 1 complete" -Level OK
|
||||||
213
scripts/02-software.ps1
Normal file
213
scripts/02-software.ps1
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Installs standard business software via winget, sets Adobe PDF default, and installs Atera RMM agent.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Uses winget to install the standard X9.cz MSP software bundle. Checks winget
|
||||||
|
availability before running. Each install is logged. After Adobe Acrobat Reader,
|
||||||
|
temporarily stops the UCPD driver (User Choice Protection Driver, present since
|
||||||
|
Win11 23H2 / Win10 22H2 update) to allow the HKCR file association write, sets
|
||||||
|
.pdf -> AcroRd32, then restarts UCPD. Atera RMM agent is installed for MSP
|
||||||
|
monitoring, remote access, and ticketing integration.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
7-zip-7zip-7zip: Installs 7-Zip (winget ID: 7zip.7zip). Used for archive management. Silent install with --accept-package-agreements --accept-source-agreements flags required for unattended deployment.
|
||||||
|
adobe-acrobat-reader-64-bit-adobe-acroba: Installs Adobe Acrobat Reader DC 64-bit (Adobe.Acrobat.Reader.64-bit). Required as the default PDF viewer to prevent Edge from handling PDFs in browser mode, which limits functionality.
|
||||||
|
openvpn-connect-openvpntechnologies-open: Installs OpenVPN Connect client. Used for client VPN access when the client network requires a VPN. The ovpn profile and credentials are configured separately per client.
|
||||||
|
atera-agent-install: Atera RMM agent installed via msiexec /qn. Download: Invoke-WebRequest from https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337. Agent enables MSP monitoring, remote access, and ticketing integration with the Atera dashboard.
|
||||||
|
adobe-pdf-default-pdf-acrord32-po-instal: Sets .pdf -> AcroRd32 file association after Acrobat install via HKCR (system-wide, no UserChoice hash issue). UCPD driver is stopped immediately before the write and restarted after to ensure the association persists across Edge updates.
|
||||||
|
ucpd-sys-kernel-driver-od-feb-2024-bloku: UCPD.sys (User Choice Protection Driver) is stopped before the PDF association write and restarted after. Pattern: Stop-Service ucpd -> set HKCR\.pdf -> Start-Service ucpd. Implemented in this script.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Feature {
|
||||||
|
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
|
||||||
|
try {
|
||||||
|
if ($null -eq $Cfg) { return $Default }
|
||||||
|
$stepFeatures = $Cfg.features.$StepID
|
||||||
|
if ($null -eq $stepFeatures) { return $Default }
|
||||||
|
$val = $stepFeatures.$FeatureID
|
||||||
|
if ($null -eq $val) { return $Default }
|
||||||
|
return [bool]$val
|
||||||
|
} catch { return $Default }
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Check winget availability
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Checking winget availability" -Level INFO
|
||||||
|
|
||||||
|
$winget = Get-Command winget -ErrorAction SilentlyContinue
|
||||||
|
if (-not $winget) {
|
||||||
|
# Try to find winget in known locations
|
||||||
|
$wingetPaths = @(
|
||||||
|
"$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe"
|
||||||
|
"$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller*\winget.exe"
|
||||||
|
)
|
||||||
|
foreach ($p in $wingetPaths) {
|
||||||
|
$found = Get-Item $p -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
if ($found) { $winget = $found.FullName; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $winget) {
|
||||||
|
Write-Log "winget not found - software installation skipped" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "winget found: $($winget.Source -or $winget)" -Level OK
|
||||||
|
|
||||||
|
# Accept agreements upfront
|
||||||
|
& winget source update --accept-source-agreements 2>&1 | Out-Null
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Install packages from config
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "software" "wingetInstalls") {
|
||||||
|
if (-not $Config -or -not $Config.software -or -not $Config.software.install) {
|
||||||
|
Write-Log "No software list in config - skipping installs" -Level WARN
|
||||||
|
} else {
|
||||||
|
foreach ($pkg in $Config.software.install) {
|
||||||
|
Write-Log "Installing $($pkg.name) ($($pkg.wingetId))" -Level INFO
|
||||||
|
$result = & winget install --id $pkg.wingetId `
|
||||||
|
--silent `
|
||||||
|
--accept-package-agreements `
|
||||||
|
--accept-source-agreements `
|
||||||
|
--disable-interactivity `
|
||||||
|
2>&1
|
||||||
|
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Log " Installed OK: $($pkg.name)" -Level OK
|
||||||
|
} elseif ($exitCode -eq -1978335189) {
|
||||||
|
# 0x8A150011 = already installed
|
||||||
|
Write-Log " Already installed: $($pkg.name)" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Failed: $($pkg.name) (exit $exitCode)" -Level ERROR
|
||||||
|
Write-Log " Output: $($result -join ' ')" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log "wingetInstalls feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Set Adobe Reader as default PDF app
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "software" "pdfDefault") {
|
||||||
|
Write-Log "Setting Adobe Reader as default PDF app" -Level INFO
|
||||||
|
|
||||||
|
# Stop UCPD driver before writing file association.
|
||||||
|
# UCPD.sys (User Choice Protection Driver) blocks UserChoice registry writes
|
||||||
|
# on Win11 23H2+ and some Win10 22H2 builds. Stopping it temporarily allows
|
||||||
|
# the HKCR association to be written reliably.
|
||||||
|
$ucpdStopped = $false
|
||||||
|
$ucpdSvc = Get-Service -Name "ucpd" -ErrorAction SilentlyContinue
|
||||||
|
if ($ucpdSvc) {
|
||||||
|
try {
|
||||||
|
Stop-Service -Name "ucpd" -Force -ErrorAction Stop
|
||||||
|
$ucpdStopped = $true
|
||||||
|
Write-Log " UCPD driver stopped" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Could not stop UCPD: $_ (association may not persist on some builds)" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find Adobe PDF viewer executable (Acrobat DC or Reader DC)
|
||||||
|
$acroPaths = @(
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe"
|
||||||
|
)
|
||||||
|
$acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $acroExe) {
|
||||||
|
Write-Log " Adobe PDF viewer not found - PDF default not set" -Level WARN
|
||||||
|
} else {
|
||||||
|
Write-Log " Found: $acroExe" -Level INFO
|
||||||
|
|
||||||
|
# Set file type association via HKCR (system-wide, requires admin)
|
||||||
|
$progId = "AcroExch.Document.DC"
|
||||||
|
$openCmd = "`"$acroExe`" `"%1`""
|
||||||
|
|
||||||
|
# HKCR\.pdf -> progId
|
||||||
|
if (-not (Test-Path "HKCR:\.pdf")) {
|
||||||
|
New-Item -Path "HKCR:\.pdf" -Force | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId
|
||||||
|
|
||||||
|
# HKCR\AcroExch.Document.DC\shell\open\command
|
||||||
|
$cmdPath = "HKCR:\$progId\shell\open\command"
|
||||||
|
if (-not (Test-Path $cmdPath)) {
|
||||||
|
New-Item -Path $cmdPath -Force | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd
|
||||||
|
|
||||||
|
Write-Log " PDF default set to AcroRd32 (HKCR)" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart UCPD after writing association
|
||||||
|
if ($ucpdStopped) {
|
||||||
|
try {
|
||||||
|
Start-Service -Name "ucpd" -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " UCPD driver restarted" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Could not restart UCPD: $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log "pdfDefault feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Install Atera RMM Agent
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "software" "ateraAgent") {
|
||||||
|
Write-Log "Installing Atera RMM Agent" -Level INFO
|
||||||
|
|
||||||
|
$ateraUrl = "https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337"
|
||||||
|
$ateraMsi = "$env:TEMP\AteraAgent.msi"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Log " Downloading Atera agent..." -Level INFO
|
||||||
|
Invoke-WebRequest -Uri $ateraUrl -OutFile $ateraMsi -UseBasicParsing -ErrorAction Stop
|
||||||
|
Write-Log " Download complete" -Level OK
|
||||||
|
|
||||||
|
$msiResult = & msiexec /i $ateraMsi /qn 2>&1
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
|
||||||
|
$ateraExe = "$env:ProgramFiles\ATERA Networks\AteraAgent\AteraAgent.exe"
|
||||||
|
if (Test-Path $ateraExe) {
|
||||||
|
Write-Log " Atera agent installed" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Atera agent install may have failed - binary not found at expected path" -Level WARN
|
||||||
|
Write-Log " msiexec output: $($msiResult -join ' ')" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Atera agent download/install failed: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Remove-Item $ateraMsi -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log "ateraAgent feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 2 complete" -Level OK
|
||||||
438
scripts/03-system-registry.ps1
Normal file
438
scripts/03-system-registry.ps1
Normal file
|
|
@ -0,0 +1,438 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Applies system-wide registry settings and power configuration (HKLM).
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Sets machine-wide registry tweaks under HKLM that apply to all users. Disables
|
||||||
|
unwanted telemetry and cloud features, configures Edge policies, sets power plan
|
||||||
|
timeouts, and disables proxy auto-detect. Uninstalls the pre-installed OneDrive
|
||||||
|
consumer version via OneDriveSetup.exe /uninstall - intentional for a clean MSP
|
||||||
|
deployment baseline. No DisableFileSyncNGSC policy key is set, so M365 installation
|
||||||
|
can install and run its own OneDrive version without restriction.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
bypass-nro-oobe-bypassnro-1: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\BypassNRO = 1. Bypasses the "Let's connect you to a network" OOBE screen. Enables offline Windows setup without forcing a Microsoft account login.
|
||||||
|
zakaz-auto-instalace-teams: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications\ConfigureChatAutoInstall = 0. Prevents Windows from auto-installing Teams Personal during OOBE or after Cumulative Updates.
|
||||||
|
zakaz-cloud-optimized-content: ContentDeliveryManager\DisableCloudOptimizedContent = 1. Stops Windows from pushing sponsored app suggestions, tips from Microsoft servers, and "Get even more from Windows" prompts.
|
||||||
|
zakaz-widgets-news-and-interests: HKLM\SOFTWARE\Policies\Microsoft\Dsh\AllowNewsAndInterests = 0. Disables the Widgets taskbar button and panel (news feed, weather, stocks). Not relevant for business deployments.
|
||||||
|
hesla-bez-expirace-net-accounts-maxpwage: net accounts /maxpwage:UNLIMITED. Sets the local password expiration policy to never. MSP-managed machines handle password rotation via other means (Atera, domain policy, manual).
|
||||||
|
casova-zona-central-europe-standard-time: Set-TimeZone -Id "Central Europe Standard Time". UTC+1 (UTC+2 in summer DST). Applied system-wide. Critical for correct log timestamps, scheduled task timing, and calendar sync.
|
||||||
|
zakaz-gamedvr: HKLM\SOFTWARE\Policies\Microsoft\Windows\GameDVR\AppCaptureEnabled = 0. Disables Xbox Game Bar screen capture overlay. Reduces background resource usage and eliminates unintended capture prompts on business machines.
|
||||||
|
edge-skryt-first-run-experience: HideFirstRunExperience=1 + DefaultBrowserSettingEnabled=0. Suppresses Edge welcome wizard and default browser prompts on first launch.
|
||||||
|
edge-policies-panel-oblibeny-vyhledavac: FavoritesBarEnabled=1 (always show), DefaultSearchProviderEnabled=1, DefaultSearchProviderName=Google, ManagedSearchEngines removes other providers.
|
||||||
|
edge-policies-tlacitka-zobrazit: DownloadsButtonEnabled=1, HistoryButtonEnabled=1.
|
||||||
|
edge-policies-tlacitka-skryt: HomeButtonEnabled=0, SplitScreenEnabled=0, EdgeEDropEnabled=0 (Drop), WebCaptureEnabled=0 (Screenshot), ShareAllowed=0.
|
||||||
|
edge-policies-obsah-a-telemetrie: NewTabPageContentEnabled=0, ShowRecommendationsEnabled=0, SpotlightExperiencesAndRecommendationsEnabled=0, PersonalizationReportingEnabled=0, EdgeShoppingAssistantEnabled=0, ShowMicrosoftRewards=0, HubsSidebarEnabled=0, SearchSuggestEnabled=0, DiagnosticData=0, FeedbackSurveysEnabled=0, EdgeCollectionsEnabled=0.
|
||||||
|
onedrive-uninstall-intentional: Uninstalls the pre-installed OneDrive consumer version via OneDriveSetup.exe /uninstall and removes Start Menu shortcut. Intentional for clean MSP deployment baseline. No DisableFileSyncNGSC policy key is set - M365 installation can reinstall and run OneDrive normally. Only the stock consumer pre-install is removed.
|
||||||
|
powercfg-nastaveni-spotreba-energie: powercfg /change: standby-timeout-ac 0 (never sleep on AC), monitor-timeout-ac 60 (screen off after 60 min on AC), standby-timeout-dc 30 (sleep after 30 min on battery), monitor-timeout-dc 15 (screen off after 15 min on battery). Applied to active power plan.
|
||||||
|
proxy-auto-detect-zakaz-autodetect-0: HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\AutoDetect = 0. Disables WPAD (Web Proxy Auto-Discovery). Eliminates startup delays from WPAD DNS lookup and prevents MITM via rogue WPAD on untrusted networks.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Feature {
|
||||||
|
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
|
||||||
|
try {
|
||||||
|
if ($null -eq $Cfg) { return $Default }
|
||||||
|
$stepFeatures = $Cfg.features.$StepID
|
||||||
|
if ($null -eq $stepFeatures) { return $Default }
|
||||||
|
$val = $stepFeatures.$FeatureID
|
||||||
|
if ($null -eq $val) { return $Default }
|
||||||
|
return [bool]$val
|
||||||
|
} catch { return $Default }
|
||||||
|
}
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class RegPrivilege {
|
||||||
|
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
|
||||||
|
static extern bool AdjustTokenPrivileges(IntPtr htok, bool disAll, ref TokPriv1Luid newState, int len, IntPtr prev, IntPtr relen);
|
||||||
|
[DllImport("kernel32.dll", ExactSpelling=true)]
|
||||||
|
static extern IntPtr GetCurrentProcess();
|
||||||
|
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
|
||||||
|
static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
|
||||||
|
[DllImport("advapi32.dll", SetLastError=true)]
|
||||||
|
static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
|
struct TokPriv1Luid { public int Count; public long Luid; public int Attr; }
|
||||||
|
const int TOKEN_QUERY = 0x8;
|
||||||
|
const int TOKEN_ADJUST = 0x20;
|
||||||
|
const int SE_PRIVILEGE_ENABLED = 2;
|
||||||
|
public static bool Enable(string privilege) {
|
||||||
|
IntPtr htok = IntPtr.Zero;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST | TOKEN_QUERY, ref htok)) return false;
|
||||||
|
TokPriv1Luid tp; tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED;
|
||||||
|
if (!LookupPrivilegeValue(null, privilege, ref tp.Luid)) return false;
|
||||||
|
return AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
function Grant-RegWriteAccess {
|
||||||
|
param([string]$Path)
|
||||||
|
# Grants Administrators FullControl on a TrustedInstaller-owned registry key.
|
||||||
|
# Enables SeTakeOwnershipPrivilege + SeRestorePrivilege to override ACL.
|
||||||
|
try {
|
||||||
|
[RegPrivilege]::Enable("SeTakeOwnershipPrivilege") | Out-Null
|
||||||
|
[RegPrivilege]::Enable("SeRestorePrivilege") | Out-Null
|
||||||
|
|
||||||
|
$hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1'
|
||||||
|
$subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', ''
|
||||||
|
$rootKey = switch ($hive) {
|
||||||
|
"HKLM" { [Microsoft.Win32.Registry]::LocalMachine }
|
||||||
|
"HKCU" { [Microsoft.Win32.Registry]::CurrentUser }
|
||||||
|
"HKCR" { [Microsoft.Win32.Registry]::ClassesRoot }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Take ownership (requires SeTakeOwnershipPrivilege)
|
||||||
|
$key = $rootKey.OpenSubKey($subkey,
|
||||||
|
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
|
||||||
|
[System.Security.AccessControl.RegistryRights]::TakeOwnership)
|
||||||
|
if ($key) {
|
||||||
|
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
|
||||||
|
$acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators")
|
||||||
|
$key.SetAccessControl($acl)
|
||||||
|
$key.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grant FullControl to Administrators (requires ChangePermissions)
|
||||||
|
$key = $rootKey.OpenSubKey($subkey,
|
||||||
|
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
|
||||||
|
[System.Security.AccessControl.RegistryRights]::ChangePermissions)
|
||||||
|
if ($key) {
|
||||||
|
$acl = $key.GetAccessControl()
|
||||||
|
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
|
||||||
|
"BUILTIN\Administrators",
|
||||||
|
[System.Security.AccessControl.RegistryRights]::FullControl,
|
||||||
|
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
|
||||||
|
[System.Security.AccessControl.PropagationFlags]::None,
|
||||||
|
[System.Security.AccessControl.AccessControlType]::Allow)
|
||||||
|
$acl.SetAccessRule($rule)
|
||||||
|
$key.SetAccessControl($acl)
|
||||||
|
$key.Close()
|
||||||
|
}
|
||||||
|
Write-Log " ACL fixed for $Path" -Level INFO
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-Reg {
|
||||||
|
param(
|
||||||
|
[string]$Path,
|
||||||
|
[string]$Name,
|
||||||
|
$Value,
|
||||||
|
[string]$Type = "DWord"
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $Path)) {
|
||||||
|
New-Item -Path $Path -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
Write-Log " SET $Path\$Name = $Value" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Retry 1: grant write access via ACL manipulation
|
||||||
|
try {
|
||||||
|
Grant-RegWriteAccess -Path $Path
|
||||||
|
if (-not (Test-Path $Path)) {
|
||||||
|
New-Item -Path $Path -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
Write-Log " SET $Path\$Name = $Value (after ACL fix)" -Level OK
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
# Retry 2: write via scheduled task running as SYSTEM
|
||||||
|
# SYSTEM has full registry access regardless of key ACL
|
||||||
|
try {
|
||||||
|
$regType = switch ($Type) {
|
||||||
|
"DWord" { "REG_DWORD" }
|
||||||
|
"String" { "REG_SZ" }
|
||||||
|
"ExpandString"{ "REG_EXPAND_SZ" }
|
||||||
|
"MultiString" { "REG_MULTI_SZ" }
|
||||||
|
"QWord" { "REG_QWORD" }
|
||||||
|
default { "REG_DWORD" }
|
||||||
|
}
|
||||||
|
# Convert registry PS path to reg.exe path
|
||||||
|
$regPath = $Path -replace '^HKLM:\\', 'HKLM\' `
|
||||||
|
-replace '^HKCU:\\', 'HKCU\' `
|
||||||
|
-replace '^HKCR:\\', 'HKCR\'
|
||||||
|
$tempScript = "$env:TEMP\set-reg-system-$([System.IO.Path]::GetRandomFileName()).ps1"
|
||||||
|
"reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f" |
|
||||||
|
Set-Content -Path $tempScript -Encoding UTF8
|
||||||
|
|
||||||
|
$taskName = "TempRegFix-$([System.IO.Path]::GetRandomFileName())"
|
||||||
|
$action = New-ScheduledTaskAction -Execute "cmd.exe" `
|
||||||
|
-Argument "/c reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f"
|
||||||
|
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 30)
|
||||||
|
$task = New-ScheduledTask -Action $action -Principal $principal -Settings $settings
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
|
||||||
|
Start-ScheduledTask -TaskName $taskName
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $tempScript -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Verify it was written
|
||||||
|
$written = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name
|
||||||
|
if ($null -ne $written) {
|
||||||
|
Write-Log " SET $Path\$Name = $Value (via SYSTEM task)" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " FAILED $Path\$Name - SYSTEM task ran but value not found" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED $Path\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-Reg {
|
||||||
|
param([string]$Path, [string]$Name)
|
||||||
|
try {
|
||||||
|
if (Test-Path $Path) {
|
||||||
|
Remove-ItemProperty -Path $Path -Name $Name -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " REMOVED $Path\$Name" -Level OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED removing $Path\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "3 - Applying HKLM system registry tweaks" -Level STEP
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Always-on: password and timezone (fundamental system settings)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log " Setting password max age to UNLIMITED" -Level INFO
|
||||||
|
$pwResult = & net accounts /maxpwage:UNLIMITED 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log " Password max age set to UNLIMITED" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Failed to set password max age: $pwResult" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
$tz = "Central Europe Standard Time"
|
||||||
|
if ($Config -and $Config.deployment -and $Config.deployment.timezone) {
|
||||||
|
$tz = $Config.deployment.timezone
|
||||||
|
}
|
||||||
|
Write-Log " Setting time zone: $tz" -Level INFO
|
||||||
|
try {
|
||||||
|
Set-TimeZone -Id $tz -ErrorAction Stop
|
||||||
|
Write-Log " Time zone set: $tz" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set time zone: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# System tweaks (Windows features, cloud noise, Xbox, Recall, Search UI)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "systemRegistry" "systemTweaks") {
|
||||||
|
Write-Log " Applying system tweaks" -Level INFO
|
||||||
|
|
||||||
|
# Bypass Network Requirement on OOBE (BypassNRO)
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" `
|
||||||
|
-Name "BypassNRO" -Value 1
|
||||||
|
|
||||||
|
# Disable auto-install of Teams (Chat)
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" `
|
||||||
|
-Name "ConfigureChatAutoInstall" -Value 0
|
||||||
|
|
||||||
|
# Disable Cloud Optimized Content (ads in Start menu etc.)
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
|
||||||
|
-Name "DisableCloudOptimizedContent" -Value 1
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
|
||||||
|
-Name "DisableWindowsConsumerFeatures" -Value 1
|
||||||
|
|
||||||
|
# Disable Widgets (News and Interests)
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Dsh" `
|
||||||
|
-Name "AllowNewsAndInterests" -Value 0
|
||||||
|
|
||||||
|
# Disable Outlook (new) auto-install via UScheduler
|
||||||
|
Write-Log " Disabling Outlook (new) auto-install" -Level INFO
|
||||||
|
$uschedulerPaths = @(
|
||||||
|
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate"
|
||||||
|
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate"
|
||||||
|
)
|
||||||
|
foreach ($uPath in $uschedulerPaths) {
|
||||||
|
if (Test-Path $uPath) {
|
||||||
|
try {
|
||||||
|
Remove-Item -Path $uPath -Recurse -Force
|
||||||
|
Write-Log " Removed UScheduler key: $uPath" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove UScheduler key: $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disable GameDVR
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" `
|
||||||
|
-Name "AllowGameDVR" -Value 0
|
||||||
|
|
||||||
|
# Disable Recall (Windows AI feature)
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
|
||||||
|
-Name "DisableAIDataAnalysis" -Value 1
|
||||||
|
|
||||||
|
# Search on taskbar - hide via HKLM policy (Win11 22H2+ enforcement)
|
||||||
|
# User-level SearchboxTaskbarMode alone is insufficient on newer Win11 builds;
|
||||||
|
# this policy key ensures the setting survives Windows Updates.
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search" `
|
||||||
|
-Name "SearchOnTaskbarMode" -Value 0
|
||||||
|
|
||||||
|
# Start menu - hide Recommended section (Win11)
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
|
||||||
|
-Name "HideRecommendedSection" -Value 1
|
||||||
|
} else {
|
||||||
|
Write-Log "systemTweaks feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Microsoft Edge policies
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "systemRegistry" "edgePolicies") {
|
||||||
|
Write-Log " Applying Edge policies" -Level INFO
|
||||||
|
$edgePath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"
|
||||||
|
|
||||||
|
# UI / first run
|
||||||
|
Set-Reg -Path $edgePath -Name "HideFirstRunExperience" -Value 1
|
||||||
|
Set-Reg -Path $edgePath -Name "DefaultBrowserSettingEnabled" -Value 0
|
||||||
|
|
||||||
|
# New tab page / recommendations
|
||||||
|
Set-Reg -Path $edgePath -Name "NewTabPageContentEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "ShowRecommendationsEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "SpotlightExperiencesAndRecommendationsEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "PersonalizationReportingEnabled" -Value 0
|
||||||
|
|
||||||
|
# Shopping / rewards / sidebar
|
||||||
|
Set-Reg -Path $edgePath -Name "EdgeShoppingAssistantEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "ShowMicrosoftRewards" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "HubsSidebarEnabled" -Value 0
|
||||||
|
|
||||||
|
# Search suggestions
|
||||||
|
Set-Reg -Path $edgePath -Name "SearchSuggestEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "ImportOnEachLaunch" -Value 0
|
||||||
|
|
||||||
|
# Telemetry / feedback
|
||||||
|
Set-Reg -Path $edgePath -Name "DiagnosticData" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "FeedbackSurveysEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "EdgeCollectionsEnabled" -Value 0
|
||||||
|
|
||||||
|
# Toolbar buttons - show
|
||||||
|
Set-Reg -Path $edgePath -Name "FavoritesBarEnabled" -Value 1 # Favorites bar always visible
|
||||||
|
Set-Reg -Path $edgePath -Name "DownloadsButtonEnabled" -Value 1
|
||||||
|
Set-Reg -Path $edgePath -Name "HistoryButtonEnabled" -Value 1
|
||||||
|
Set-Reg -Path $edgePath -Name "PerformanceButtonEnabled" -Value 1 # Sleeping Tabs / Performance
|
||||||
|
|
||||||
|
# Toolbar buttons - hide
|
||||||
|
Set-Reg -Path $edgePath -Name "HomeButtonEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "SplitScreenEnabled" -Value 0
|
||||||
|
Set-Reg -Path $edgePath -Name "EdgeEDropEnabled" -Value 0 # Drop
|
||||||
|
Set-Reg -Path $edgePath -Name "WebCaptureEnabled" -Value 0 # Screenshot
|
||||||
|
Set-Reg -Path $edgePath -Name "ShareAllowed" -Value 0 # Share
|
||||||
|
|
||||||
|
# Default search engine: Google
|
||||||
|
# SearchProviderEnabled must be 1, SearchProviderName + URL set the provider
|
||||||
|
Set-Reg -Path $edgePath -Name "DefaultSearchProviderEnabled" -Value 1 -Type "DWord"
|
||||||
|
Set-Reg -Path $edgePath -Name "DefaultSearchProviderName" -Value "Google" -Type "String"
|
||||||
|
Set-Reg -Path $edgePath -Name "DefaultSearchProviderSearchURL" `
|
||||||
|
-Value "https://www.google.com/search?q={searchTerms}" -Type "String"
|
||||||
|
# Remove other search engines (empty list = no other providers besides default)
|
||||||
|
Set-Reg -Path $edgePath -Name "ManagedSearchEngines" `
|
||||||
|
-Value '[{"is_default":true,"name":"Google","search_url":"https://www.google.com/search?q={searchTerms}","keyword":"google.com"}]' `
|
||||||
|
-Type "String"
|
||||||
|
|
||||||
|
# Disable desktop shortcut on install/update
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" `
|
||||||
|
-Name "CreateDesktopShortcutDefault" -Value 0
|
||||||
|
} else {
|
||||||
|
Write-Log "edgePolicies feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# OneDrive - uninstall from clean Windows (no policy block)
|
||||||
|
# NOTE: No policy key is set intentionally - M365 installation can reinstall
|
||||||
|
# and run OneDrive normally. Policy DisableFileSyncNGSC would prevent that.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "systemRegistry" "oneDriveUninstall") {
|
||||||
|
Write-Log " Uninstalling OneDrive" -Level INFO
|
||||||
|
|
||||||
|
$oneDrivePaths = @(
|
||||||
|
"$env:SystemRoot\System32\OneDriveSetup.exe"
|
||||||
|
"$env:SystemRoot\SysWOW64\OneDriveSetup.exe"
|
||||||
|
)
|
||||||
|
foreach ($odPath in $oneDrivePaths) {
|
||||||
|
if (Test-Path $odPath) {
|
||||||
|
try {
|
||||||
|
& $odPath /uninstall 2>&1 | Out-Null
|
||||||
|
Write-Log " OneDrive uninstalled via $odPath" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " OneDrive uninstall failed: $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove OneDrive Start Menu shortcut
|
||||||
|
$odLnk = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"
|
||||||
|
if (Test-Path $odLnk) {
|
||||||
|
Remove-Item $odLnk -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Removed OneDrive Start Menu shortcut" -Level OK
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log "oneDriveUninstall feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Power configuration
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "systemRegistry" "powercfg") {
|
||||||
|
Write-Log " Applying power configuration" -Level INFO
|
||||||
|
|
||||||
|
$powercfgArgs = @(
|
||||||
|
@("/change", "standby-timeout-ac", "0"), # never sleep on AC
|
||||||
|
@("/change", "monitor-timeout-ac", "60"), # screen off after 60 min on AC
|
||||||
|
@("/change", "standby-timeout-dc", "30"), # sleep after 30 min on battery
|
||||||
|
@("/change", "monitor-timeout-dc", "15") # screen off after 15 min on battery
|
||||||
|
)
|
||||||
|
foreach ($a in $powercfgArgs) {
|
||||||
|
$result = & powercfg @a 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log " powercfg $($a -join ' ')" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " powercfg $($a -join ' ') failed: $result" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log "powercfg feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Proxy auto-detect disable (WPAD)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "systemRegistry" "proxyDisable") {
|
||||||
|
Write-Log " Disabling WPAD proxy auto-detect" -Level INFO
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings" `
|
||||||
|
-Name "AutoDetect" -Value 0
|
||||||
|
} else {
|
||||||
|
Write-Log "proxyDisable feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 3 complete" -Level OK
|
||||||
375
scripts/04-default-profile.ps1
Normal file
375
scripts/04-default-profile.ps1
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Applies registry settings to the Default User profile and the current logged-in user.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Loads C:\Users\Default\NTUSER.DAT as a temporary hive (HKU\DefaultProfile), applies
|
||||||
|
all settings, then unloads it. Every new user account created on this machine inherits
|
||||||
|
these settings on first logon. The same settings are applied directly to the current
|
||||||
|
user's HKCU. Does NOT block OneDrive re-launch - the Explorer namespace CLSID and RunOnce entries have been removed.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
taskbar-zarovnat-vlevo-taskbaral-0: TaskbarAl = 0 in Explorer\Advanced. Windows 11 default is center-aligned (TaskbarAl = 1). Left alignment matches Windows 10 muscle memory and is strongly preferred by business users transitioning from Win10.
|
||||||
|
taskbar-skryt-search-copilot-task-view-w: Hides Search box (SearchboxTaskbarMode=0), Copilot button (ShowCopilotButton=0), Task View (ShowTaskViewButton=0), Widgets (TaskbarDa=0), Chat/Teams (TaskbarMn=0). Reduces taskbar clutter to just pinned apps and running processes.
|
||||||
|
taskbar-zobrazit-vsechny-ikonky-v-tray-s: Registers scheduled task that sets EnableAutoTray=0 on logon (repeat every 1 min). Windows 11 periodically re-hides tray icons - this task forces all icons visible so users can see VPN status, antivirus, backup, etc.
|
||||||
|
taskbar-vyprazdnit-pinlist-taskbarlayout: Deploys TaskbarLayoutModification.xml. ProfileType=default: empty pins (clean slate). ProfileType=admin: Explorer+PowerShell+Edge. ProfileType=user: Explorer+Edge. Lock is removed by UnlockStartLayout task 5 min after first boot so users can customize.
|
||||||
|
explorer-zobrazovat-pripony-souboru-hide: HideFileExt = 0 in Explorer\Advanced. Shows file extensions (.docx, .exe, .pdf, .ps1) in File Explorer. Essential for recognizing file types, avoiding phishing (fake .pdf.exe), and general IT work.
|
||||||
|
explorer-otevrit-na-this-pc-launchto-1: LaunchTo = 1. File Explorer opens to "This PC" (drives view) instead of Quick Access. More useful on fresh machines where Quick Access history is empty and irrelevant.
|
||||||
|
start-menu-vyprazdnit-piny-win11: ConfigureStartPins = {"pinnedList":[]} applied via registry. Removes all default Start menu tiles (Edge, Teams, Store, Office, Solitaire, etc.) from the Windows 11 Start grid. User starts with an empty, clean Start menu.
|
||||||
|
start-menu-zakaz-bing-vyhledavani: DisableSearchBoxSuggestions = 1 in Software\Policies\Microsoft\Windows. Disables web search, Bing suggestions, and online results in Start menu search. Search returns only local apps, files, and settings.
|
||||||
|
copilot-zakaz-turnoffwindowscopilot-1: TurnOffWindowsCopilot = 1 in SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot. Disables the Windows Copilot sidebar entirely. Not suitable for most client environments (data privacy, AI usage policies).
|
||||||
|
numlock-zapnout-pri-startu-initialkeyboa: InitialKeyboardIndicators = 2 in Default profile. Ensures NumLock is enabled when Windows starts. Standard expectation for users working with numeric data - prevents confusion on data entry.
|
||||||
|
accent-barva-na-titulnich-listech-colorp: ColorPrevalence = 1 in Personalize key. Shows the X9.cz accent color (#223B47) on window title bars and borders. Gives all windows a consistent branded appearance.
|
||||||
|
onedrive-runonce-klic-je-tady-smazat: REMOVED. The RunOnce key deletion and Explorer namespace CLSID removal were deleted - those registry tweaks prevented a freshly installed OneDrive (e.g. for M365) from launching. OneDrive AppX uninstall in step 01 is intentional; blocking re-launch is not.
|
||||||
|
explorer-showrecent-0-showfrequent-0: ShowRecent=0 and ShowFrequent=0 in HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer. Hides Recent files and Frequent folders from Quick Access. Privacy improvement and cleaner File Explorer on fresh deployments.
|
||||||
|
explorer-fullpath-1-cabinetstate: FullPath=1 in HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState. Displays the full directory path (e.g. C:\Users\jan\Documents\Projekty) in the File Explorer title bar instead of just the folder name.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile,
|
||||||
|
[ValidateSet("default","admin","user")]
|
||||||
|
[string]$ProfileType = "default"
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Feature {
|
||||||
|
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
|
||||||
|
try {
|
||||||
|
if ($null -eq $Cfg) { return $Default }
|
||||||
|
$stepFeatures = $Cfg.features.$StepID
|
||||||
|
if ($null -eq $stepFeatures) { return $Default }
|
||||||
|
$val = $stepFeatures.$FeatureID
|
||||||
|
if ($null -eq $val) { return $Default }
|
||||||
|
return [bool]$val
|
||||||
|
} catch { return $Default }
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Helper - apply a registry setting to both Default hive and current HKCU
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
function Grant-HiveWriteAccess {
|
||||||
|
param([string]$HivePath) # full path e.g. "Registry::HKU\DefaultProfile\Software\..."
|
||||||
|
# Grants Administrators FullControl on a loaded hive key with restricted ACL.
|
||||||
|
try {
|
||||||
|
$acl = Get-Acl -Path $HivePath -ErrorAction Stop
|
||||||
|
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
|
||||||
|
"BUILTIN\Administrators",
|
||||||
|
[System.Security.AccessControl.RegistryRights]::FullControl,
|
||||||
|
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
|
||||||
|
[System.Security.AccessControl.PropagationFlags]::None,
|
||||||
|
[System.Security.AccessControl.AccessControlType]::Allow
|
||||||
|
)
|
||||||
|
$acl.SetAccessRule($rule)
|
||||||
|
Set-Acl -Path $HivePath -AclObject $acl -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Grant-HiveWriteAccess failed for $HivePath - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-ProfileReg {
|
||||||
|
param(
|
||||||
|
[string]$SubKey, # relative to HKCU (e.g. "Software\Microsoft\...")
|
||||||
|
[string]$Name,
|
||||||
|
$Value,
|
||||||
|
[string]$Type = "DWord"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply to loaded Default hive
|
||||||
|
$defPath = "Registry::HKU\DefaultProfile\$SubKey"
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $defPath)) {
|
||||||
|
New-Item -Path $defPath -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Retry after granting write access to parent key
|
||||||
|
try {
|
||||||
|
$parentPath = $defPath -replace '\\[^\\]+$', ''
|
||||||
|
if (Test-Path $parentPath) { Grant-HiveWriteAccess -HivePath $parentPath }
|
||||||
|
if (-not (Test-Path $defPath)) {
|
||||||
|
New-Item -Path $defPath -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " DEFAULT HIVE failed $SubKey\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply to current user as well
|
||||||
|
$hkcuPath = "HKCU:\$SubKey"
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $hkcuPath)) {
|
||||||
|
New-Item -Path $hkcuPath -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $hkcuPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
Write-Log " SET $SubKey\$Name = $Value" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " HKCU failed $SubKey\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-ProfileReg {
|
||||||
|
param([string]$SubKey, [string]$Name)
|
||||||
|
|
||||||
|
$defPath = "Registry::HKU\DefaultProfile\$SubKey"
|
||||||
|
try {
|
||||||
|
if (Test-Path $defPath) {
|
||||||
|
Remove-ItemProperty -Path $defPath -Name $Name -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
$hkcuPath = "HKCU:\$SubKey"
|
||||||
|
try {
|
||||||
|
if (Test-Path $hkcuPath) {
|
||||||
|
Remove-ItemProperty -Path $hkcuPath -Name $Name -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " REMOVED $SubKey\$Name" -Level OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED removing $SubKey\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Load Default profile hive
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$hivePath = "C:\Users\Default\NTUSER.DAT"
|
||||||
|
$hiveKey = "DefaultProfile"
|
||||||
|
|
||||||
|
Write-Log "Loading Default hive: $hivePath" -Level INFO
|
||||||
|
|
||||||
|
# Unload first in case previous run left it mounted
|
||||||
|
& reg unload "HKU\$hiveKey" 2>&1 | Out-Null
|
||||||
|
|
||||||
|
$loadResult = & reg load "HKU\$hiveKey" $hivePath 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to load Default hive: $loadResult" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Log "Default hive loaded" -Level OK
|
||||||
|
|
||||||
|
try {
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Taskbar tweaks (alignment, buttons, tray, layout XML)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "defaultProfile" "taskbarTweaks") {
|
||||||
|
Write-Log "Applying taskbar tweaks" -Level STEP
|
||||||
|
|
||||||
|
$tbPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
|
||||||
|
|
||||||
|
# Win11: align taskbar to left (0 = left, 1 = center)
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0
|
||||||
|
|
||||||
|
# Hide Search box / button - Win10/11 (0 = hidden, 1 = icon, 2 = full box)
|
||||||
|
# Note: Win11 uses Search subkey, Win10 uses Explorer\Advanced - set both
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Search" `
|
||||||
|
-Name "SearchboxTaskbarMode" -Value 0
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0
|
||||||
|
|
||||||
|
# Hide Task View button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "ShowTaskViewButton" -Value 0
|
||||||
|
|
||||||
|
# Hide Widgets button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "TaskbarDa" -Value 0
|
||||||
|
|
||||||
|
# Hide Chat / Teams button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "TaskbarMn" -Value 0
|
||||||
|
|
||||||
|
# Hide Copilot button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "ShowCopilotButton" -Value 0
|
||||||
|
|
||||||
|
# Show all tray icons
|
||||||
|
# EnableAutoTray = 0 works on Win10; Win11 ignores it but set anyway
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
|
-Name "EnableAutoTray" -Value 0
|
||||||
|
|
||||||
|
# Win11 workaround: clear cached tray icon streams so all icons appear on next login
|
||||||
|
$trayNotifyKey = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $trayNotifyKey) {
|
||||||
|
Remove-ItemProperty -Path $trayNotifyKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $trayNotifyKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Cleared TrayNotify icon streams (Win11 systray workaround)" -Level OK
|
||||||
|
}
|
||||||
|
$defTrayKey = "Registry::HKU\DefaultProfile\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $defTrayKey) {
|
||||||
|
Remove-ItemProperty -Path $defTrayKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $defTrayKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Cleared TrayNotify icon streams in Default hive" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# Desktop icons - show This PC
|
||||||
|
# CLSID {20D04FE0-3AEA-1069-A2D8-08002B30309D} = This PC / Computer
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" `
|
||||||
|
-Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0
|
||||||
|
|
||||||
|
# Taskbar pinned apps layout (Win10/11)
|
||||||
|
# ProfileType: default = empty, admin = Explorer+PS+Edge, user = Explorer+Edge
|
||||||
|
# Note: TaskbarLayoutModification.xml locks the taskbar temporarily.
|
||||||
|
# UnlockStartLayout scheduled task removes the lock 5 min after first boot
|
||||||
|
# so users can then customize pins freely.
|
||||||
|
# Win11 24H2+ may require ProvisionedLayoutModification.xml format instead.
|
||||||
|
Write-Log " Writing taskbar layout (ProfileType=$ProfileType)" -Level INFO
|
||||||
|
|
||||||
|
$taskbarLayoutDir = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell"
|
||||||
|
if (-not (Test-Path $taskbarLayoutDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $taskbarLayoutDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$pinList = switch ($ProfileType) {
|
||||||
|
"admin" {
|
||||||
|
@'
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\File Explorer.lnk"/>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\Windows PowerShell\Windows PowerShell.lnk"/>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/>
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
"user" {
|
||||||
|
@'
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\File Explorer.lnk"/>
|
||||||
|
<taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/>
|
||||||
|
'@
|
||||||
|
}
|
||||||
|
default { "" } # empty = clean slate
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskbarLayoutXml = @"
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LayoutModificationTemplate
|
||||||
|
xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
|
||||||
|
xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
|
||||||
|
xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
|
||||||
|
xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
|
||||||
|
Version="1">
|
||||||
|
<CustomTaskbarLayoutCollection PinListPlacement="Replace">
|
||||||
|
<defaultlayout:TaskbarLayout>
|
||||||
|
<taskbar:TaskbarPinList>
|
||||||
|
$pinList
|
||||||
|
</taskbar:TaskbarPinList>
|
||||||
|
</defaultlayout:TaskbarLayout>
|
||||||
|
</CustomTaskbarLayoutCollection>
|
||||||
|
</LayoutModificationTemplate>
|
||||||
|
"@
|
||||||
|
$taskbarLayoutXml | Set-Content -Path "$taskbarLayoutDir\LayoutModification.xml" -Encoding UTF8 -Force
|
||||||
|
Write-Log " Taskbar LayoutModification.xml written (profile: $ProfileType)" -Level OK
|
||||||
|
|
||||||
|
# NumLock on startup
|
||||||
|
Set-ProfileReg -SubKey "Control Panel\Keyboard" `
|
||||||
|
-Name "InitialKeyboardIndicators" -Value 2 -Type "String"
|
||||||
|
|
||||||
|
# Accent color on title bars
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorPrevalence" -Value 1
|
||||||
|
} else {
|
||||||
|
Write-Log "taskbarTweaks feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Start menu tweaks (pins, Bing, Copilot, GameDVR)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "defaultProfile" "startMenuTweaks") {
|
||||||
|
Write-Log "Applying Start menu tweaks" -Level STEP
|
||||||
|
|
||||||
|
# Disable Bing search suggestions in Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\Explorer" `
|
||||||
|
-Name "DisableSearchBoxSuggestions" -Value 1
|
||||||
|
|
||||||
|
# Win11: empty Start menu pins
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" `
|
||||||
|
-Name "ConfigureStartPins" `
|
||||||
|
-Value '{"pinnedList":[]}' `
|
||||||
|
-Type "String"
|
||||||
|
|
||||||
|
# Hide "Recently added" apps in Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "Start_TrackProgs" -Value 0
|
||||||
|
|
||||||
|
# Hide recently opened files/docs from Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "Start_TrackDocs" -Value 0
|
||||||
|
|
||||||
|
# Disable Copilot
|
||||||
|
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\WindowsCopilot" `
|
||||||
|
-Name "TurnOffWindowsCopilot" -Value 1
|
||||||
|
|
||||||
|
# Disable GameDVR
|
||||||
|
Set-ProfileReg -SubKey "System\GameConfigStore" `
|
||||||
|
-Name "GameDVR_Enabled" -Value 0
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\GameDVR" `
|
||||||
|
-Name "AppCaptureEnabled" -Value 0
|
||||||
|
} else {
|
||||||
|
Write-Log "startMenuTweaks feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Explorer tweaks (file extensions, LaunchTo, ShowRecent, FullPath)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (Get-Feature $Config "defaultProfile" "explorerTweaks") {
|
||||||
|
Write-Log "Applying Explorer tweaks" -Level STEP
|
||||||
|
|
||||||
|
$advPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
|
||||||
|
|
||||||
|
# Show file extensions in Explorer
|
||||||
|
Set-ProfileReg -SubKey $advPath -Name "HideFileExt" -Value 0
|
||||||
|
|
||||||
|
# Open Explorer to This PC instead of Quick Access
|
||||||
|
Set-ProfileReg -SubKey $advPath -Name "LaunchTo" -Value 1
|
||||||
|
|
||||||
|
# Hide Recent files from Quick Access
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
|
-Name "ShowRecent" -Value 0
|
||||||
|
|
||||||
|
# Hide Frequent folders from Quick Access
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
|
-Name "ShowFrequent" -Value 0
|
||||||
|
|
||||||
|
# Show full path in Explorer title bar
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState" `
|
||||||
|
-Name "FullPath" -Value 1
|
||||||
|
} else {
|
||||||
|
Write-Log "explorerTweaks feature disabled - skipping" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Unload Default hive - always, even on error
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Unloading Default hive" -Level INFO
|
||||||
|
[GC]::Collect()
|
||||||
|
[GC]::WaitForPendingFinalizers()
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
|
||||||
|
$unloadResult = & reg unload "HKU\$hiveKey" 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log "Default hive unloaded" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log "Failed to unload Default hive: $unloadResult" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Restart Explorer to apply taskbar/tray changes to current session
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Restarting Explorer to apply taskbar changes" -Level INFO
|
||||||
|
try {
|
||||||
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Start-Process explorer
|
||||||
|
Write-Log "Explorer restarted" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Explorer restart failed (non-fatal): $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 4 complete" -Level OK
|
||||||
190
scripts/05-personalization.ps1
Normal file
190
scripts/05-personalization.ps1
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets system colors, wallpaper, and visual theme.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Applies X9.cz visual identity: dark taskbar/Start with accent color #223B47
|
||||||
|
(deep blue-gray), light app mode, no transparency. Wallpaper is set to a solid
|
||||||
|
color matching the accent. BackInfo.exe (Step 07) overwrites the wallpaper with
|
||||||
|
a live system info BMP on every logon - solid color is only the fallback.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
system-tema-taskbar-start-dark: SystemUsesLightTheme=0 in Themes\Personalize. Dark mode for shell (taskbar, Start menu, Action Center, notification area). Does NOT affect application windows - those stay light. Reduces eye strain in dim environments.
|
||||||
|
aplikacni-tema-light: AppsUseLightTheme=1. Application windows (File Explorer, Settings, Calculator, etc.) use white/light backgrounds. Majority of business applications (Office, browsers) also respect this and show light mode.
|
||||||
|
accent-barva-223b47-tmave-modroseda: AccentColor DWORD = 0xFF473B22 (stored as ABGR: A=FF, B=47, G=3B, R=22). The deep blue-gray #223B47 is the X9.cz brand color, also used as the solid wallpaper background.
|
||||||
|
accent-barva-na-start-a-taskbaru-ano: ColorPrevalence=1. Applies accent color to taskbar background and Start menu surface. The taskbar becomes the brand color instead of default black, creating a distinct recognizable look on X9.cz-deployed machines.
|
||||||
|
pruhlednost-vypnuta: EnableTransparency=0. Disables Aero translucency on taskbar and Start. Improves text readability on the taskbar, reduces subtle GPU usage, and looks more professional/consistent on business machines.
|
||||||
|
tapeta-jednobarevna-223b47-bez-obrazku: Wallpaper set to solid color #223B47 via SystemParametersInfo(SPI_SETDESKWALLPAPER). BackInfo.exe generates a BMP with hostname, username, OS, network info and sets it as wallpaper on every logon. Solid color = fallback only.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accent color #223B47 stored as ABGR DWORD: 0xFF473B22
|
||||||
|
# A=FF B=47 G=3B R=22 -> 0xFF473B22 = 4283612962
|
||||||
|
$AccentColorABGR = 0xFF473B22
|
||||||
|
|
||||||
|
# Gradient colors (Windows generates these automatically but we set them explicitly)
|
||||||
|
# AccentPalette is 32 bytes - 8 shades of the accent color (BGRA each)
|
||||||
|
# We use the same color for all shades as a safe default
|
||||||
|
$AccentColorHex = "#223B47"
|
||||||
|
|
||||||
|
function Set-Reg {
|
||||||
|
param([string]$Path, [string]$Name, $Value, [string]$Type = "DWord")
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null }
|
||||||
|
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force
|
||||||
|
Write-Log " SET $Path\$Name = $Value" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED $Path\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Apply-ThemeSettings {
|
||||||
|
param([string]$HiveRoot) # "HKCU:" or "Registry::HKU\DefaultProfile"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# System theme - Dark (taskbar, Start, action center)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "SystemUsesLightTheme" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# App theme - Light
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "AppsUseLightTheme" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Accent color on Start and taskbar
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "ColorPrevalence" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Transparency effects - disabled
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "EnableTransparency" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Accent color
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "AccentColor" -Value $AccentColorABGR -Type "DWord"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorizationColor" -Value $AccentColorABGR -Type "DWord"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorizationAfterglow" -Value $AccentColorABGR -Type "DWord"
|
||||||
|
|
||||||
|
# Accent color on title bars and borders
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorPrevalence" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Wallpaper - solid color #223B47 (fallback before DesktopInfo runs)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Background color as decimal RGB
|
||||||
|
Set-Reg -Path "$HiveRoot\Control Panel\Colors" `
|
||||||
|
-Name "Background" -Value "34 59 71" -Type "String"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
||||||
|
-Name "WallpaperStyle" -Value "0" -Type "String"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
||||||
|
-Name "TileWallpaper" -Value "0" -Type "String"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Desktop icons - show This PC
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" `
|
||||||
|
-Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Load Default hive
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$hivePath = "C:\Users\Default\NTUSER.DAT"
|
||||||
|
$hiveKey = "DefaultProfile"
|
||||||
|
|
||||||
|
Write-Log "Loading Default hive for personalization" -Level INFO
|
||||||
|
|
||||||
|
& reg unload "HKU\$hiveKey" 2>&1 | Out-Null
|
||||||
|
$loadResult = & reg load "HKU\$hiveKey" $hivePath 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to load Default hive: $loadResult" -Level ERROR
|
||||||
|
Write-Log "Applying personalization to current user only" -Level WARN
|
||||||
|
|
||||||
|
Write-Log "Applying theme to current user (HKCU)" -Level STEP
|
||||||
|
Apply-ThemeSettings -HiveRoot "HKCU:"
|
||||||
|
|
||||||
|
# Set wallpaper via SystemParametersInfo for current user
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class WallpaperHelper {
|
||||||
|
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||||
|
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
[WallpaperHelper]::SystemParametersInfo(20, 0, "", 3) | Out-Null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Log "Applying theme to Default hive" -Level STEP
|
||||||
|
Apply-ThemeSettings -HiveRoot "Registry::HKU\DefaultProfile"
|
||||||
|
|
||||||
|
Write-Log "Applying theme to current user (HKCU)" -Level STEP
|
||||||
|
Apply-ThemeSettings -HiveRoot "HKCU:"
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
[GC]::Collect()
|
||||||
|
[GC]::WaitForPendingFinalizers()
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
|
||||||
|
$unloadResult = & reg unload "HKU\$hiveKey" 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log "Default hive unloaded" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log "Failed to unload Default hive: $unloadResult" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Apply wallpaper (solid color) to current desktop session
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Setting desktop wallpaper to solid color" -Level INFO
|
||||||
|
|
||||||
|
try {
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class WallpaperHelper {
|
||||||
|
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||||
|
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
|
||||||
|
# Empty string = solid color defined in Control Panel\Colors\Background
|
||||||
|
[WallpaperHelper]::SystemParametersInfo(20, 0, "", 3) | Out-Null
|
||||||
|
Write-Log " Desktop wallpaper updated" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to update wallpaper: $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 5 complete" -Level OK
|
||||||
205
scripts/06-scheduled-tasks.ps1
Normal file
205
scripts/06-scheduled-tasks.ps1
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Registers logon scheduled tasks to maintain per-user settings that Windows resets.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Creates scheduled tasks under Task Scheduler that run at user logon (and optionally
|
||||||
|
on a timer) to enforce settings that Windows tends to revert. Tasks are registered
|
||||||
|
in the Default profile task store so new user accounts inherit them automatically.
|
||||||
|
Note: PDF-DefaultApp task has been removed - PDF default is set once during deployment.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
showalltrayicons-pri-logonu-kazdou-1-min: Task 'ShowAllTrayIcons': runs at logon, repeats every 1 minute. Sets HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\EnableAutoTray=0. Windows 11 re-enables auto-hiding of tray icons after updates and sometimes after logon - the 1-min repeat ensures permanent override.
|
||||||
|
unlockstartlayout-jednou-po-aplikaci-lay: Task 'UnlockStartLayout': runs once, 30 seconds after logon. Clears the Start menu layout lock bit that is set when ConfigureStartPins is applied. Without this, users cannot pin or unpin apps from Start after deployment.
|
||||||
|
pdf-defaultapp-pri-kazdem-logonu: REMOVED. PDF default is set once during deployment (step 02) with UCPD service stopped. The scheduled task is no longer needed.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
$ScriptDir = "C:\Windows\Setup\Scripts"
|
||||||
|
if (-not (Test-Path $ScriptDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Register-Task {
|
||||||
|
param(
|
||||||
|
[string]$TaskName,
|
||||||
|
[string]$Description,
|
||||||
|
[object]$Action,
|
||||||
|
[object[]]$Triggers,
|
||||||
|
[string]$RunLevel = "Highest"
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
# Remove existing task with same name
|
||||||
|
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-StartWhenAvailable
|
||||||
|
|
||||||
|
$principal = New-ScheduledTaskPrincipal -GroupId "Users" `
|
||||||
|
-RunLevel $RunLevel
|
||||||
|
|
||||||
|
$task = New-ScheduledTask -Action $Action `
|
||||||
|
-Trigger $Triggers `
|
||||||
|
-Settings $settings `
|
||||||
|
-Principal $principal `
|
||||||
|
-Description $Description
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName $TaskName -InputObject $task -Force | Out-Null
|
||||||
|
Write-Log " Registered task: $TaskName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to register task $TaskName - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Task: ShowAllTrayIcons
|
||||||
|
# Runs on logon: clears TrayNotify icon cache and restarts Explorer so all
|
||||||
|
# tray icons are visible on first login (Win10: EnableAutoTray=0, Win11: cache clear)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: ShowAllTrayIcons" -Level STEP
|
||||||
|
|
||||||
|
$showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1"
|
||||||
|
@'
|
||||||
|
# Win10: disable auto-hiding of tray icons
|
||||||
|
$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer"
|
||||||
|
Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Win11: clear icon stream cache so all icons become visible after Explorer restart
|
||||||
|
$trayPath = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $trayPath) {
|
||||||
|
Remove-ItemProperty -Path $trayPath -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $trayPath -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart Explorer to apply changes
|
||||||
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Milliseconds 1500
|
||||||
|
if (-not (Get-Process explorer -ErrorAction SilentlyContinue)) {
|
||||||
|
Start-Process explorer
|
||||||
|
}
|
||||||
|
'@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
|
$showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`""
|
||||||
|
$showTrayTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
|
||||||
|
Register-Task -TaskName "ShowAllTrayIcons" `
|
||||||
|
-Description "Show all system tray icons for current user" `
|
||||||
|
-Action $showTrayAction `
|
||||||
|
-Triggers $showTrayTrigger
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: PDF-DefaultApp" -Level STEP
|
||||||
|
|
||||||
|
$pdfScript = "$ScriptDir\PDF-DefaultApp.ps1"
|
||||||
|
@'
|
||||||
|
# Restore .pdf -> Adobe Reader HKCR association (system-wide).
|
||||||
|
# Runs as SYSTEM so it can write to HKCR regardless of Edge updates.
|
||||||
|
# Note: HKCU UserChoice requires Windows Hash validation and cannot be
|
||||||
|
# set reliably via registry; HKCR provides the system-wide fallback.
|
||||||
|
$acroPaths = @(
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe"
|
||||||
|
)
|
||||||
|
$acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
|
if (-not $acroExe) { exit 0 }
|
||||||
|
|
||||||
|
$progId = "AcroExch.Document.DC"
|
||||||
|
$openCmd = "`"$acroExe`" `"%1`""
|
||||||
|
|
||||||
|
# HKCR\.pdf
|
||||||
|
if (-not (Test-Path "HKCR:\.pdf")) { New-Item -Path "HKCR:\.pdf" -Force | Out-Null }
|
||||||
|
$current = (Get-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -ErrorAction SilentlyContinue)."(Default)"
|
||||||
|
if ($current -ne $progId) {
|
||||||
|
Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# HKCR\AcroExch.Document.DC\shell\open\command
|
||||||
|
$cmdPath = "HKCR:\$progId\shell\open\command"
|
||||||
|
if (-not (Test-Path $cmdPath)) { New-Item -Path $cmdPath -Force | Out-Null }
|
||||||
|
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd -Force
|
||||||
|
'@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
|
$pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`""
|
||||||
|
$pdfTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
|
||||||
|
# Runs as SYSTEM to allow HKCR writes (system-wide file association)
|
||||||
|
$pdfPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
|
$pdfSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-StartWhenAvailable
|
||||||
|
$pdfTask = New-ScheduledTask -Action $pdfAction `
|
||||||
|
-Trigger $pdfTrigger `
|
||||||
|
-Settings $pdfSettings `
|
||||||
|
-Principal $pdfPrincipal `
|
||||||
|
-Description "Restore Adobe Reader as default PDF app on logon"
|
||||||
|
try {
|
||||||
|
Unregister-ScheduledTask -TaskName "PDF-DefaultApp" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Register-ScheduledTask -TaskName "PDF-DefaultApp" -InputObject $pdfTask -Force | Out-Null
|
||||||
|
Write-Log " Registered task: PDF-DefaultApp" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to register task PDF-DefaultApp - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Task: UnlockStartLayout
|
||||||
|
# Runs once after deployment to unlock the Start menu layout
|
||||||
|
# so users can still customize it later
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: UnlockStartLayout" -Level STEP
|
||||||
|
|
||||||
|
$unlockScript = "$ScriptDir\UnlockStartLayout.ps1"
|
||||||
|
@'
|
||||||
|
# Remove Start layout lock so users can modify it
|
||||||
|
$layoutXml = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml"
|
||||||
|
if (Test-Path $layoutXml) {
|
||||||
|
Remove-Item $layoutXml -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Unregister self after running once
|
||||||
|
Unregister-ScheduledTask -TaskName "UnlockStartLayout" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
'@ | Set-Content -Path $unlockScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
|
$unlockAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$unlockScript`""
|
||||||
|
# Trigger: 5 minutes after system startup, once
|
||||||
|
$unlockTrigger = New-ScheduledTaskTrigger -AtStartup
|
||||||
|
$unlockTrigger.Delay = "PT5M"
|
||||||
|
|
||||||
|
$unlockPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
|
$unlockSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 10) `
|
||||||
|
-StartWhenAvailable
|
||||||
|
$unlockTask = New-ScheduledTask -Action $unlockAction `
|
||||||
|
-Trigger $unlockTrigger `
|
||||||
|
-Settings $unlockSettings `
|
||||||
|
-Principal $unlockPrincipal `
|
||||||
|
-Description "Unlock Start menu layout 5 min after first boot"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Unregister-ScheduledTask -TaskName "UnlockStartLayout" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Register-ScheduledTask -TaskName "UnlockStartLayout" -InputObject $unlockTask -Force | Out-Null
|
||||||
|
Write-Log " Registered task: UnlockStartLayout" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to register task UnlockStartLayout - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 6 complete" -Level OK
|
||||||
126
scripts/07-backinfo.ps1
Normal file
126
scripts/07-backinfo.ps1
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Deploys BackInfo.exe to C:\Program Files\Backinfo\ and configures auto-start.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Copies the BackInfo folder from assets to Program Files, writes the OS name
|
||||||
|
to the registry (HKLM\SOFTWARE\BackInfo\OSName) so BackInfo can display it,
|
||||||
|
and creates a Startup shortcut so BackInfo launches on every user logon.
|
||||||
|
BackInfo renders a BMP wallpaper overlay with hostname, username, OS, HW info,
|
||||||
|
and network info - configured via BackInfo.ini.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
kopirovat-assets-backinfo-do-program-fil: Copies all files from assets\Backinfo\ to C:\Program Files\Backinfo\. Includes BackInfo.exe, BackInfo.ini, and backinfo_W11.ps1. Creates the target directory if it does not exist.
|
||||||
|
registry-osname-hklm-software-backinfo: Detects Windows build number and edition, writes OSName string to HKLM\SOFTWARE\BackInfo\OSName (and WOW6432Node). BackInfo.ini references %OSName% to display the correct OS on the wallpaper.
|
||||||
|
startup-shortcut-backinfo-exe: Creates a shortcut at C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\BackInfo.lnk pointing to C:\Program Files\Backinfo\BackInfo.exe. Ensures BackInfo starts for every user on logon.
|
||||||
|
07-desktop-info-ps1-smazat-nahrazeno: 07-desktop-info.ps1 is superseded by this script. BackInfo.exe is the preferred approach - stable on Win10 and Win11, configurable via INI, already present in assets.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copy BackInfo assets to Program Files
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$assetsBackinfo = Join-Path $PSScriptRoot "..\assets\Backinfo"
|
||||||
|
$destBackinfo = "C:\Program Files\Backinfo"
|
||||||
|
|
||||||
|
Write-Log "Deploying BackInfo to $destBackinfo" -Level INFO
|
||||||
|
|
||||||
|
if (-not (Test-Path $assetsBackinfo)) {
|
||||||
|
Write-Log " Assets not found: $assetsBackinfo" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $destBackinfo)) {
|
||||||
|
New-Item -ItemType Directory -Path $destBackinfo -Force | Out-Null
|
||||||
|
}
|
||||||
|
Copy-Item -Path "$assetsBackinfo\*" -Destination $destBackinfo -Recurse -Force
|
||||||
|
Write-Log " Copied BackInfo assets to $destBackinfo" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to copy BackInfo assets: $_" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Detect OS name and write to registry (BackInfo reads this via %OSName%)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Detecting OS for BackInfo registry" -Level INFO
|
||||||
|
|
||||||
|
try {
|
||||||
|
$cvPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
|
||||||
|
$cv = Get-ItemProperty -Path $cvPath
|
||||||
|
$build = [int]$cv.CurrentBuild
|
||||||
|
|
||||||
|
$osBase = if ($build -ge 22000) { "Windows 11" } else { "Windows 10" }
|
||||||
|
|
||||||
|
$edition = switch ($cv.EditionID) {
|
||||||
|
"Professional" { "Pro" }
|
||||||
|
"ProfessionalN" { "Pro N" }
|
||||||
|
"Core" { "Home" }
|
||||||
|
"CoreN" { "Home N" }
|
||||||
|
"Enterprise" { "Enterprise" }
|
||||||
|
"Education" { "Education" }
|
||||||
|
default { $cv.EditionID }
|
||||||
|
}
|
||||||
|
|
||||||
|
$osName = "$osBase $edition"
|
||||||
|
|
||||||
|
foreach ($regPath in @("HKLM:\SOFTWARE\BackInfo", "HKLM:\SOFTWARE\WOW6432Node\BackInfo")) {
|
||||||
|
if (-not (Test-Path $regPath)) {
|
||||||
|
New-Item -Path $regPath -Force | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $regPath -Name "OSName" -Value $osName -Type String -Force
|
||||||
|
}
|
||||||
|
Write-Log " OSName set to: $osName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set BackInfo registry: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Create Startup shortcut for all users
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Creating BackInfo startup shortcut" -Level INFO
|
||||||
|
|
||||||
|
try {
|
||||||
|
$backInfoExe = "$destBackinfo\BackInfo.exe"
|
||||||
|
$shortcutPath = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\BackInfo.lnk"
|
||||||
|
|
||||||
|
$wsh = New-Object -ComObject WScript.Shell
|
||||||
|
$shortcut = $wsh.CreateShortcut($shortcutPath)
|
||||||
|
$shortcut.TargetPath = $backInfoExe
|
||||||
|
$shortcut.WorkingDirectory = $destBackinfo
|
||||||
|
$shortcut.Description = "BackInfo system info wallpaper"
|
||||||
|
$shortcut.Save()
|
||||||
|
|
||||||
|
Write-Log " Startup shortcut created: $shortcutPath" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to create startup shortcut: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Launch BackInfo now to render initial wallpaper
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Launching BackInfo for initial render" -Level INFO
|
||||||
|
try {
|
||||||
|
Start-Process -FilePath "$destBackinfo\BackInfo.exe" -ErrorAction Stop
|
||||||
|
Write-Log " BackInfo launched" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " BackInfo launch failed (non-fatal): $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 7 complete" -Level OK
|
||||||
252
scripts/07-desktop-info.ps1
Normal file
252
scripts/07-desktop-info.ps1
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
DEPRECATED - delete this script. Replaced by BackInfo.exe.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Original custom PowerShell approach to render system info onto the desktop wallpaper
|
||||||
|
using WPF (System.Windows.Media / System.Drawing). Superseded by BackInfo.exe which
|
||||||
|
is already present in assets/Backinfo/ and handles Win10/Win11 natively.
|
||||||
|
ACTION REQUIRED: Delete this file. Add a BackInfo deployment step to the master script.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
07-desktop-info-ps1-smazat-stary-pristup: DELETE THIS FILE. The WPF rendering approach had compatibility issues on some Windows editions and required maintaining complex PS rendering code. BackInfo.exe is a mature, stable replacement already bundled in assets/Backinfo/.
|
||||||
|
zkopirovat-assets-backinfo-do-c-program-: NEW STEP (in master script): Copy assets/Backinfo/ to C:\Program Files\Backinfo\ on the target machine. Includes BackInfo.exe, BackInfo.ini (display config), and backinfo_W11.ps1 (setup helper).
|
||||||
|
spustit-backinfo-w11-ps1-detekce-os-regi: Run backinfo_W11.ps1 after file copy. Detects Win10 vs Win11, writes the required registry key for wallpaper rendering compatibility, and creates a Startup shortcut in the All Users Startup folder.
|
||||||
|
backinfo-exe-v-assets-backinfo-k-dispozi: BackInfo.exe reads BackInfo.ini on each run. INI configures: font size and family, position of each info block, which data sources to show (hostname, username, OS version, CPU, RAM, disk, IP address, domain).
|
||||||
|
backinfo-auto-start-pri-kazdem-logonu-vi: The Startup shortcut created by backinfo_W11.ps1 ensures BackInfo.exe runs on every user logon. It re-reads live system data each time, so the wallpaper BMP always shows current information (username changes, IP changes, etc.).
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
$ScriptDir = "C:\Windows\Setup\Scripts"
|
||||||
|
$RenderScript = "$ScriptDir\DesktopInfo-Render.ps1"
|
||||||
|
$BmpPath = "$ScriptDir\desktopinfo.bmp"
|
||||||
|
|
||||||
|
if (-not (Test-Path $ScriptDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Write the rendering script (runs on every logon as the user)
|
||||||
|
# Layout: hostname (large bold, centered), then detail lines centered
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO
|
||||||
|
|
||||||
|
$renderContent = @'
|
||||||
|
# DesktopInfo-Render.ps1
|
||||||
|
# Collects system info and renders it centered on the desktop wallpaper.
|
||||||
|
# Runs on every user logon via Scheduled Task.
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
$LogFile = "C:\Windows\Setup\Scripts\desktopinfo.log"
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message" -Encoding UTF8
|
||||||
|
}
|
||||||
|
Write-Log "DesktopInfo render started" -Level INFO
|
||||||
|
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class WallpaperApi {
|
||||||
|
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||||
|
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Collect system info
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Collecting system info"
|
||||||
|
$hostname = $env:COMPUTERNAME
|
||||||
|
$userDomain = $env:USERDOMAIN
|
||||||
|
$userName = $env:USERNAME
|
||||||
|
$loggedUser = if ($userDomain -and $userDomain -ne $hostname) { "$userDomain\$userName" } else { "$hostname\$userName" }
|
||||||
|
|
||||||
|
$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
|
||||||
|
$osName = if ($osInfo) { $osInfo.Caption -replace "^Microsoft\s*", "" } else { "Windows" }
|
||||||
|
$ramGB = if ($osInfo) { [math]::Round($osInfo.TotalVisibleMemorySize / 1024 / 1024, 1) } else { "?" }
|
||||||
|
|
||||||
|
$cpuInfo = Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
$cpuCount = if ($cpuInfo) { $cpuInfo.NumberOfLogicalProcessors } else { "?" }
|
||||||
|
$cpuSpeed = if ($cpuInfo) { $cpuInfo.MaxClockSpeed } else { "?" }
|
||||||
|
|
||||||
|
$ips = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" } |
|
||||||
|
Select-Object -ExpandProperty IPAddress) -join ", "
|
||||||
|
if (-not $ips) { $ips = "N/A" }
|
||||||
|
|
||||||
|
$csInfo = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
|
||||||
|
$domain = if ($csInfo -and $csInfo.PartOfDomain) { $csInfo.Domain } `
|
||||||
|
elseif ($csInfo -and $csInfo.Workgroup) { $csInfo.Workgroup.ToLower() } `
|
||||||
|
else { "N/A" }
|
||||||
|
|
||||||
|
Write-Log "hostname=$hostname user=$loggedUser os=$osName ram=$($ramGB)GB cpu=${cpuCount}x${cpuSpeed}MHz ips=$ips domain=$domain"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Screen dimensions
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
|
||||||
|
$width = if ($screen) { $screen.Bounds.Width } else { 1920 }
|
||||||
|
$height = if ($screen) { $screen.Bounds.Height } else { 1080 }
|
||||||
|
Write-Log "screen=${width}x${height}"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Create bitmap and graphics context
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$bmp = New-Object System.Drawing.Bitmap($width, $height)
|
||||||
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||||
|
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
|
||||||
|
$g.Clear([System.Drawing.ColorTranslator]::FromHtml("#556364"))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Fonts and brushes
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$fontName = "Segoe UI"
|
||||||
|
$fontTitle = New-Object System.Drawing.Font($fontName, 36, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$fontBold = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$fontReg = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Regular)
|
||||||
|
$brushWhite = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
||||||
|
$brushGray = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("#C8D2D2"))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Lines: text, font, brush
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$texts = @(
|
||||||
|
$hostname
|
||||||
|
"Logged on user: $loggedUser"
|
||||||
|
"OS: $osName"
|
||||||
|
"CPU: $cpuCount at $cpuSpeed MHz RAM: $($ramGB)GB"
|
||||||
|
"IPv4 address: $ips Machine domain: $domain"
|
||||||
|
)
|
||||||
|
$fonts = @( $fontTitle, $fontReg, $fontBold, $fontReg, $fontReg )
|
||||||
|
$brushes = @( $brushWhite, $brushGray, $brushGray, $brushGray, $brushGray )
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Measure total block height, then center vertically
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$lineSpacing = 8
|
||||||
|
$heights = @()
|
||||||
|
for ($i = 0; $i -lt $texts.Count; $i++) {
|
||||||
|
$heights += [int]($g.MeasureString($texts[$i], $fonts[$i]).Height)
|
||||||
|
}
|
||||||
|
$totalH = ($heights | Measure-Object -Sum).Sum + $lineSpacing * ($texts.Count - 1)
|
||||||
|
$currentY = [int](($height - $totalH) / 2)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Draw each line centered horizontally
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
for ($i = 0; $i -lt $texts.Count; $i++) {
|
||||||
|
$sz = $g.MeasureString($texts[$i], $fonts[$i])
|
||||||
|
$x = [int](($width - $sz.Width) / 2)
|
||||||
|
$g.DrawString($texts[$i], $fonts[$i], $brushes[$i], [float]$x, [float]$currentY)
|
||||||
|
$currentY += $heights[$i] + $lineSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
$g.Dispose()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Save and set as wallpaper
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$bmpPath = "C:\Windows\Setup\Scripts\desktopinfo.bmp"
|
||||||
|
Write-Log "Saving BMP: $bmpPath"
|
||||||
|
$bmp.Save($bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
|
||||||
|
$bmp.Dispose()
|
||||||
|
|
||||||
|
# Clear Windows wallpaper cache so it reloads from our BMP
|
||||||
|
# Without this, Windows reuses TranscodedWallpaper and ignores the updated file
|
||||||
|
$transcodedPath = "$env:APPDATA\Microsoft\Windows\Themes\TranscodedWallpaper"
|
||||||
|
if (Test-Path $transcodedPath) {
|
||||||
|
Remove-Item $transcodedPath -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log "Cleared TranscodedWallpaper cache"
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
|
||||||
|
$result = [WallpaperApi]::SystemParametersInfo(20, 0, $bmpPath, 3)
|
||||||
|
Write-Log "SystemParametersInfo result: $result"
|
||||||
|
Write-Log "DesktopInfo render complete" -Level INFO
|
||||||
|
'@
|
||||||
|
|
||||||
|
$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force
|
||||||
|
Write-Log "Render script written" -Level OK
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Store deployment date in registry (used for reference)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Storing deployment date in registry" -Level INFO
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path "HKLM:\SOFTWARE\X9\Deployment")) {
|
||||||
|
New-Item -Path "HKLM:\SOFTWARE\X9\Deployment" -Force | Out-Null
|
||||||
|
}
|
||||||
|
$existingDate = (Get-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" `
|
||||||
|
-Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate
|
||||||
|
if (-not $existingDate) {
|
||||||
|
Set-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" `
|
||||||
|
-Name "DeployDate" `
|
||||||
|
-Value (Get-Date -Format "yyyy-MM-dd") `
|
||||||
|
-Force
|
||||||
|
Write-Log " DeployDate set: $(Get-Date -Format 'yyyy-MM-dd')" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " DeployDate already set: $existingDate" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set DeployDate: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Register scheduled task: DesktopInfo
|
||||||
|
# Runs the render script on every user logon
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: DesktopInfo" -Level STEP
|
||||||
|
|
||||||
|
try {
|
||||||
|
Unregister-ScheduledTask -TaskName "DesktopInfo" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`""
|
||||||
|
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
$trigger.Delay = "PT20S" # wait for network to be available
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-StartWhenAvailable
|
||||||
|
$principal = New-ScheduledTaskPrincipal -GroupId "Users" -RunLevel Limited
|
||||||
|
|
||||||
|
$task = New-ScheduledTask -Action $action `
|
||||||
|
-Trigger $trigger `
|
||||||
|
-Settings $settings `
|
||||||
|
-Principal $principal `
|
||||||
|
-Description "Render system info onto desktop wallpaper on logon"
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName "DesktopInfo" -InputObject $task -Force | Out-Null
|
||||||
|
Write-Log "Task DesktopInfo registered" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Failed to register DesktopInfo task: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Run once immediately for current user
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Running DesktopInfo render now for current user" -Level INFO
|
||||||
|
try {
|
||||||
|
& powershell.exe -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File $RenderScript
|
||||||
|
Write-Log "DesktopInfo rendered" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "DesktopInfo render failed: $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 7 complete" -Level OK
|
||||||
146
scripts/08-activation.ps1
Normal file
146
scripts/08-activation.ps1
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Activates Windows using a product key from config or KMS GVLK fallback.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Checks if Windows is already activated (LicenseStatus = 1). If not, reads the
|
||||||
|
product key from config.json activation.productKey. If no key is present, selects
|
||||||
|
the appropriate GVLK for the detected Windows edition and activates via KMS.
|
||||||
|
Optionally configures a specific KMS server if activation.kmsServer is set.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
oa3-bios-uefi-klic-kontrola-embedded-ke: Checks for OA3 embedded product key in BIOS/UEFI firmware via SoftwareLicensingService.OA3xOriginalProductKey WMI query. If a key is found, it is installed via slmgr /ipk and activation is attempted. Most OEM machines (since Win8 OA3) have a digital entitlement key in firmware - this path handles them without requiring a key in config.json.
|
||||||
|
klic-z-config-json-activation-productkey: Reads activation.productKey from config.json. Installs via slmgr.vbs /ipk <key> and activates via slmgr.vbs /ato. Supports MAK (Multiple Activation Key) for volume licensing without KMS, and retail keys. Takes priority over GVLK fallback.
|
||||||
|
fallback-na-gvlk-kms-client-key-dle-edic: When no key is in config, detects Windows edition via (Get-WmiObject SoftwareLicensingProduct).Name and maps to Microsoft's published GVLK table. Pro: W269N-WFGWX-YVC9B-4J6C9-T83GX, Enterprise: NPPR9-FWDCX-D2C8J-H872K-2YT43, Home: TX9XD-98N7V-6WMQ6-BX7FG-H8Q99.
|
||||||
|
volitelny-kms-server-activation-kmsserve: If activation.kmsServer is in config.json, runs slmgr.vbs /skms <server>:<port> before /ato. Used for clients with on-premises KMS infrastructure (common in larger organizations with volume licensing).
|
||||||
|
preskocit-pokud-jiz-aktivovano: Queries Win32_WindowsLicenseStatus or SoftwareLicensingProduct to check LicenseStatus. Value 1 = Licensed (fully activated). Script skips activation attempt and logs "Windows already activated" to avoid unnecessary slmgr calls.
|
||||||
|
typ-klice-mak-vs-kms-vs-retail: Key type selection depends on client's Microsoft licensing: MAK = volume license key activates online against Microsoft (limited activations), KMS = requires KMS server on network (VLSC subscription), Retail = individual license from Microsoft Store or OEM.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# KMS Generic Volume License Keys (GVLK)
|
||||||
|
# Source: https://docs.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys
|
||||||
|
# These are official Microsoft-published keys for use with KMS infrastructure.
|
||||||
|
# Replace with your MAK/retail key for standalone activation.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$KmsKeys = @{
|
||||||
|
# Windows 11
|
||||||
|
"Windows 11 Pro" = "W269N-WFGWX-YVC9B-4J6C9-T83GX"
|
||||||
|
"Windows 11 Pro N" = "MH37W-N47XK-V7XM9-C7227-GCQG9"
|
||||||
|
"Windows 11 Pro Education" = "6TP4R-GNPTD-KYYHQ-7B7DP-J447Y"
|
||||||
|
"Windows 11 Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2"
|
||||||
|
"Windows 11 Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43"
|
||||||
|
# Windows 10
|
||||||
|
"Windows 10 Pro" = "W269N-WFGWX-YVC9B-4J6C9-T83GX"
|
||||||
|
"Windows 10 Pro N" = "MH37W-N47XK-V7XM9-C7227-GCQG9"
|
||||||
|
"Windows 10 Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2"
|
||||||
|
"Windows 10 Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43"
|
||||||
|
"Windows 10 Home" = "TX9XD-98N7V-6WMQ6-BX7FG-H8Q99"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Check for OA3 embedded BIOS/UEFI product key
|
||||||
|
# Most OEM machines since Win8 OA3 have a product key embedded in firmware.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Checking for OA3 embedded BIOS/UEFI product key" -Level INFO
|
||||||
|
|
||||||
|
$oa3Key = (Get-CimInstance -ClassName SoftwareLicensingService -ErrorAction SilentlyContinue).OA3xOriginalProductKey
|
||||||
|
if ($oa3Key -and $oa3Key.Trim() -ne '') {
|
||||||
|
$maskedKey = $oa3Key.Substring(0, [Math]::Min(5, $oa3Key.Length)) + "-XXXXX-XXXXX-XXXXX-XXXXX"
|
||||||
|
Write-Log " OA3 key found in firmware: $maskedKey" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " No OA3 key found in firmware" -Level INFO
|
||||||
|
$oa3Key = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Check current activation status
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Checking Windows activation status" -Level INFO
|
||||||
|
|
||||||
|
$licenseStatus = (Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND Name LIKE 'Windows%'" -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1).LicenseStatus
|
||||||
|
# LicenseStatus: 0=Unlicensed, 1=Licensed, 2=OOBGrace, 3=OOTGrace, 4=NonGenuineGrace, 5=Notification, 6=ExtendedGrace
|
||||||
|
|
||||||
|
if ($licenseStatus -eq 1) {
|
||||||
|
Write-Log " Windows is already activated - skipping" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Activation status: $licenseStatus (not activated)" -Level WARN
|
||||||
|
|
||||||
|
# Detect Windows edition
|
||||||
|
$osCaption = (Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue).Caption
|
||||||
|
Write-Log " Detected OS: $osCaption" -Level INFO
|
||||||
|
|
||||||
|
# Key priority: config.json > OA3 firmware > GVLK
|
||||||
|
$customKey = $null
|
||||||
|
if ($Config -and $Config.activation -and $Config.activation.productKey) {
|
||||||
|
$customKey = $Config.activation.productKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($customKey -and $customKey -ne "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX") {
|
||||||
|
# Use key from config (highest priority)
|
||||||
|
$keyToUse = $customKey
|
||||||
|
Write-Log " Using product key from config" -Level INFO
|
||||||
|
} elseif ($oa3Key) {
|
||||||
|
# Use OA3 key from firmware
|
||||||
|
$keyToUse = $oa3Key
|
||||||
|
Write-Log " Using OA3 key from firmware" -Level INFO
|
||||||
|
} else {
|
||||||
|
# Find matching GVLK key by OS name
|
||||||
|
$keyToUse = $null
|
||||||
|
foreach ($entry in $KmsKeys.GetEnumerator()) {
|
||||||
|
if ($osCaption -like "*$($entry.Key)*") {
|
||||||
|
$keyToUse = $entry.Value
|
||||||
|
Write-Log " Matched GVLK key for: $($entry.Key)" -Level INFO
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $keyToUse) {
|
||||||
|
Write-Log " No matching key found for: $osCaption" -Level WARN
|
||||||
|
Write-Log " Skipping activation - set activation.productKey in config.json" -Level WARN
|
||||||
|
} else {
|
||||||
|
# Install key
|
||||||
|
Write-Log " Installing product key..." -Level INFO
|
||||||
|
$ipkResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ipk $keyToUse 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log " Key installed" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Key install result: $ipkResult" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set KMS server if configured
|
||||||
|
if ($Config -and $Config.activation -and $Config.activation.kmsServer) {
|
||||||
|
$kmsServer = $Config.activation.kmsServer
|
||||||
|
Write-Log " Setting KMS server: $kmsServer" -Level INFO
|
||||||
|
& cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /skms $kmsServer 2>&1 | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attempt activation
|
||||||
|
Write-Log " Attempting activation..." -Level INFO
|
||||||
|
$atoResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ato 2>&1
|
||||||
|
$atoOutput = $atoResult -join " "
|
||||||
|
|
||||||
|
if ($atoOutput -match "successfully" -or $atoOutput -match "uspesn") {
|
||||||
|
Write-Log " Activation successful" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Activation result: $atoOutput" -Level WARN
|
||||||
|
Write-Log " Activation may require a KMS server or valid MAK key" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 8 - Activation complete" -Level OK
|
||||||
137
scripts/09-pc-identity.ps1
Normal file
137
scripts/09-pc-identity.ps1
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets PC identity: computer name, description, and creates C:\X9 folder structure.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Renames the computer if deployment.pcName is set in config.json. Sets the
|
||||||
|
computer description (visible in System properties and network neighborhood).
|
||||||
|
Creates C:\X9\ directory structure with subdirectories for logs, scripts and
|
||||||
|
assets. Copies X9 icon and creates Desktop.ini so the folder shows a custom
|
||||||
|
icon in Explorer. Computer rename requires a restart - this step runs last
|
||||||
|
before the final summary.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
rename-computer-dle-config-deployment-pcn: Renames the computer via Rename-Computer if config.json deployment.pcName is set and differs from the current name. Rename takes effect after restart. If pcName is empty, rename is skipped and the current name is preserved.
|
||||||
|
popis-pocitace-computer-description: Sets the computer description shown in System Properties and Network Neighborhood. Read from config.json deployment.pcDescription, default "X9 deployment". Written to HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters\SrvComment.
|
||||||
|
vytvorit-cx9-adresar: Creates C:\X9\ with subdirectories Logs\, Scripts\, Assets\. Used for deployment logs, custom per-client scripts, and client-specific configuration assets.
|
||||||
|
cx9-vlastni-ikonka-desktop-ini: Copies X9-ikona.ico to C:\X9\ and creates Desktop.ini with IconResource entry. Sets System+Hidden attributes on Desktop.ini and ReadOnly on C:\X9\ so Explorer displays the custom folder icon.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# C:\X9 directory structure
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Creating C:\X9 directory structure" -Level INFO
|
||||||
|
|
||||||
|
$x9Root = "C:\X9"
|
||||||
|
$x9Dirs = @("$x9Root\Logs", "$x9Root\Scripts", "$x9Root\Assets")
|
||||||
|
|
||||||
|
foreach ($dir in $x9Dirs) {
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $dir)) {
|
||||||
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||||
|
}
|
||||||
|
Write-Log " Dir: $dir" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to create $dir - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copy X9 icon and create Desktop.ini for custom folder appearance
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$assetsLogo = Join-Path $PSScriptRoot "..\assets\Logo"
|
||||||
|
$icoSrc = Get-ChildItem -Path $assetsLogo -Filter "*.ico" -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1
|
||||||
|
|
||||||
|
if ($icoSrc) {
|
||||||
|
$icoDest = "$x9Root\X9-ikona.ico"
|
||||||
|
try {
|
||||||
|
Copy-Item -Path $icoSrc.FullName -Destination $icoDest -Force
|
||||||
|
Write-Log " Copied icon: $icoDest" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to copy icon: $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
$desktopIni = "$x9Root\desktop.ini"
|
||||||
|
try {
|
||||||
|
@"
|
||||||
|
[.ShellClassInfo]
|
||||||
|
IconResource=X9-ikona.ico,0
|
||||||
|
[ViewState]
|
||||||
|
Mode=
|
||||||
|
Vid=
|
||||||
|
FolderType=Generic
|
||||||
|
"@ | Set-Content -Path $desktopIni -Encoding Unicode -Force
|
||||||
|
|
||||||
|
# desktop.ini must be System+Hidden; folder must be ReadOnly for Explorer to show the icon
|
||||||
|
(Get-Item $desktopIni -Force).Attributes = "System,Hidden"
|
||||||
|
(Get-Item $x9Root).Attributes = "ReadOnly,Directory"
|
||||||
|
Write-Log " Desktop.ini created for custom folder icon" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to create desktop.ini: $_" -Level WARN
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log " No .ico found in assets\Logo - custom folder icon skipped" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Computer description
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$pcDesc = "X9 deployment"
|
||||||
|
if ($Config -and $Config.deployment -and $Config.deployment.pcDescription) {
|
||||||
|
$pcDesc = $Config.deployment.pcDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Setting computer description: $pcDesc" -Level INFO
|
||||||
|
try {
|
||||||
|
Set-ItemProperty `
|
||||||
|
-Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" `
|
||||||
|
-Name "SrvComment" -Value $pcDesc -Type String -Force
|
||||||
|
Write-Log " Computer description set" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set computer description: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Rename computer (must be last - requires restart to take effect)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$pcName = $null
|
||||||
|
if ($Config -and $Config.deployment -and $Config.deployment.pcName) {
|
||||||
|
$pcName = $Config.deployment.pcName.Trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pcName -and $pcName -ne "") {
|
||||||
|
$currentName = $env:COMPUTERNAME
|
||||||
|
if ($currentName -eq $pcName) {
|
||||||
|
Write-Log "Computer name already '$pcName' - no rename needed" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log "Renaming computer: '$currentName' -> '$pcName'" -Level INFO
|
||||||
|
try {
|
||||||
|
Rename-Computer -NewName $pcName -Force -ErrorAction Stop
|
||||||
|
Write-Log " Computer renamed to '$pcName' (restart required)" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to rename computer: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log "No pcName in config - computer rename skipped" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 9 complete" -Level OK
|
||||||
111
scripts/10-network.ps1
Normal file
111
scripts/10-network.ps1
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets network profile to Private, enables ping, and enables Network Discovery.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Sets all connected network adapter profiles from Public to Private. Private
|
||||||
|
profile enables file sharing, network discovery, and other LAN features.
|
||||||
|
Enables ICMP echo (ping) via Windows Firewall for diagnostic purposes.
|
||||||
|
Enables the Network Discovery firewall rule group for the Private profile
|
||||||
|
so this PC is visible to other computers on the local network.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
nastavit-sitovy-profil-private: Sets all connected network profiles to Private via Set-NetConnectionProfile. Public profile blocks most LAN features. Private is required for file sharing, printer sharing, and network discovery. Applied to all currently connected adapters.
|
||||||
|
povolit-ping-icmp-firewall: Enables "File and Printer Sharing (Echo Request)" firewall rules for ICMPv4 and ICMPv6. ICMP echo is disabled by default on clean Windows. Required for network diagnostics, monitoring tools, and basic connectivity verification.
|
||||||
|
zapnout-network-discovery: Enables the Network Discovery firewall rule group (FPS-NB_Name-In-UDP, LLMNR, etc.) for Private and Domain profiles via Set-NetFirewallRule. Allows this PC to appear in Network Neighborhood and browse other machines.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Set network profiles to Private
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Setting network profiles to Private" -Level INFO
|
||||||
|
|
||||||
|
try {
|
||||||
|
$profiles = Get-NetConnectionProfile -ErrorAction Stop
|
||||||
|
foreach ($profile in $profiles) {
|
||||||
|
if ($profile.NetworkCategory -ne "Private") {
|
||||||
|
Set-NetConnectionProfile -InterfaceIndex $profile.InterfaceIndex `
|
||||||
|
-NetworkCategory Private -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " $($profile.Name): Public -> Private" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " $($profile.Name): already Private" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set network profiles: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Enable ICMP echo (ping) - ICMPv4 and ICMPv6
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Enabling ICMP echo (ping)" -Level INFO
|
||||||
|
|
||||||
|
$icmpRules = @(
|
||||||
|
"FPS-ICMP4-ERQ-In", # File and Printer Sharing (Echo Request - ICMPv4-In)
|
||||||
|
"FPS-ICMP6-ERQ-In", # File and Printer Sharing (Echo Request - ICMPv6-In)
|
||||||
|
"CoreNet-ICMP4-DU-In",
|
||||||
|
"CoreNet-ICMP6-DU-In"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($rule in $icmpRules) {
|
||||||
|
try {
|
||||||
|
$r = Get-NetFirewallRule -Name $rule -ErrorAction SilentlyContinue
|
||||||
|
if ($r) {
|
||||||
|
Enable-NetFirewallRule -Name $rule -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Enabled: $rule" -Level OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Rule not found or error: $rule - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Also enable by display name for robustness across Windows versions
|
||||||
|
try {
|
||||||
|
Get-NetFirewallRule -DisplayGroup "File and Printer Sharing" -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.DisplayName -like "*Echo*" } |
|
||||||
|
Enable-NetFirewallRule -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Enabled File and Printer Sharing Echo rules" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Could not enable Echo rules via DisplayGroup: $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Enable Network Discovery firewall rules
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Enabling Network Discovery" -Level INFO
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Enable all Network Discovery rules for Private profile
|
||||||
|
Get-NetFirewallRule -DisplayGroup "Network Discovery" -ErrorAction Stop |
|
||||||
|
Where-Object { $_.Profile -match "Private|Any" } |
|
||||||
|
Enable-NetFirewallRule -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Network Discovery rules enabled (Private)" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to enable Network Discovery rules: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable via netsh as fallback (covers older Windows builds)
|
||||||
|
$netshResult = & netsh advfirewall firewall set rule group="Network Discovery" new enable=Yes 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log " Network Discovery enabled via netsh" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " netsh Network Discovery: $netshResult" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 10 complete" -Level OK
|
||||||
152
scripts/11-dell-update.ps1
Normal file
152
scripts/11-dell-update.ps1
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Detects Dell hardware, installs Dell Command | Update, and applies all available updates.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Checks Win32_ComputerSystem.Manufacturer - if not Dell, the step exits silently without
|
||||||
|
error so the same deployment script works on any hardware. On Dell machines, installs
|
||||||
|
Dell Command | Update (Universal) via winget and immediately runs /applyUpdates with
|
||||||
|
-reboot=disable. This covers drivers, firmware, and BIOS. BIOS and firmware updates are
|
||||||
|
staged at this point and finalize automatically during the restart that closes the
|
||||||
|
deployment. The operator does not need to run a separate update pass after setup.
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
detekce-dell-hw-win32-computersystem: Reads Win32_ComputerSystem.Manufacturer. If the string does not contain "Dell", the entire step is skipped without error. The deployment continues normally on HP, Lenovo, or any other brand.
|
||||||
|
instalace-dell-command-update-via-winget: Installs Dell.CommandUpdate.Universal silently via winget. This is the current DCU generation (v5+) that supports Latitude, OptiPlex, Precision, Vostro, and XPS on Win10 and Win11.
|
||||||
|
spusteni-vsech-aktualizaci-drivery-firmware-bios: Runs dcu-cli.exe /applyUpdates -silent -reboot=disable. Covers driver, firmware, and BIOS categories in a single pass. The -reboot=disable flag prevents DCU from rebooting mid-deployment.
|
||||||
|
bios-firmware-staging-reboot: BIOS and firmware updates are staged by DCU and finalize on the next system restart. The deployment already ends with a restart (step 09 - computer rename), so no extra reboot is needed.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
switch ($Level) {
|
||||||
|
"OK" { Write-Host $line -ForegroundColor Green }
|
||||||
|
"ERROR" { Write-Host $line -ForegroundColor Red }
|
||||||
|
"WARN" { Write-Host $line -ForegroundColor Yellow }
|
||||||
|
"STEP" { Write-Host $line -ForegroundColor Cyan }
|
||||||
|
default { Write-Host $line }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Feature {
|
||||||
|
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
|
||||||
|
try {
|
||||||
|
if ($null -eq $Cfg) { return $Default }
|
||||||
|
$stepFeatures = $Cfg.features.$StepID
|
||||||
|
if ($null -eq $stepFeatures) { return $Default }
|
||||||
|
$val = $stepFeatures.$FeatureID
|
||||||
|
if ($null -eq $val) { return $Default }
|
||||||
|
return [bool]$val
|
||||||
|
} catch { return $Default }
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Detect Dell hardware
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Checking hardware manufacturer" -Level INFO
|
||||||
|
|
||||||
|
try {
|
||||||
|
$cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
|
||||||
|
$manufacturer = $cs.Manufacturer
|
||||||
|
$model = $cs.Model
|
||||||
|
Write-Log " Manufacturer: $manufacturer Model: $model" -Level INFO
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to query Win32_ComputerSystem: $_" -Level ERROR
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($manufacturer -notmatch "Dell") {
|
||||||
|
Write-Log "Not a Dell machine ($manufacturer) - step skipped" -Level WARN
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Dell confirmed: $model" -Level OK
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Install Dell Command | Update via winget
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Installing Dell Command | Update (Universal)..." -Level STEP
|
||||||
|
|
||||||
|
$wingetArgs = @(
|
||||||
|
"install",
|
||||||
|
"--id", "Dell.CommandUpdate.Universal",
|
||||||
|
"--silent",
|
||||||
|
"--accept-package-agreements",
|
||||||
|
"--accept-source-agreements"
|
||||||
|
)
|
||||||
|
|
||||||
|
$wingetOutput = & winget @wingetArgs 2>&1
|
||||||
|
$wingetExit = $LASTEXITCODE
|
||||||
|
$wingetOutput | ForEach-Object { Write-Log " [winget] $_" -Level INFO }
|
||||||
|
|
||||||
|
if ($wingetExit -ne 0 -and $wingetExit -ne 1638) { # 1638 = already installed
|
||||||
|
Write-Log " winget exit code $wingetExit - checking if DCU is already present" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
# Locate dcu-cli.exe (path is the same for x64 and Universal edition)
|
||||||
|
$dcuCandidates = @(
|
||||||
|
"C:\Program Files\Dell\CommandUpdate\dcu-cli.exe",
|
||||||
|
"C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe"
|
||||||
|
)
|
||||||
|
$dcuCli = $dcuCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $dcuCli) {
|
||||||
|
Write-Log " dcu-cli.exe not found - cannot run updates" -Level ERROR
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log " dcu-cli.exe found: $dcuCli" -Level OK
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Run updates - categories controlled by feature flags
|
||||||
|
# -reboot=disable -> no mid-deployment reboot; BIOS/firmware staged for next restart
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$runDrivers = Get-Feature $Config "dellUpdate" "drivers"
|
||||||
|
$runBios = Get-Feature $Config "dellUpdate" "bios"
|
||||||
|
|
||||||
|
if (-not $runDrivers -and -not $runBios) {
|
||||||
|
Write-Log "Both drivers and bios features disabled - skipping update run" -Level INFO
|
||||||
|
} else {
|
||||||
|
# Build update type list from enabled features
|
||||||
|
$updateTypes = @()
|
||||||
|
if ($runDrivers) {
|
||||||
|
$updateTypes += "driver"
|
||||||
|
$updateTypes += "firmware"
|
||||||
|
}
|
||||||
|
if ($runBios) {
|
||||||
|
$updateTypes += "bios"
|
||||||
|
}
|
||||||
|
$updateTypeArg = $updateTypes -join ","
|
||||||
|
|
||||||
|
Write-Log "Running Dell Command | Update (updateType=$updateTypeArg, no auto-reboot)..." -Level STEP
|
||||||
|
Write-Log " This may take several minutes depending on available updates" -Level INFO
|
||||||
|
|
||||||
|
$dcuOutput = & $dcuCli /applyUpdates -silent -reboot=disable "-updateType=$updateTypeArg" 2>&1
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
$dcuOutput | ForEach-Object { Write-Log " [DCU] $_" -Level INFO }
|
||||||
|
|
||||||
|
Write-Log " DCU exit code: $exitCode" -Level INFO
|
||||||
|
|
||||||
|
# Dell Command | Update exit codes:
|
||||||
|
# 0 = completed, no updates required or updates applied (no reboot needed)
|
||||||
|
# 1 = updates applied, reboot required to finalize BIOS/firmware
|
||||||
|
# 5 = no applicable updates found for this system
|
||||||
|
# others = error or partial failure
|
||||||
|
switch ($exitCode) {
|
||||||
|
0 { Write-Log "Dell Command | Update: complete (no reboot required)" -Level OK }
|
||||||
|
1 { Write-Log "Dell Command | Update: updates staged - BIOS/firmware will finalize on restart" -Level OK }
|
||||||
|
5 { Write-Log "Dell Command | Update: no applicable updates for this model" -Level OK }
|
||||||
|
default { Write-Log "Dell Command | Update: exit code $exitCode - review DCU log in C:\ProgramData\Dell\UpdateService\Logs" -Level WARN }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 11 complete" -Level OK
|
||||||
157
setup.ps1
Normal file
157
setup.ps1
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
# setup.ps1 - Claude Code bootstrap for Windows
|
||||||
|
# Usage:
|
||||||
|
# irm https://gist.githubusercontent.com/YOUR_GIST_URL/raw/setup.ps1 | iex
|
||||||
|
#
|
||||||
|
# Or with parameters (paste as one line):
|
||||||
|
# $env:CC_API_KEY="sk-ant-..."; $env:CC_REPO="https://github.com/org/repo"; irm https://gist.../raw/setup.ps1 | iex
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
|
function Write-Step { param([string]$Msg) Write-Host "[SETUP] $Msg" -ForegroundColor Cyan }
|
||||||
|
function Write-OK { param([string]$Msg) Write-Host " OK: $Msg" -ForegroundColor Green }
|
||||||
|
function Write-Fail { param([string]$Msg) Write-Host " ERR: $Msg" -ForegroundColor Red; exit 1 }
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Claude Code Bootstrap - X9.cz" -ForegroundColor Cyan
|
||||||
|
Write-Host " ==============================" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# API KEY
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
$apiKey = $env:CC_API_KEY
|
||||||
|
if (-not $apiKey) {
|
||||||
|
$apiKey = Read-Host "Enter Anthropic API key (sk-ant-...)"
|
||||||
|
}
|
||||||
|
if (-not $apiKey -or -not $apiKey.StartsWith("sk-")) {
|
||||||
|
Write-Fail "Invalid API key"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# REPO URL
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
$repoUrl = $env:CC_REPO
|
||||||
|
if (-not $repoUrl) {
|
||||||
|
$repoUrl = Read-Host "Enter repo URL (https://github.com/org/repo)"
|
||||||
|
}
|
||||||
|
if (-not $repoUrl) {
|
||||||
|
Write-Fail "No repo URL provided"
|
||||||
|
}
|
||||||
|
|
||||||
|
$workDir = if ($env:CC_WORKDIR) { $env:CC_WORKDIR } else { "$HOME\Projects" }
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# NODE.JS
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Checking Node.js..."
|
||||||
|
|
||||||
|
$nodeOk = $false
|
||||||
|
try {
|
||||||
|
$nodeVer = & node --version 2>$null
|
||||||
|
if ($nodeVer -match 'v(\d+)' -and [int]$Matches[1] -ge 18) {
|
||||||
|
Write-OK "Node.js $nodeVer"
|
||||||
|
$nodeOk = $true
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (-not $nodeOk) {
|
||||||
|
Write-Step "Installing Node.js via winget..."
|
||||||
|
try {
|
||||||
|
winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements --silent
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
|
||||||
|
[System.Environment]::GetEnvironmentVariable("Path","User")
|
||||||
|
Write-OK "Node.js installed"
|
||||||
|
} catch {
|
||||||
|
Write-Fail "Node.js install failed. Install manually: https://nodejs.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# GIT
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Checking Git..."
|
||||||
|
|
||||||
|
$gitOk = $false
|
||||||
|
try { git --version | Out-Null; $gitOk = $true; Write-OK "Git available" } catch {}
|
||||||
|
|
||||||
|
if (-not $gitOk) {
|
||||||
|
Write-Step "Installing Git via winget..."
|
||||||
|
try {
|
||||||
|
winget install Git.Git --accept-package-agreements --accept-source-agreements --silent
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" +
|
||||||
|
[System.Environment]::GetEnvironmentVariable("Path","User")
|
||||||
|
Write-OK "Git installed"
|
||||||
|
} catch {
|
||||||
|
Write-Fail "Git install failed. Install manually: https://git-scm.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# CLAUDE CODE
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Checking Claude Code..."
|
||||||
|
|
||||||
|
$ccOk = $false
|
||||||
|
try { $ccVer = claude --version 2>$null; Write-OK "Claude Code $ccVer"; $ccOk = $true } catch {}
|
||||||
|
|
||||||
|
if (-not $ccOk) {
|
||||||
|
Write-Step "Installing Claude Code..."
|
||||||
|
try {
|
||||||
|
npm install -g @anthropic-ai/claude-code
|
||||||
|
Write-OK "Claude Code installed"
|
||||||
|
} catch {
|
||||||
|
Write-Fail "Claude Code install failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# API KEY - ulozit
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Saving API key..."
|
||||||
|
[System.Environment]::SetEnvironmentVariable("ANTHROPIC_API_KEY", $apiKey, "User")
|
||||||
|
$env:ANTHROPIC_API_KEY = $apiKey
|
||||||
|
# Vymazat z env aby nezustal v historii procesu
|
||||||
|
Remove-Item Env:\CC_API_KEY -ErrorAction SilentlyContinue
|
||||||
|
Write-OK "API key saved"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# CLONE / PULL REPO
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Step "Setting up repository..."
|
||||||
|
New-Item -ItemType Directory -Path $workDir -Force | Out-Null
|
||||||
|
|
||||||
|
$repoName = ($repoUrl -split '/')[-1] -replace '\.git$', ''
|
||||||
|
$targetPath = Join-Path $workDir $repoName
|
||||||
|
|
||||||
|
if (Test-Path (Join-Path $targetPath ".git")) {
|
||||||
|
Write-Step "Repo exists, pulling latest..."
|
||||||
|
Push-Location $targetPath
|
||||||
|
git pull
|
||||||
|
Pop-Location
|
||||||
|
} else {
|
||||||
|
git clone $repoUrl $targetPath
|
||||||
|
Write-OK "Cloned to $targetPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# CLEAN PS HISTORY - odstranit radky s API key
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
$histPath = (Get-PSReadlineOption).HistorySavePath
|
||||||
|
if ($histPath -and (Test-Path $histPath)) {
|
||||||
|
$clean = Get-Content $histPath | Where-Object { $_ -notmatch 'sk-ant-|CC_API_KEY|ANTHROPIC' }
|
||||||
|
$clean | Set-Content $histPath
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# LAUNCH
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " ==============================" -ForegroundColor Green
|
||||||
|
Write-Host " Ready! Repo: $targetPath" -ForegroundColor Green
|
||||||
|
Write-Host " ==============================" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Set-Location $targetPath
|
||||||
|
claude
|
||||||
214
setup.sh
Executable file
214
setup.sh
Executable file
|
|
@ -0,0 +1,214 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# setup.sh - Claude Code bootstrap for macOS / Linux
|
||||||
|
# Usage:
|
||||||
|
# curl -fsSL https://gist.githubusercontent.com/YOUR_GIST_URL/raw/setup.sh | bash
|
||||||
|
#
|
||||||
|
# Or with parameters:
|
||||||
|
# CC_API_KEY="sk-ant-..." CC_REPO="https://github.com/org/repo" bash <(curl -fsSL https://gist.../raw/setup.sh)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GRAY='\033[0;90m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
step() { echo -e "\n${CYAN}[SETUP] $1${NC}"; }
|
||||||
|
ok() { echo -e " ${GREEN}OK: $1${NC}"; }
|
||||||
|
fail() { echo -e " ${RED}ERR: $1${NC}"; exit 1; }
|
||||||
|
skip() { echo -e " ${GRAY}SKIP: $1${NC}"; }
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${CYAN} Claude Code Bootstrap - X9.cz"
|
||||||
|
echo -e " ==============================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# API KEY
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
API_KEY="${CC_API_KEY:-}"
|
||||||
|
if [ -z "$API_KEY" ]; then
|
||||||
|
read -rsp "Enter Anthropic API key (sk-ant-...): " API_KEY
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
if [[ ! "$API_KEY" == sk-* ]]; then
|
||||||
|
fail "Invalid API key"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# REPO URL
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
REPO_URL="${CC_REPO:-}"
|
||||||
|
if [ -z "$REPO_URL" ]; then
|
||||||
|
read -rp "Enter repo URL (https://github.com/org/repo): " REPO_URL
|
||||||
|
fi
|
||||||
|
if [ -z "$REPO_URL" ]; then
|
||||||
|
fail "No repo URL provided"
|
||||||
|
fi
|
||||||
|
|
||||||
|
WORK_DIR="${CC_WORKDIR:-$HOME/Projects}"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# DETECT OS
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
OS="$(uname -s)"
|
||||||
|
step "Detected OS: $OS"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# NODE.JS
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
step "Checking Node.js..."
|
||||||
|
|
||||||
|
node_ok=false
|
||||||
|
if command -v node &>/dev/null; then
|
||||||
|
NODE_VER=$(node --version | sed 's/v//' | cut -d. -f1)
|
||||||
|
if [ "$NODE_VER" -ge 18 ]; then
|
||||||
|
ok "Node.js $(node --version)"
|
||||||
|
node_ok=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$node_ok" = false ]; then
|
||||||
|
step "Installing Node.js..."
|
||||||
|
case "$OS" in
|
||||||
|
Darwin)
|
||||||
|
if command -v brew &>/dev/null; then
|
||||||
|
brew install node
|
||||||
|
else
|
||||||
|
step "Installing Homebrew first..."
|
||||||
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
brew install node
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
Linux)
|
||||||
|
# Node via NodeSource
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
|
||||||
|
if command -v apt-get &>/dev/null; then
|
||||||
|
sudo apt-get install -y nodejs
|
||||||
|
elif command -v dnf &>/dev/null; then
|
||||||
|
sudo dnf install -y nodejs
|
||||||
|
elif command -v yum &>/dev/null; then
|
||||||
|
sudo yum install -y nodejs
|
||||||
|
else
|
||||||
|
fail "Cannot detect package manager. Install Node.js manually: https://nodejs.org"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
fail "Unsupported OS: $OS. Install Node.js manually: https://nodejs.org"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
ok "Node.js installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# GIT
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
step "Checking Git..."
|
||||||
|
|
||||||
|
if command -v git &>/dev/null; then
|
||||||
|
ok "Git available"
|
||||||
|
else
|
||||||
|
step "Installing Git..."
|
||||||
|
case "$OS" in
|
||||||
|
Darwin) brew install git ;;
|
||||||
|
Linux)
|
||||||
|
if command -v apt-get &>/dev/null; then sudo apt-get install -y git
|
||||||
|
elif command -v dnf &>/dev/null; then sudo dnf install -y git
|
||||||
|
elif command -v yum &>/dev/null; then sudo yum install -y git
|
||||||
|
else fail "Cannot install Git automatically"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
ok "Git installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# CLAUDE CODE
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
step "Checking Claude Code..."
|
||||||
|
|
||||||
|
if command -v claude &>/dev/null; then
|
||||||
|
ok "Claude Code $(claude --version)"
|
||||||
|
else
|
||||||
|
step "Installing Claude Code..."
|
||||||
|
npm install -g @anthropic-ai/claude-code
|
||||||
|
ok "Claude Code installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# API KEY - ulozit
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
step "Saving API key..."
|
||||||
|
|
||||||
|
SHELL_RC=""
|
||||||
|
case "$SHELL" in
|
||||||
|
*/zsh) SHELL_RC="$HOME/.zshrc" ;;
|
||||||
|
*/bash)
|
||||||
|
if [ "$OS" = "Darwin" ]; then
|
||||||
|
SHELL_RC="$HOME/.bash_profile"
|
||||||
|
else
|
||||||
|
SHELL_RC="$HOME/.bashrc"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) SHELL_RC="$HOME/.profile" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Odstrante stary zaznam pokud existuje
|
||||||
|
if [ -f "$SHELL_RC" ]; then
|
||||||
|
grep -v 'ANTHROPIC_API_KEY' "$SHELL_RC" > "${SHELL_RC}.tmp" && mv "${SHELL_RC}.tmp" "$SHELL_RC"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "export ANTHROPIC_API_KEY=\"$API_KEY\"" >> "$SHELL_RC"
|
||||||
|
export ANTHROPIC_API_KEY="$API_KEY"
|
||||||
|
unset CC_API_KEY
|
||||||
|
ok "API key saved to $SHELL_RC"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# CLONE / PULL REPO
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
step "Setting up repository..."
|
||||||
|
mkdir -p "$WORK_DIR"
|
||||||
|
|
||||||
|
REPO_NAME=$(basename "$REPO_URL" .git)
|
||||||
|
TARGET_PATH="$WORK_DIR/$REPO_NAME"
|
||||||
|
|
||||||
|
if [ -d "$TARGET_PATH/.git" ]; then
|
||||||
|
step "Repo exists, pulling latest..."
|
||||||
|
git -C "$TARGET_PATH" pull
|
||||||
|
else
|
||||||
|
git clone "$REPO_URL" "$TARGET_PATH"
|
||||||
|
ok "Cloned to $TARGET_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# CLEAN SHELL HISTORY - odstranit radky s API key
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
HIST_FILE="${HISTFILE:-$HOME/.bash_history}"
|
||||||
|
if [ -f "$HIST_FILE" ]; then
|
||||||
|
grep -v 'sk-ant-\|CC_API_KEY\|ANTHROPIC' "$HIST_FILE" > "${HIST_FILE}.tmp" && mv "${HIST_FILE}.tmp" "$HIST_FILE"
|
||||||
|
fi
|
||||||
|
# zsh history
|
||||||
|
ZSH_HIST="$HOME/.zsh_history"
|
||||||
|
if [ -f "$ZSH_HIST" ]; then
|
||||||
|
grep -v 'sk-ant-\|CC_API_KEY\|ANTHROPIC' "$ZSH_HIST" > "${ZSH_HIST}.tmp" && mv "${ZSH_HIST}.tmp" "$ZSH_HIST"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# LAUNCH
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN} =============================="
|
||||||
|
echo -e " Ready! Repo: $TARGET_PATH"
|
||||||
|
echo -e " ==============================${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " Run: ${CYAN}source $SHELL_RC && cd $TARGET_PATH && claude${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Pokud je skript spusten primo (ne pres pipe), rovnou spustit
|
||||||
|
if [ -t 0 ]; then
|
||||||
|
cd "$TARGET_PATH"
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$SHELL_RC"
|
||||||
|
claude
|
||||||
|
fi
|
||||||
265
tests/Test-Deployment.ps1
Normal file
265
tests/Test-Deployment.ps1
Normal file
|
|
@ -0,0 +1,265 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
|
||||||
|
# Post-deployment verification script.
|
||||||
|
# Checks that all deployment steps completed correctly.
|
||||||
|
# Outputs a pass/fail report.
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
$PassCount = 0
|
||||||
|
$FailCount = 0
|
||||||
|
$WarnCount = 0
|
||||||
|
|
||||||
|
function Test-Check {
|
||||||
|
param(
|
||||||
|
[string]$Name,
|
||||||
|
[scriptblock]$Check,
|
||||||
|
[switch]$WarnOnly
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
$result = & $Check
|
||||||
|
if ($result) {
|
||||||
|
Write-Host " [PASS] $Name" -ForegroundColor Green
|
||||||
|
$script:PassCount++
|
||||||
|
} else {
|
||||||
|
if ($WarnOnly) {
|
||||||
|
Write-Host " [WARN] $Name" -ForegroundColor Yellow
|
||||||
|
$script:WarnCount++
|
||||||
|
} else {
|
||||||
|
Write-Host " [FAIL] $Name" -ForegroundColor Red
|
||||||
|
$script:FailCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host " [FAIL] $Name (exception: $_)" -ForegroundColor Red
|
||||||
|
$script:FailCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-RegValue {
|
||||||
|
param([string]$Path, [string]$Name)
|
||||||
|
try {
|
||||||
|
return (Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop).$Name
|
||||||
|
}
|
||||||
|
catch { return $null }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================"
|
||||||
|
Write-Host " Deployment Verification"
|
||||||
|
Write-Host " Computer: $env:COMPUTERNAME"
|
||||||
|
Write-Host " Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
|
||||||
|
Write-Host "========================================"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Log file
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Log ---"
|
||||||
|
Test-Check "Deploy.log exists" {
|
||||||
|
Test-Path "C:\Windows\Setup\Scripts\Deploy.log"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Admin account
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Admin account ---"
|
||||||
|
Test-Check "Account adminx9 exists" {
|
||||||
|
Get-LocalUser -Name "adminx9" -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
Test-Check "Account adminx9 is enabled" {
|
||||||
|
(Get-LocalUser -Name "adminx9" -ErrorAction SilentlyContinue).Enabled -eq $true
|
||||||
|
}
|
||||||
|
Test-Check "Account adminx9 in Administrators" {
|
||||||
|
$adminsGroup = (Get-LocalGroup | Where-Object { $_.SID -eq "S-1-5-32-544" }).Name
|
||||||
|
Get-LocalGroupMember -Group $adminsGroup -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Name -like "*adminx9" }
|
||||||
|
}
|
||||||
|
Test-Check "Account adminx9 hidden from login screen" {
|
||||||
|
$specialPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
|
||||||
|
(Get-ItemProperty -Path $specialPath -Name "adminx9" -ErrorAction SilentlyContinue).adminx9 -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Activation
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Activation ---"
|
||||||
|
Test-Check "Windows activated" {
|
||||||
|
$status = (Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND Name LIKE 'Windows%'" -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1).LicenseStatus
|
||||||
|
$status -eq 1
|
||||||
|
} -WarnOnly
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Software
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Software ---"
|
||||||
|
Test-Check "7-Zip installed" {
|
||||||
|
(Get-AppxPackage -Name "7zip.7zip" -ErrorAction SilentlyContinue) -or
|
||||||
|
(Test-Path "${env:ProgramFiles}\7-Zip\7z.exe") -or
|
||||||
|
(Test-Path "${env:ProgramFiles(x86)}\7-Zip\7z.exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Adobe Acrobat installed" {
|
||||||
|
(Test-Path "$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe") -or
|
||||||
|
(Test-Path "${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe") -or
|
||||||
|
(Test-Path "${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe") -or
|
||||||
|
(Test-Path "$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "OpenVPN Connect installed" {
|
||||||
|
(Test-Path "$env:ProgramFiles\OpenVPN Connect\OpenVPNConnect.exe") -or
|
||||||
|
(Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" `
|
||||||
|
-ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "OpenVPN*" })
|
||||||
|
} -WarnOnly
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Bloatware
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Bloatware removal ---"
|
||||||
|
|
||||||
|
$bloatwareToCheck = @(
|
||||||
|
"Microsoft.549981C3F5F10" # Cortana
|
||||||
|
"Microsoft.BingNews"
|
||||||
|
"MicrosoftTeams"
|
||||||
|
"Microsoft.XboxApp"
|
||||||
|
"Microsoft.YourPhone"
|
||||||
|
"Microsoft.ZuneMusic"
|
||||||
|
"Microsoft.GamingApp"
|
||||||
|
)
|
||||||
|
|
||||||
|
foreach ($pkg in $bloatwareToCheck) {
|
||||||
|
Test-Check "Removed: $pkg" {
|
||||||
|
$installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue
|
||||||
|
-not $installed
|
||||||
|
} -WarnOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Calculator kept" {
|
||||||
|
Get-AppxPackage -Name "Microsoft.WindowsCalculator" -AllUsers -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# System registry (HKLM)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- System registry ---"
|
||||||
|
|
||||||
|
Test-Check "BypassNRO set" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" "BypassNRO") -eq 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Teams auto-install disabled" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" "ConfigureChatAutoInstall") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Widgets disabled" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Dsh" "AllowNewsAndInterests") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Edge First Run hidden" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Edge" "HideFirstRunExperience") -eq 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "OneDrive disabled via policy" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive" "DisableFileSyncNGSC") -eq 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "GameDVR disabled" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" "AllowGameDVR") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Time zone set" {
|
||||||
|
(Get-TimeZone).Id -eq "Central Europe Standard Time"
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Deployment date in registry" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\X9\Deployment" "DeployDate") -ne $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Current user (HKCU) - personalization
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- User settings (current user) ---"
|
||||||
|
|
||||||
|
Test-Check "Dark system theme" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" "SystemUsesLightTheme") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Light app theme" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" "AppsUseLightTheme") -eq 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Transparency disabled" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" "EnableTransparency") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Taskbar aligned left" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "TaskbarAl") -eq 0
|
||||||
|
} -WarnOnly
|
||||||
|
|
||||||
|
Test-Check "File extensions visible" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "HideFileExt") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "Explorer opens to This PC" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "LaunchTo") -eq 1
|
||||||
|
}
|
||||||
|
Test-Check "This PC icon on desktop" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" "{20D04FE0-3AEA-1069-A2D8-08002B30309D}") -eq 0
|
||||||
|
}
|
||||||
|
Test-Check "Start menu Recommended section hidden" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" "HideRecommendedSection") -eq 1
|
||||||
|
}
|
||||||
|
Test-Check "Start menu recently added hidden" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "Start_TrackProgs") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Scheduled tasks
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Scheduled tasks ---"
|
||||||
|
|
||||||
|
$tasks = @("ShowAllTrayIcons", "PDF-DefaultApp", "DesktopInfo", "UnlockStartLayout")
|
||||||
|
foreach ($t in $tasks) {
|
||||||
|
Test-Check "Task registered: $t" {
|
||||||
|
Get-ScheduledTask -TaskName $t -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# DesktopInfo
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- DesktopInfo ---"
|
||||||
|
|
||||||
|
Test-Check "Render script exists" {
|
||||||
|
Test-Path "C:\Windows\Setup\Scripts\DesktopInfo-Render.ps1"
|
||||||
|
}
|
||||||
|
|
||||||
|
Test-Check "BMP file exists" {
|
||||||
|
Test-Path "C:\Windows\Setup\Scripts\desktopinfo.bmp"
|
||||||
|
} -WarnOnly
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "========================================"
|
||||||
|
Write-Host " PASS: $PassCount FAIL: $FailCount WARN: $WarnCount"
|
||||||
|
Write-Host "========================================"
|
||||||
|
|
||||||
|
if ($FailCount -gt 0) {
|
||||||
|
Write-Host "Deployment verification FAILED. Review items above." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
Write-Host "Deployment verification PASSED." -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
87
tools/extract-docs.py
Normal file
87
tools/extract-docs.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
extract-docs.py - Extract documentation from PS script headers and generate web/data/descriptions.json
|
||||||
|
|
||||||
|
Usage: python3 tools/extract-docs.py
|
||||||
|
Run from repo root. Reads scripts/*.ps1, writes web/data/descriptions.json.
|
||||||
|
|
||||||
|
PS script header format:
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
One line description
|
||||||
|
.DESCRIPTION
|
||||||
|
Multi-line description
|
||||||
|
.ITEMS
|
||||||
|
slug-of-spec-row: Description of what this item does
|
||||||
|
another-slug: Another description
|
||||||
|
#>
|
||||||
|
"""
|
||||||
|
import os, re, json
|
||||||
|
|
||||||
|
SCRIPTS_DIR = os.path.join(os.path.dirname(__file__), '..', 'scripts')
|
||||||
|
OUTPUT_FILE = os.path.join(os.path.dirname(__file__), '..', 'web', 'data', 'descriptions.json')
|
||||||
|
|
||||||
|
def parse_script(path):
|
||||||
|
with open(path, encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Extract <# ... #> block
|
||||||
|
m = re.search(r'<#(.*?)#>', content, re.DOTALL)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
|
||||||
|
block = m.group(1)
|
||||||
|
|
||||||
|
def extract_section(name):
|
||||||
|
pattern = r'\.' + name + r'\s*\n(.*?)(?=\n\s*\.[A-Z]|\Z)'
|
||||||
|
sm = re.search(pattern, block, re.DOTALL)
|
||||||
|
if not sm:
|
||||||
|
return ''
|
||||||
|
return re.sub(r'\n[ \t]+', '\n', sm.group(1)).strip()
|
||||||
|
|
||||||
|
synopsis = extract_section('SYNOPSIS').replace('\n', ' ').strip()
|
||||||
|
description = extract_section('DESCRIPTION').strip()
|
||||||
|
items_raw = extract_section('ITEMS')
|
||||||
|
|
||||||
|
items = {}
|
||||||
|
for line in items_raw.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or ':' not in line:
|
||||||
|
continue
|
||||||
|
slug, _, desc = line.partition(':')
|
||||||
|
slug = slug.strip()
|
||||||
|
desc = desc.strip()
|
||||||
|
if slug and desc:
|
||||||
|
items[slug] = desc
|
||||||
|
|
||||||
|
return {
|
||||||
|
'synopsis': synopsis,
|
||||||
|
'description': description,
|
||||||
|
'items': items,
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
result = {}
|
||||||
|
for fname in sorted(os.listdir(SCRIPTS_DIR)):
|
||||||
|
if not fname.endswith('.ps1'):
|
||||||
|
continue
|
||||||
|
script_id = fname.replace('.ps1', '')
|
||||||
|
path = os.path.join(SCRIPTS_DIR, fname)
|
||||||
|
parsed = parse_script(path)
|
||||||
|
if parsed:
|
||||||
|
result[script_id] = parsed
|
||||||
|
item_count = len(parsed['items'])
|
||||||
|
print(f'OK {fname}: {item_count} items')
|
||||||
|
else:
|
||||||
|
print(f'--- {fname}: no doc header found')
|
||||||
|
|
||||||
|
with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
print(f'\nWritten: {OUTPUT_FILE}')
|
||||||
|
print(f'Scripts documented: {len(result)}')
|
||||||
|
total_items = sum(len(v["items"]) for v in result.values())
|
||||||
|
print(f'Total item descriptions: {total_items}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
160
web/data/descriptions.json
Normal file
160
web/data/descriptions.json
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
{
|
||||||
|
"00-admin-account": {
|
||||||
|
"synopsis": "Creates the adminx9 local administrator account for MSP use.",
|
||||||
|
"description": "Creates a hidden local administrator account 'adminx9' used by X9.cz technicians\nfor remote management and on-site administration. The account has no password by\ndesign - it is invisible to regular users and only accessible to technicians who\nknow it exists. FullName is set to \"X9.cz s.r.o.\" so it is identifiable in\nsystem tools. Password policy is set so it never expires.",
|
||||||
|
"items": {
|
||||||
|
"vytvorit-lokalni-ucet-adminx9": "Creates the account via [ADSI] WinNT provider. No password by design - the account is hidden from users and used only by MSP technicians for remote administration.",
|
||||||
|
"pridat-do-skupiny-administrators": "Adds adminx9 to the local Administrators group via net localgroup. Required for full system management rights.",
|
||||||
|
"skryt-z-login-obrazovky-specialaccounts-": "Sets HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\SpecialAccounts\\UserList\\adminx9 = 0. Removes the user tile from Windows login and lock screen completely.",
|
||||||
|
"heslo-nevypirsi-uzivatel-nesmeni-heslo": "Sets ADS_UF_DONT_EXPIRE_PASSWD and ADS_UF_PASSWD_CANT_CHANGE flags via ADSI userFlags. The account never locks out or requires password maintenance.",
|
||||||
|
"zadne-heslo-aktualne-nastavovano-z-confi": "Account created with empty password. Previous version used config.json password - removed because plaintext passwords in config files are a security risk.",
|
||||||
|
"fullname-x9-cz-s-r-o-via-adsi": "Sets FullName property via [ADSI] so the account shows as \"X9.cz s.r.o.\" in User Accounts panel, Event Viewer, and audit logs."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"01-bloatware": {
|
||||||
|
"synopsis": "Removes pre-installed bloatware: AppX packages, Capabilities, and Optional Features.",
|
||||||
|
"description": "Removes Microsoft-bundled apps and features not needed in a business MSP deployment.\nRemoval is done for all users (-AllUsers) and from the provisioning store so new\nusers do not get them either. Calculator is intentionally kept.",
|
||||||
|
"items": {
|
||||||
|
"appx-balicky-odstraneni-pro-vsechny-uziv": "Uses Remove-AppxPackage -AllUsers and Remove-AppxProvisionedPackage. The provisioned removal prevents apps from reinstalling for new user profiles. Covers ~35 apps including Cortana, Copilot, Teams personal, Xbox, Skype, News, Weather, Maps.",
|
||||||
|
"zachovano-microsoft-windowscalculator": "Calculator is explicitly excluded. Lightweight utility frequently used by technicians and end users. Removing it would require manual reinstall from Store.",
|
||||||
|
"windows-capabilities-fax-ie-openssh-wmp-": "Removed via Remove-WindowsCapability: Fax & Scan, Internet Explorer mode, OpenSSH client, Windows Media Player (legacy), WordPad, Handwriting recognition, Steps Recorder, Math Input Panel, Quick Assist.",
|
||||||
|
"windows-optional-features-ps-2-0-mediapl": "Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"02-software": {
|
||||||
|
"synopsis": "Installs standard business software via winget, sets Adobe PDF default, and installs Atera RMM agent.",
|
||||||
|
"description": "Uses winget to install the standard X9.cz MSP software bundle. Checks winget\navailability before running. Each install is logged. After Adobe Acrobat Reader,\ntemporarily stops the UCPD driver (User Choice Protection Driver, present since\nWin11 23H2 / Win10 22H2 update) to allow the HKCR file association write, sets\n.pdf -> AcroRd32, then restarts UCPD. Atera RMM agent is installed for MSP\nmonitoring, remote access, and ticketing integration.",
|
||||||
|
"items": {
|
||||||
|
"7-zip-7zip-7zip": "Installs 7-Zip (winget ID: 7zip.7zip). Used for archive management. Silent install with --accept-package-agreements --accept-source-agreements flags required for unattended deployment.",
|
||||||
|
"adobe-acrobat-reader-64-bit-adobe-acroba": "Installs Adobe Acrobat Reader DC 64-bit (Adobe.Acrobat.Reader.64-bit). Required as the default PDF viewer to prevent Edge from handling PDFs in browser mode, which limits functionality.",
|
||||||
|
"openvpn-connect-openvpntechnologies-open": "Installs OpenVPN Connect client. Used for client VPN access when the client network requires a VPN. The ovpn profile and credentials are configured separately per client.",
|
||||||
|
"atera-agent-install": "Atera RMM agent installed via msiexec /qn. Download: Invoke-WebRequest from https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337. Agent enables MSP monitoring, remote access, and ticketing integration with the Atera dashboard.",
|
||||||
|
"adobe-pdf-default-pdf-acrord32-po-instal": "Sets .pdf -> AcroRd32 file association after Acrobat install via HKCR (system-wide, no UserChoice hash issue). UCPD driver is stopped immediately before the write and restarted after to ensure the association persists across Edge updates.",
|
||||||
|
"ucpd-sys-kernel-driver-od-feb-2024-bloku": "UCPD.sys (User Choice Protection Driver) is stopped before the PDF association write and restarted after. Pattern: Stop-Service ucpd -> set HKCR\\.pdf -> Start-Service ucpd. Implemented in this script."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"03-system-registry": {
|
||||||
|
"synopsis": "Applies system-wide registry settings and power configuration (HKLM).",
|
||||||
|
"description": "Sets machine-wide registry tweaks under HKLM that apply to all users. Disables\nunwanted telemetry and cloud features, configures Edge policies, sets power plan\ntimeouts, and disables proxy auto-detect. Uninstalls the pre-installed OneDrive\nconsumer version via OneDriveSetup.exe /uninstall - intentional for a clean MSP\ndeployment baseline. No DisableFileSyncNGSC policy key is set, so M365 installation\ncan install and run its own OneDrive version without restriction.",
|
||||||
|
"items": {
|
||||||
|
"bypass-nro-oobe-bypassnro-1": "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\OOBE\\BypassNRO = 1. Bypasses the \"Let's connect you to a network\" OOBE screen. Enables offline Windows setup without forcing a Microsoft account login.",
|
||||||
|
"zakaz-auto-instalace-teams": "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Communications\\ConfigureChatAutoInstall = 0. Prevents Windows from auto-installing Teams Personal during OOBE or after Cumulative Updates.",
|
||||||
|
"zakaz-cloud-optimized-content": "ContentDeliveryManager\\DisableCloudOptimizedContent = 1. Stops Windows from pushing sponsored app suggestions, tips from Microsoft servers, and \"Get even more from Windows\" prompts.",
|
||||||
|
"zakaz-widgets-news-and-interests": "HKLM\\SOFTWARE\\Policies\\Microsoft\\Dsh\\AllowNewsAndInterests = 0. Disables the Widgets taskbar button and panel (news feed, weather, stocks). Not relevant for business deployments.",
|
||||||
|
"hesla-bez-expirace-net-accounts-maxpwage": "net accounts /maxpwage:UNLIMITED. Sets the local password expiration policy to never. MSP-managed machines handle password rotation via other means (Atera, domain policy, manual).",
|
||||||
|
"casova-zona-central-europe-standard-time": "Set-TimeZone -Id \"Central Europe Standard Time\". UTC+1 (UTC+2 in summer DST). Applied system-wide. Critical for correct log timestamps, scheduled task timing, and calendar sync.",
|
||||||
|
"zakaz-gamedvr": "HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\GameDVR\\AppCaptureEnabled = 0. Disables Xbox Game Bar screen capture overlay. Reduces background resource usage and eliminates unintended capture prompts on business machines.",
|
||||||
|
"edge-skryt-first-run-experience": "HideFirstRunExperience=1 + DefaultBrowserSettingEnabled=0. Suppresses Edge welcome wizard and default browser prompts on first launch.",
|
||||||
|
"edge-policies-panel-oblibeny-vyhledavac": "FavoritesBarEnabled=1 (always show), DefaultSearchProviderEnabled=1, DefaultSearchProviderName=Google, ManagedSearchEngines removes other providers.",
|
||||||
|
"edge-policies-tlacitka-zobrazit": "DownloadsButtonEnabled=1, HistoryButtonEnabled=1.",
|
||||||
|
"edge-policies-tlacitka-skryt": "HomeButtonEnabled=0, SplitScreenEnabled=0, EdgeEDropEnabled=0 (Drop), WebCaptureEnabled=0 (Screenshot), ShareAllowed=0.",
|
||||||
|
"edge-policies-obsah-a-telemetrie": "NewTabPageContentEnabled=0, ShowRecommendationsEnabled=0, SpotlightExperiencesAndRecommendationsEnabled=0, PersonalizationReportingEnabled=0, EdgeShoppingAssistantEnabled=0, ShowMicrosoftRewards=0, HubsSidebarEnabled=0, SearchSuggestEnabled=0, DiagnosticData=0, FeedbackSurveysEnabled=0, EdgeCollectionsEnabled=0.",
|
||||||
|
"onedrive-uninstall-intentional": "Uninstalls the pre-installed OneDrive consumer version via OneDriveSetup.exe /uninstall and removes Start Menu shortcut. Intentional for clean MSP deployment baseline. No DisableFileSyncNGSC policy key is set - M365 installation can reinstall and run OneDrive normally. Only the stock consumer pre-install is removed.",
|
||||||
|
"powercfg-nastaveni-spotreba-energie": "powercfg /change: standby-timeout-ac 0 (never sleep on AC), monitor-timeout-ac 60 (screen off after 60 min on AC), standby-timeout-dc 30 (sleep after 30 min on battery), monitor-timeout-dc 15 (screen off after 15 min on battery). Applied to active power plan.",
|
||||||
|
"proxy-auto-detect-zakaz-autodetect-0": "HKLM\\SOFTWARE\\Policies\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\AutoDetect = 0. Disables WPAD (Web Proxy Auto-Discovery). Eliminates startup delays from WPAD DNS lookup and prevents MITM via rogue WPAD on untrusted networks."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"04-default-profile": {
|
||||||
|
"synopsis": "Applies registry settings to the Default User profile and the current logged-in user.",
|
||||||
|
"description": "Loads C:\\Users\\Default\\NTUSER.DAT as a temporary hive (HKU\\DefaultProfile), applies\nall settings, then unloads it. Every new user account created on this machine inherits\nthese settings on first logon. The same settings are applied directly to the current\nuser's HKCU. Does NOT block OneDrive re-launch - the Explorer namespace CLSID and RunOnce entries have been removed.",
|
||||||
|
"items": {
|
||||||
|
"taskbar-zarovnat-vlevo-taskbaral-0": "TaskbarAl = 0 in Explorer\\Advanced. Windows 11 default is center-aligned (TaskbarAl = 1). Left alignment matches Windows 10 muscle memory and is strongly preferred by business users transitioning from Win10.",
|
||||||
|
"taskbar-skryt-search-copilot-task-view-w": "Hides Search box (SearchboxTaskbarMode=0), Copilot button (ShowCopilotButton=0), Task View (ShowTaskViewButton=0), Widgets (TaskbarDa=0), Chat/Teams (TaskbarMn=0). Reduces taskbar clutter to just pinned apps and running processes.",
|
||||||
|
"taskbar-zobrazit-vsechny-ikonky-v-tray-s": "Registers scheduled task that sets EnableAutoTray=0 on logon (repeat every 1 min). Windows 11 periodically re-hides tray icons - this task forces all icons visible so users can see VPN status, antivirus, backup, etc.",
|
||||||
|
"taskbar-vyprazdnit-pinlist-taskbarlayout": "Deploys TaskbarLayoutModification.xml. ProfileType=default: empty pins (clean slate). ProfileType=admin: Explorer+PowerShell+Edge. ProfileType=user: Explorer+Edge. Lock is removed by UnlockStartLayout task 5 min after first boot so users can customize.",
|
||||||
|
"explorer-zobrazovat-pripony-souboru-hide": "HideFileExt = 0 in Explorer\\Advanced. Shows file extensions (.docx, .exe, .pdf, .ps1) in File Explorer. Essential for recognizing file types, avoiding phishing (fake .pdf.exe), and general IT work.",
|
||||||
|
"explorer-otevrit-na-this-pc-launchto-1": "LaunchTo = 1. File Explorer opens to \"This PC\" (drives view) instead of Quick Access. More useful on fresh machines where Quick Access history is empty and irrelevant.",
|
||||||
|
"start-menu-vyprazdnit-piny-win11": "ConfigureStartPins = {\"pinnedList\":[]} applied via registry. Removes all default Start menu tiles (Edge, Teams, Store, Office, Solitaire, etc.) from the Windows 11 Start grid. User starts with an empty, clean Start menu.",
|
||||||
|
"start-menu-zakaz-bing-vyhledavani": "DisableSearchBoxSuggestions = 1 in Software\\Policies\\Microsoft\\Windows. Disables web search, Bing suggestions, and online results in Start menu search. Search returns only local apps, files, and settings.",
|
||||||
|
"copilot-zakaz-turnoffwindowscopilot-1": "TurnOffWindowsCopilot = 1 in SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsCopilot. Disables the Windows Copilot sidebar entirely. Not suitable for most client environments (data privacy, AI usage policies).",
|
||||||
|
"numlock-zapnout-pri-startu-initialkeyboa": "InitialKeyboardIndicators = 2 in Default profile. Ensures NumLock is enabled when Windows starts. Standard expectation for users working with numeric data - prevents confusion on data entry.",
|
||||||
|
"accent-barva-na-titulnich-listech-colorp": "ColorPrevalence = 1 in Personalize key. Shows the X9.cz accent color (#223B47) on window title bars and borders. Gives all windows a consistent branded appearance.",
|
||||||
|
"onedrive-runonce-klic-je-tady-smazat": "REMOVED. The RunOnce key deletion and Explorer namespace CLSID removal were deleted - those registry tweaks prevented a freshly installed OneDrive (e.g. for M365) from launching. OneDrive AppX uninstall in step 01 is intentional; blocking re-launch is not.",
|
||||||
|
"explorer-showrecent-0-showfrequent-0": "ShowRecent=0 and ShowFrequent=0 in HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer. Hides Recent files and Frequent folders from Quick Access. Privacy improvement and cleaner File Explorer on fresh deployments.",
|
||||||
|
"explorer-fullpath-1-cabinetstate": "FullPath=1 in HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CabinetState. Displays the full directory path (e.g. C:\\Users\\jan\\Documents\\Projekty) in the File Explorer title bar instead of just the folder name."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"05-personalization": {
|
||||||
|
"synopsis": "Sets system colors, wallpaper, and visual theme.",
|
||||||
|
"description": "Applies X9.cz visual identity: dark taskbar/Start with accent color #223B47\n(deep blue-gray), light app mode, no transparency. Wallpaper is set to a solid\ncolor matching the accent. BackInfo.exe (Step 07) overwrites the wallpaper with\na live system info BMP on every logon - solid color is only the fallback.",
|
||||||
|
"items": {
|
||||||
|
"system-tema-taskbar-start-dark": "SystemUsesLightTheme=0 in Themes\\Personalize. Dark mode for shell (taskbar, Start menu, Action Center, notification area). Does NOT affect application windows - those stay light. Reduces eye strain in dim environments.",
|
||||||
|
"aplikacni-tema-light": "AppsUseLightTheme=1. Application windows (File Explorer, Settings, Calculator, etc.) use white/light backgrounds. Majority of business applications (Office, browsers) also respect this and show light mode.",
|
||||||
|
"accent-barva-223b47-tmave-modroseda": "AccentColor DWORD = 0xFF473B22 (stored as ABGR: A=FF, B=47, G=3B, R=22). The deep blue-gray #223B47 is the X9.cz brand color, also used as the solid wallpaper background.",
|
||||||
|
"accent-barva-na-start-a-taskbaru-ano": "ColorPrevalence=1. Applies accent color to taskbar background and Start menu surface. The taskbar becomes the brand color instead of default black, creating a distinct recognizable look on X9.cz-deployed machines.",
|
||||||
|
"pruhlednost-vypnuta": "EnableTransparency=0. Disables Aero translucency on taskbar and Start. Improves text readability on the taskbar, reduces subtle GPU usage, and looks more professional/consistent on business machines.",
|
||||||
|
"tapeta-jednobarevna-223b47-bez-obrazku": "Wallpaper set to solid color #223B47 via SystemParametersInfo(SPI_SETDESKWALLPAPER). BackInfo.exe generates a BMP with hostname, username, OS, network info and sets it as wallpaper on every logon. Solid color = fallback only."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"06-scheduled-tasks": {
|
||||||
|
"synopsis": "Registers logon scheduled tasks to maintain per-user settings that Windows resets.",
|
||||||
|
"description": "Creates scheduled tasks under Task Scheduler that run at user logon (and optionally\non a timer) to enforce settings that Windows tends to revert. Tasks are registered\nin the Default profile task store so new user accounts inherit them automatically.\nNote: PDF-DefaultApp task has been removed - PDF default is set once during deployment.",
|
||||||
|
"items": {
|
||||||
|
"showalltrayicons-pri-logonu-kazdou-1-min": "Task 'ShowAllTrayIcons': runs at logon, repeats every 1 minute. Sets HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\EnableAutoTray=0. Windows 11 re-enables auto-hiding of tray icons after updates and sometimes after logon - the 1-min repeat ensures permanent override.",
|
||||||
|
"unlockstartlayout-jednou-po-aplikaci-lay": "Task 'UnlockStartLayout': runs once, 30 seconds after logon. Clears the Start menu layout lock bit that is set when ConfigureStartPins is applied. Without this, users cannot pin or unpin apps from Start after deployment.",
|
||||||
|
"pdf-defaultapp-pri-kazdem-logonu": "REMOVED. PDF default is set once during deployment (step 02) with UCPD service stopped. The scheduled task is no longer needed."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"07-backinfo": {
|
||||||
|
"synopsis": "Deploys BackInfo.exe to C:\\Program Files\\Backinfo\\ and configures auto-start.",
|
||||||
|
"description": "Copies the BackInfo folder from assets to Program Files, writes the OS name\nto the registry (HKLM\\SOFTWARE\\BackInfo\\OSName) so BackInfo can display it,\nand creates a Startup shortcut so BackInfo launches on every user logon.\nBackInfo renders a BMP wallpaper overlay with hostname, username, OS, HW info,\nand network info - configured via BackInfo.ini.",
|
||||||
|
"items": {
|
||||||
|
"kopirovat-assets-backinfo-do-program-fil": "Copies all files from assets\\Backinfo\\ to C:\\Program Files\\Backinfo\\. Includes BackInfo.exe, BackInfo.ini, and backinfo_W11.ps1. Creates the target directory if it does not exist.",
|
||||||
|
"registry-osname-hklm-software-backinfo": "Detects Windows build number and edition, writes OSName string to HKLM\\SOFTWARE\\BackInfo\\OSName (and WOW6432Node). BackInfo.ini references %OSName% to display the correct OS on the wallpaper.",
|
||||||
|
"startup-shortcut-backinfo-exe": "Creates a shortcut at C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp\\BackInfo.lnk pointing to C:\\Program Files\\Backinfo\\BackInfo.exe. Ensures BackInfo starts for every user on logon.",
|
||||||
|
"07-desktop-info-ps1-smazat-nahrazeno": "07-desktop-info.ps1 is superseded by this script. BackInfo.exe is the preferred approach - stable on Win10 and Win11, configurable via INI, already present in assets."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"07-desktop-info": {
|
||||||
|
"synopsis": "DEPRECATED - delete this script. Replaced by BackInfo.exe.",
|
||||||
|
"description": "Original custom PowerShell approach to render system info onto the desktop wallpaper\nusing WPF (System.Windows.Media / System.Drawing). Superseded by BackInfo.exe which\nis already present in assets/Backinfo/ and handles Win10/Win11 natively.\nACTION REQUIRED: Delete this file. Add a BackInfo deployment step to the master script.",
|
||||||
|
"items": {
|
||||||
|
"07-desktop-info-ps1-smazat-stary-pristup": "DELETE THIS FILE. The WPF rendering approach had compatibility issues on some Windows editions and required maintaining complex PS rendering code. BackInfo.exe is a mature, stable replacement already bundled in assets/Backinfo/.",
|
||||||
|
"zkopirovat-assets-backinfo-do-c-program-": "NEW STEP (in master script): Copy assets/Backinfo/ to C:\\Program Files\\Backinfo\\ on the target machine. Includes BackInfo.exe, BackInfo.ini (display config), and backinfo_W11.ps1 (setup helper).",
|
||||||
|
"spustit-backinfo-w11-ps1-detekce-os-regi": "Run backinfo_W11.ps1 after file copy. Detects Win10 vs Win11, writes the required registry key for wallpaper rendering compatibility, and creates a Startup shortcut in the All Users Startup folder.",
|
||||||
|
"backinfo-exe-v-assets-backinfo-k-dispozi": "BackInfo.exe reads BackInfo.ini on each run. INI configures: font size and family, position of each info block, which data sources to show (hostname, username, OS version, CPU, RAM, disk, IP address, domain).",
|
||||||
|
"backinfo-auto-start-pri-kazdem-logonu-vi": "The Startup shortcut created by backinfo_W11.ps1 ensures BackInfo.exe runs on every user logon. It re-reads live system data each time, so the wallpaper BMP always shows current information (username changes, IP changes, etc.)."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"08-activation": {
|
||||||
|
"synopsis": "Activates Windows using a product key from config or KMS GVLK fallback.",
|
||||||
|
"description": "Checks if Windows is already activated (LicenseStatus = 1). If not, reads the\nproduct key from config.json activation.productKey. If no key is present, selects\nthe appropriate GVLK for the detected Windows edition and activates via KMS.\nOptionally configures a specific KMS server if activation.kmsServer is set.",
|
||||||
|
"items": {
|
||||||
|
"oa3-bios-uefi-klic-kontrola-embedded-ke": "Checks for OA3 embedded product key in BIOS/UEFI firmware via SoftwareLicensingService.OA3xOriginalProductKey WMI query. If a key is found, it is installed via slmgr /ipk and activation is attempted. Most OEM machines (since Win8 OA3) have a digital entitlement key in firmware - this path handles them without requiring a key in config.json.",
|
||||||
|
"klic-z-config-json-activation-productkey": "Reads activation.productKey from config.json. Installs via slmgr.vbs /ipk <key> and activates via slmgr.vbs /ato. Supports MAK (Multiple Activation Key) for volume licensing without KMS, and retail keys. Takes priority over GVLK fallback.",
|
||||||
|
"fallback-na-gvlk-kms-client-key-dle-edic": "When no key is in config, detects Windows edition via (Get-WmiObject SoftwareLicensingProduct).Name and maps to Microsoft's published GVLK table. Pro: W269N-WFGWX-YVC9B-4J6C9-T83GX, Enterprise: NPPR9-FWDCX-D2C8J-H872K-2YT43, Home: TX9XD-98N7V-6WMQ6-BX7FG-H8Q99.",
|
||||||
|
"volitelny-kms-server-activation-kmsserve": "If activation.kmsServer is in config.json, runs slmgr.vbs /skms <server>:<port> before /ato. Used for clients with on-premises KMS infrastructure (common in larger organizations with volume licensing).",
|
||||||
|
"preskocit-pokud-jiz-aktivovano": "Queries Win32_WindowsLicenseStatus or SoftwareLicensingProduct to check LicenseStatus. Value 1 = Licensed (fully activated). Script skips activation attempt and logs \"Windows already activated\" to avoid unnecessary slmgr calls.",
|
||||||
|
"typ-klice-mak-vs-kms-vs-retail": "Key type selection depends on client's Microsoft licensing: MAK = volume license key activates online against Microsoft (limited activations), KMS = requires KMS server on network (VLSC subscription), Retail = individual license from Microsoft Store or OEM."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"09-pc-identity": {
|
||||||
|
"synopsis": "Sets PC identity: computer name, description, and creates C:\\X9 folder structure.",
|
||||||
|
"description": "Renames the computer if deployment.pcName is set in config.json. Sets the\ncomputer description (visible in System properties and network neighborhood).\nCreates C:\\X9\\ directory structure with subdirectories for logs, scripts and\nassets. Copies X9 icon and creates Desktop.ini so the folder shows a custom\nicon in Explorer. Computer rename requires a restart - this step runs last\nbefore the final summary.",
|
||||||
|
"items": {
|
||||||
|
"rename-computer-dle-config-deployment-pcn": "Renames the computer via Rename-Computer if config.json deployment.pcName is set and differs from the current name. Rename takes effect after restart. If pcName is empty, rename is skipped and the current name is preserved.",
|
||||||
|
"popis-pocitace-computer-description": "Sets the computer description shown in System Properties and Network Neighborhood. Read from config.json deployment.pcDescription, default \"X9 deployment\". Written to HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Parameters\\SrvComment.",
|
||||||
|
"vytvorit-cx9-adresar": "Creates C:\\X9\\ with subdirectories Logs\\, Scripts\\, Assets\\. Used for deployment logs, custom per-client scripts, and client-specific configuration assets.",
|
||||||
|
"cx9-vlastni-ikonka-desktop-ini": "Copies X9-ikona.ico to C:\\X9\\ and creates Desktop.ini with IconResource entry. Sets System+Hidden attributes on Desktop.ini and ReadOnly on C:\\X9\\ so Explorer displays the custom folder icon."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"10-network": {
|
||||||
|
"synopsis": "Sets network profile to Private, enables ping, and enables Network Discovery.",
|
||||||
|
"description": "Sets all connected network adapter profiles from Public to Private. Private\nprofile enables file sharing, network discovery, and other LAN features.\nEnables ICMP echo (ping) via Windows Firewall for diagnostic purposes.\nEnables the Network Discovery firewall rule group for the Private profile\nso this PC is visible to other computers on the local network.",
|
||||||
|
"items": {
|
||||||
|
"nastavit-sitovy-profil-private": "Sets all connected network profiles to Private via Set-NetConnectionProfile. Public profile blocks most LAN features. Private is required for file sharing, printer sharing, and network discovery. Applied to all currently connected adapters.",
|
||||||
|
"povolit-ping-icmp-firewall": "Enables \"File and Printer Sharing (Echo Request)\" firewall rules for ICMPv4 and ICMPv6. ICMP echo is disabled by default on clean Windows. Required for network diagnostics, monitoring tools, and basic connectivity verification.",
|
||||||
|
"zapnout-network-discovery": "Enables the Network Discovery firewall rule group (FPS-NB_Name-In-UDP, LLMNR, etc.) for Private and Domain profiles via Set-NetFirewallRule. Allows this PC to appear in Network Neighborhood and browse other machines."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"11-dell-update": {
|
||||||
|
"synopsis": "Detects Dell hardware, installs Dell Command | Update, and applies all available updates.",
|
||||||
|
"description": "Checks Win32_ComputerSystem.Manufacturer - if not Dell, the step exits silently without\nerror so the same deployment script works on any hardware. On Dell machines, installs\nDell Command | Update (Universal) via winget and immediately runs /applyUpdates with\n-reboot=disable. This covers drivers, firmware, and BIOS. BIOS and firmware updates are\nstaged at this point and finalize automatically during the restart that closes the\ndeployment. The operator does not need to run a separate update pass after setup.",
|
||||||
|
"items": {
|
||||||
|
"detekce-dell-hw-win32-computersystem": "Reads Win32_ComputerSystem.Manufacturer. If the string does not contain \"Dell\", the entire step is skipped without error. The deployment continues normally on HP, Lenovo, or any other brand.",
|
||||||
|
"instalace-dell-command-update-via-winget": "Installs Dell.CommandUpdate.Universal silently via winget. This is the current DCU generation (v5+) that supports Latitude, OptiPlex, Precision, Vostro, and XPS on Win10 and Win11.",
|
||||||
|
"spusteni-vsech-aktualizaci-drivery-firmware-bios": "Runs dcu-cli.exe /applyUpdates -silent -reboot=disable. Covers driver, firmware, and BIOS categories in a single pass. The -reboot=disable flag prevents DCU from rebooting mid-deployment.",
|
||||||
|
"bios-firmware-staging-reboot": "BIOS and firmware updates are staged by DCU and finalize on the next system restart. The deployment already ends with a restart (step 09 - computer rename), so no extra reboot is needed."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
web/favicon.ico
Normal file
BIN
web/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
18
web/get.ps1
Normal file
18
web/get.ps1
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# xetup installer - downloads and launches the latest release
|
||||||
|
# Usage: irm xetup.x9.cz/get.ps1 | iex
|
||||||
|
|
||||||
|
$api = 'https://xetup.x9.cz/forgejo-api/repos/x9/xetup/releases?limit=1'
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rel = (Invoke-RestMethod -Uri $api -UseBasicParsing)[0]
|
||||||
|
$asset = $rel.assets | Where-Object { $_.name -eq 'xetup.exe' } | Select-Object -First 1
|
||||||
|
if (-not $asset) { Write-Error "xetup.exe not found in latest release"; exit 1 }
|
||||||
|
|
||||||
|
$out = "$env:TEMP\xetup.exe"
|
||||||
|
Write-Host "Downloading xetup $($rel.tag_name)..."
|
||||||
|
Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $out -UseBasicParsing
|
||||||
|
Write-Host "Launching..."
|
||||||
|
Start-Process -FilePath $out -Verb RunAs
|
||||||
|
} catch {
|
||||||
|
Write-Error "Failed: $_"
|
||||||
|
}
|
||||||
271
web/index.html
Normal file
271
web/index.html
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="cs">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Xetup - Windows deployment pro X9.cz</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #0f1117;
|
||||||
|
--card: #1a1d27;
|
||||||
|
--border: #2a2d3a;
|
||||||
|
--text: #e0e0e0;
|
||||||
|
--muted: #888;
|
||||||
|
--accent: #223B47;
|
||||||
|
--accent-bright: #2d5266;
|
||||||
|
--green: #2ea043;
|
||||||
|
--blue: #58a6ff;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .75rem;
|
||||||
|
}
|
||||||
|
.logo-text { font-size: 1.2rem; font-weight: 700; color: #fff; letter-spacing: -.02em; }
|
||||||
|
.logo-sub { font-size: .8rem; color: var(--muted); margin-left: .2rem; }
|
||||||
|
header nav { margin-left: auto; display: flex; gap: 1.5rem; }
|
||||||
|
header nav a { color: var(--muted); text-decoration: none; font-size: .88rem; transition: color .15s; }
|
||||||
|
header nav a:hover { color: var(--text); }
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: .25rem .75rem;
|
||||||
|
font-size: .75rem;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
letter-spacing: .03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.8rem; font-weight: 800; color: #fff;
|
||||||
|
letter-spacing: -.04em; line-height: 1.1;
|
||||||
|
margin-bottom: 1rem; max-width: 600px;
|
||||||
|
}
|
||||||
|
h1 span { color: var(--blue); }
|
||||||
|
|
||||||
|
.tagline {
|
||||||
|
font-size: 1.1rem; color: var(--muted); max-width: 480px;
|
||||||
|
line-height: 1.6; margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- ACTIONS ---- */
|
||||||
|
.actions {
|
||||||
|
display: flex; gap: .75rem; flex-wrap: wrap;
|
||||||
|
justify-content: center; margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
padding: .6rem 1.4rem; background: var(--accent-bright); color: #fff;
|
||||||
|
border: 1px solid transparent; border-radius: 8px;
|
||||||
|
font-size: .95rem; font-weight: 600; text-decoration: none; transition: opacity .15s;
|
||||||
|
}
|
||||||
|
.btn-primary:hover { opacity: .85; }
|
||||||
|
.btn-secondary {
|
||||||
|
padding: .6rem 1.4rem; background: transparent; color: var(--text);
|
||||||
|
border: 1px solid var(--border); border-radius: 8px;
|
||||||
|
font-size: .95rem; text-decoration: none; transition: background .15s;
|
||||||
|
}
|
||||||
|
.btn-secondary:hover { background: var(--card); }
|
||||||
|
|
||||||
|
/* ---- INSTALL COMMAND ---- */
|
||||||
|
.install-box {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
display: flex; align-items: center; gap: 0;
|
||||||
|
background: #0d1117; border: 1px solid var(--border);
|
||||||
|
border-radius: 10px; overflow: hidden;
|
||||||
|
font-size: .88rem; max-width: 520px; width: 100%;
|
||||||
|
}
|
||||||
|
.install-box code {
|
||||||
|
flex: 1; padding: .65rem 1rem;
|
||||||
|
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
|
||||||
|
color: var(--blue); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.install-box button {
|
||||||
|
padding: .65rem .9rem; background: var(--card);
|
||||||
|
border: none; border-left: 1px solid var(--border);
|
||||||
|
color: var(--muted); cursor: pointer; font-size: .8rem;
|
||||||
|
transition: color .15s, background .15s; white-space: nowrap;
|
||||||
|
}
|
||||||
|
.install-box button:hover { background: var(--accent); color: #fff; }
|
||||||
|
.install-box button.copied { color: var(--green); }
|
||||||
|
|
||||||
|
/* ---- DOWNLOAD STRIP ---- */
|
||||||
|
.download-strip {
|
||||||
|
margin-bottom: 3.5rem;
|
||||||
|
display: flex; align-items: center; gap: .6rem;
|
||||||
|
background: var(--card); border: 1px solid var(--border);
|
||||||
|
border-radius: 10px; padding: .6rem 1.1rem;
|
||||||
|
font-size: .85rem;
|
||||||
|
}
|
||||||
|
.download-strip a {
|
||||||
|
color: var(--blue); text-decoration: none; font-weight: 600;
|
||||||
|
display: flex; align-items: center; gap: .35rem;
|
||||||
|
}
|
||||||
|
.download-strip a:hover { text-decoration: underline; }
|
||||||
|
.download-strip .dl-meta { color: var(--muted); font-size: .78rem; }
|
||||||
|
.download-strip .dl-sep { color: var(--border); }
|
||||||
|
|
||||||
|
/* ---- CARDS ---- */
|
||||||
|
.cards {
|
||||||
|
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem; max-width: 720px; width: 100%;
|
||||||
|
}
|
||||||
|
.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 1.2rem; text-align: left; }
|
||||||
|
.card-icon { font-size: 1.3rem; margin-bottom: .6rem; display: block; }
|
||||||
|
.card h3 { font-size: .9rem; font-weight: 600; color: #fff; margin-bottom: .3rem; }
|
||||||
|
.card p { font-size: .82rem; color: var(--muted); line-height: 1.4; }
|
||||||
|
|
||||||
|
footer {
|
||||||
|
border-top: 1px solid var(--border); padding: 1.2rem 2rem;
|
||||||
|
text-align: center; font-size: .8rem; color: var(--muted);
|
||||||
|
}
|
||||||
|
footer a { color: var(--muted); text-decoration: none; }
|
||||||
|
footer a:hover { color: var(--text); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<div style="display:flex;align-items:center;gap:.6rem">
|
||||||
|
<img src="/x9-logo.jpeg" alt="X9.cz" style="height:28px;width:28px;border-radius:5px;object-fit:cover;">
|
||||||
|
<span class="logo-text">xetup</span><span class="logo-sub">by X9.cz</span>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<a href="/spec/">Specifikace</a>
|
||||||
|
<a href="https://git.xetup.x9.cz/x9/xetup">Git</a>
|
||||||
|
<a href="https://git.xetup.x9.cz/x9/xetup/issues">Issues</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="badge">MSP deployment tool — X9.cz</div>
|
||||||
|
|
||||||
|
<h1>Automaticky nastavene <span>Windows</span> za 20 minut</h1>
|
||||||
|
|
||||||
|
<p class="tagline">
|
||||||
|
Nahrazuje 3 hodiny rucniho nastavovani jednim skriptem.
|
||||||
|
Win10 + Win11, OEM i cisty install, funguje offline.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a href="/spec/" class="btn-primary">Zobrazit specifikaci</a>
|
||||||
|
<a href="https://git.xetup.x9.cz/x9/xetup" class="btn-secondary">Git repozitar</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="install-box">
|
||||||
|
<code id="install-cmd">curl -Lo xetup.exe xetup.x9.cz/dl</code>
|
||||||
|
<button id="copy-btn" onclick="copyCmd()">Kopirovat</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dynamic download strip – filled by JS from Forgejo releases API -->
|
||||||
|
<div class="download-strip" id="dl-strip" style="display:none">
|
||||||
|
<a id="dl-link" href="#" download>
|
||||||
|
⬇ xetup.exe
|
||||||
|
</a>
|
||||||
|
<span class="dl-sep">·</span>
|
||||||
|
<span class="dl-meta" id="dl-meta">nacitam...</span>
|
||||||
|
<span class="dl-sep">·</span>
|
||||||
|
<a href="https://git.xetup.x9.cz/x9/xetup/releases" style="color:var(--muted);font-size:.78rem;text-decoration:none">
|
||||||
|
vsechny verze
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
<div class="card">
|
||||||
|
<span class="card-icon">⚙</span>
|
||||||
|
<h3>~20 stroju / mesic</h3>
|
||||||
|
<p>Ruzni klienti, Win10 i Win11, vcetne nepodporovaneho HW.</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<span class="card-icon">🔒</span>
|
||||||
|
<h3>Offline provoz</h3>
|
||||||
|
<p>Scripty + assets jsou soucasti balicku. Site jen pro winget a Atera.</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<span class="card-icon">📄</span>
|
||||||
|
<h3>Per-client config</h3>
|
||||||
|
<p>config.json vedle .exe pro opakovatelne nasazeni u stejneho klienta.</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<span class="card-icon">🚀</span>
|
||||||
|
<h3>Go TUI launcher</h3>
|
||||||
|
<p>xetup.exe — jednotny binarni spoustec s TUI formularom a live logem.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
© 2026 <a href="https://x9.cz">X9.cz s.r.o.</a>
|
||||||
|
·
|
||||||
|
<a href="https://git.xetup.x9.cz/x9/xetup">Forgejo</a>
|
||||||
|
·
|
||||||
|
<a href="/spec/">Specifikace</a>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const API = '/forgejo-api';
|
||||||
|
const REPO = 'x9/xetup';
|
||||||
|
|
||||||
|
fetch(API + '/repos/' + REPO + '/releases?limit=1')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(releases => {
|
||||||
|
if (!Array.isArray(releases) || !releases.length) return;
|
||||||
|
const rel = releases[0];
|
||||||
|
const asset = (rel.assets || []).find(a => a.name === 'xetup.exe');
|
||||||
|
if (!asset) return;
|
||||||
|
|
||||||
|
const strip = document.getElementById('dl-strip');
|
||||||
|
const link = document.getElementById('dl-link');
|
||||||
|
const meta = document.getElementById('dl-meta');
|
||||||
|
const sizeMB = (asset.size / 1048576).toFixed(1);
|
||||||
|
|
||||||
|
link.href = asset.browser_download_url;
|
||||||
|
link.textContent = '\u2b15 xetup.exe';
|
||||||
|
meta.textContent = rel.tag_name + ' \u00b7 ' + sizeMB + ' MB \u00b7 Windows x64';
|
||||||
|
strip.style.display = '';
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
})();
|
||||||
|
|
||||||
|
function copyCmd() {
|
||||||
|
const cmd = document.getElementById('install-cmd').textContent;
|
||||||
|
const btn = document.getElementById('copy-btn');
|
||||||
|
navigator.clipboard.writeText(cmd).then(function() {
|
||||||
|
btn.textContent = 'Skopirovano!';
|
||||||
|
btn.classList.add('copied');
|
||||||
|
setTimeout(function() {
|
||||||
|
btn.textContent = 'Kopirovat';
|
||||||
|
btn.classList.remove('copied');
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
33
web/nginx.conf
Normal file
33
web/nginx.conf
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
charset utf-8;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ $uri.html =404;
|
||||||
|
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate" always;
|
||||||
|
add_header Pragma "no-cache" always;
|
||||||
|
add_header Expires "0" always;
|
||||||
|
add_header X-Content-Type-Options nosniff always;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Permanent shortlink to latest xetup.exe – never needs updating
|
||||||
|
location = /dl {
|
||||||
|
return 302 https://git.xetup.x9.cz/x9/xetup/releases/download/latest/xetup.exe;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy Forgejo API calls so browser doesn't need CORS or direct access to Forgejo
|
||||||
|
location /forgejo-api/ {
|
||||||
|
proxy_pass http://xetup-forgejo:3000/api/v1/;
|
||||||
|
proxy_set_header Host xetup-forgejo;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 404 /404.html;
|
||||||
|
}
|
||||||
1355
web/spec/index.html
Normal file
1355
web/spec/index.html
Normal file
File diff suppressed because it is too large
Load diff
BIN
web/x9-logo.jpeg
Normal file
BIN
web/x9-logo.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 292 KiB |
BIN
xetup.exe
Executable file
BIN
xetup.exe
Executable file
Binary file not shown.
Loading…
Reference in a new issue