aboutsummaryrefslogtreecommitdiff
path: root/windows/Documents/PowerShell/bootstrap.ps1
diff options
context:
space:
mode:
authorsrdusr <trevorgray@srdusr.com>2025-09-23 21:09:05 +0200
committersrdusr <trevorgray@srdusr.com>2025-09-23 21:09:05 +0200
commitb451a80b5767bb4849208db659ddf506e8090e12 (patch)
tree4ddfc9d93e4e79574df95e03f00c4dee5d793be8 /windows/Documents/PowerShell/bootstrap.ps1
parent57403da32da78683b7509f66058d7d334dfb5e58 (diff)
downloaddotfiles-b451a80b5767bb4849208db659ddf506e8090e12.tar.gz
dotfiles-b451a80b5767bb4849208db659ddf506e8090e12.zip
Minor fixes
Diffstat (limited to 'windows/Documents/PowerShell/bootstrap.ps1')
-rw-r--r--windows/Documents/PowerShell/bootstrap.ps1949
1 files changed, 611 insertions, 338 deletions
diff --git a/windows/Documents/PowerShell/bootstrap.ps1 b/windows/Documents/PowerShell/bootstrap.ps1
index 73d53b5..d2f4369 100644
--- a/windows/Documents/PowerShell/bootstrap.ps1
+++ b/windows/Documents/PowerShell/bootstrap.ps1
@@ -1,396 +1,669 @@
-# Requires -RunAsAdministrator
-
-# Set execution policy to remote signed
-Set-ExecutionPolicy RemoteSigned -Scope CurrentUser -Force
-
-# Set network category to private
-Set-NetConnectionProfile -NetworkCategory Private
-
-# Variables
-$dotfiles_url = 'https://github.com/srdusr/dotfiles.git'
-$dotfiles_dir = "$HOME\.cfg"
-
-# Function to handle errors
-function handle_error {
- param ($message)
- Write-Host $message -ForegroundColor Red
- exit 1
+#!/usr/bin/env pwsh
+
+# Created By: srdusr
+# Created On: Windows PowerShell Bootstrap Script
+# Project: Dotfiles installation script for Windows
+
+# Dependencies: git, powershell
+
+param(
+ [string]$Profile = "essentials",
+ [switch]$Force = $false,
+ [switch]$Ask = $false,
+ [switch]$Help = $false
+)
+
+# Color definitions for pretty UI
+$Script:Colors = @{
+ Reset = "`e[0m"
+ Red = "`e[0;31m"
+ Green = "`e[0;32m"
+ Yellow = "`e[0;33m"
+ Blue = "`e[0;34m"
+ Cyan = "`e[0;36m"
+ White = "`e[0;37m"
+ Bold = "`e[1m"
}
-# Logs
-New-Item -Path $Env:USERPROFILE\Logs -ItemType directory -Force
-Start-Transcript -Path $Env:USERPROFILE\Logs\Bootstrap.log
-$ErrorActionPreference = 'SilentlyContinue'
-Write-Host "Bootstrap.log generated in Logs\"
-
-# Function to check if the current session is elevated
-function Test-IsAdmin {
- $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
- $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
- return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
+# Prompt helper: Yes/No with default; in non-Ask mode, returns default immediately
+function Prompt-YesNo {
+ param(
+ [Parameter(Mandatory=$true)][string]$Question,
+ [ValidateSet('Y','N')][string]$Default = 'Y'
+ )
+ if (-not $Script:AskPreference) {
+ return $Default -eq 'Y'
+ }
+ $suffix = if ($Default -eq 'Y') { '[Y/n]' } else { '[y/N]' }
+ while ($true) {
+ Write-Host -NoNewline "$Question $suffix: " -ForegroundColor Yellow
+ $resp = Read-Host
+ if ([string]::IsNullOrWhiteSpace($resp)) { $resp = $Default }
+ switch ($resp.ToUpper()) {
+ 'Y' { return $true }
+ 'YES' { return $true }
+ 'N' { return $false }
+ 'NO' { return $false }
+ default { Write-Warning "Please answer Y/yes or N/no" }
+ }
+ }
}
-# Ensure the script is run as administrator
-if (-not (Test-IsAdmin)) {
- handle_error "This script must be run as an administrator."
+# Configuration
+$Script:Config = @{
+ DotfilesUrl = 'https://github.com/srdusr/dotfiles.git'
+ DotfilesDir = "$HOME\.cfg"
+ LogFile = "$HOME\AppData\Local\dotfiles_install.log"
+ StateFile = "$HOME\AppData\Local\dotfiles_install_state"
+ BackupDir = "$HOME\.dotfiles-backup-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ PackagesFile = "packages.yml"
+ OS = "windows"
}
-# Imports
-. $HOME\.config\powershell\initialize.ps1
-. $HOME\.config\powershell\bloatware.ps1
-
-# Configure PowerShell
-Write-Host "Configuring PowerShell"
-Write-Host "----------------------------------------"
-
-# Get the "MyDocuments" path for the current user, excluding OneDrive
-$UserMyDocumentsPath = [System.Environment]::GetFolderPath('MyDocuments').Replace("OneDrive", "")
-
-$PowerShellProfileDirectory = "$UserMyDocumentsPath\PowerShell"
-$PowerShellLegacySymlink = "$UserMyDocumentsPath\WindowsPowerShell"
-
-$PowerShellProfileTemplate = "$PSScriptRoot\$USERNAME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
-$env:PSModulePath = $env:PSModulePath -replace "\\OneDrive\\Documents\\WindowsPowerShell\\","\.powershell\"
-
-# Set documents path to user's local Documents folder
-$documentsPath = "$UserMyDocumentsPath"
-$powerShellProfileDir = "$documentsPath\PowerShell"
-
-# Output the chosen PowerShell profile directory
-$PROFILE = "$powerShellProfileDir\Microsoft.PowerShell_profile.ps1"
-Write-Host "PowerShell profile directory set to: $powerShellProfileDir"
-
-if (-not (Test-Path -Path $powerShellProfileDir)) {
- New-Item -ItemType Directory -Path $powerShellProfileDir -Force
+# Logging functions
+function Write-Log {
+ param([string]$Message, [string]$Level = "INFO")
+ $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
+ $logEntry = "[$timestamp] [$Level] $Message"
+ Add-Content -Path $Script:Config.LogFile -Value $logEntry -Force
}
-New-Item -ItemType HardLink -Force `
- -Path "$powerShellProfileDir\Microsoft.PowerShell_profile.ps1" `
- -Target "$home\.config\powershell\Microsoft.PowerShell_profile.ps1"
-
-# Set environment variable
-[System.Environment]::SetEnvironmentVariable('PowerShellProfileDir', $powerShellProfileDir, [System.EnvironmentVariableTarget]::User)
-
-Write-Host "PowerShell profile directory set to: $powerShellProfileDir"
-Write-Host "Environment variable 'PowerShellProfileDir' set to: $powerShellProfileDir"
-
-# Verify profile sourcing
-if (!(Test-Path -Path "$home\.config\powershell\Microsoft.PowerShell_profile.ps1")) {
- handle_error "PowerShell profile does not exist. Please create it at $home\.config\powershell\Microsoft.PowerShell_profile.ps1"
+function Write-ColorOutput {
+ param([string]$Message, [string]$Color = "White")
+ Write-Host $Message -ForegroundColor $Color
+ Write-Log $Message
}
-# Install Chocolatey if not installed
-Write-Host "Installing Chocolatey"
-Write-Host "----------------------------------------"
-
-if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
- [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
- Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
-
- # Check if Chocolatey installed successfully
- if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
- handle_error "Chocolatey installation failed."
- }
-} else {
- Write-Host "Chocolatey is already installed."
+function Write-Success { param([string]$Message) Write-ColorOutput "✓ $Message" "Green" }
+function Write-Info { param([string]$Message) Write-ColorOutput "ℹ $Message" "Cyan" }
+function Write-Warning { param([string]$Message) Write-ColorOutput "⚠ $Message" "Yellow" }
+function Write-Error { param([string]$Message) Write-ColorOutput "✗ $Message" "Red" }
+
+function Write-Header {
+ param([string]$Title)
+ Write-Host ""
+ Write-Host "=" * 60 -ForegroundColor Blue
+ Write-Host " $Title" -ForegroundColor Bold
+ Write-Host "=" * 60 -ForegroundColor Blue
+ Write-Host ""
}
-# Install Applications
-Write-Host "Installing Applications"
-Write-Host "----------------------------------------"
-
-# Check if the powershell-yaml module is installed, if not, install it
-if (-not (Get-Module powershell-yaml -ListAvailable)) {
- $policy = Get-PSRepository -Name 'PSGallery' | Select-Object -ExpandProperty InstallationPolicy
- Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
- Install-Module powershell-yaml
- Set-PSRepository -Name 'PSGallery' -InstallationPolicy $policy
+# Utility functions
+function Test-CommandExists {
+ param([string]$Command)
+ return [bool](Get-Command $Command -ErrorAction SilentlyContinue)
}
-Import-Module powershell-yaml
-
-# Load packages.yml
-$packagesFile = "$HOME\packages.yml"
-$packages = Get-Content $packagesFile | ConvertFrom-Yaml
-
-# Ensure 'windows' section exists and has applications listed
-if ($packages.windows) {
- foreach ($app in $packages.windows) {
- # Check if the application is already installed
- if (-not (choco list --local-only | Select-String -Pattern "^$app\s")) {
- Write-Host "Installing $app"
- choco install $app -y
-
- if ($LASTEXITCODE -ne 0) {
- handle_error "Installation of $app failed."
- } else {
- Write-Host "$app installed successfully."
- }
- } else {
- Write-Host "$app is already installed."
- }
- }
-} else {
- Write-Host "No applications specified under the 'windows' section in $packagesFile."
+function Test-IsAdmin {
+ $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent()
+ $principal = New-Object Security.Principal.WindowsPrincipal($currentUser)
+ return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
-# Set Chrome as default browser ------------------------
-#Add-Type -AssemblyName 'System.Windows.Forms'
-#Start-Process $env:windir\system32\control.exe -ArgumentList '/name Microsoft.DefaultPrograms /page pageDefaultProgram\pageAdvancedSettings?pszAppName=google%20chrome'
-#Sleep 2
-#[System.Windows.Forms.SendKeys]::SendWait("{TAB} {TAB}{TAB} ")
-SetDefaultBrowser firefox
-
-# Refresh the environment variables
-Write-Host "Refreshing environment variables"
-
-# Update the current session environment variables
-Write-Host "Setting environment variables" -ForegroundColor Cyan
-$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
-[System.Environment]::SetEnvironmentVariable("Path", $env:Path, [System.EnvironmentVariableTarget]::Process)
-[Environment]::SetEnvironmentVariable("HOME", "$env:USERPROFILE", "User")
-[Environment]::SetEnvironmentVariable("LC_ALL", "C.UTF-8", "User")
-refreshenv
-
-# Add Git to PATH if it's installed via Chocolatey
-Write-Host "Checking for Git installation"
-$gitBinPath = "C:\Program Files\Git\bin"
-$gitCmdPath = "C:\Program Files\Git\cmd"
-$gitPaths = @($gitBinPath, $gitCmdPath)
-
-foreach ($path in $gitPaths) {
- if (Test-Path $path) {
- Write-Host "Adding $path to PATH"
- [System.Environment]::SetEnvironmentVariable("Path", "$env:Path;$path", [System.EnvironmentVariableTarget]::Machine)
- [System.Environment]::SetEnvironmentVariable("Path", "$env:Path;$path", [System.EnvironmentVariableTarget]::User)
- [System.Environment]::SetEnvironmentVariable("Path", "$env:Path;$path", [System.EnvironmentVariableTarget]::Process)
+function Invoke-AdminCommand {
+ param([string]$Command)
+ if (-not (Test-IsAdmin)) {
+ Write-Warning "Elevating privileges for: $Command"
+ Start-Process powershell.exe -ArgumentList "-NoProfile", "-Command", $Command -Verb RunAs -Wait
} else {
- Write-Host "$path does not exist."
+ Invoke-Expression $Command
}
}
-# Check if Git is installed
-Write-Host "Checking for Git installation"
-if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
- handle_error "Git is not installed or not found in PATH after installation."
-} else {
- Write-Host "Git is installed and available in PATH."
+# Package management functions
+function Get-PackageManager {
+ if (Test-CommandExists "choco") { return "chocolatey" }
+ if (Test-CommandExists "winget") { return "winget" }
+ if (Test-CommandExists "scoop") { return "scoop" }
+ return $null
}
-# Define the `config` alias in the current session
-function global:config {
- git --git-dir="$env:USERPROFILE\.cfg" --work-tree="$env:USERPROFILE" $args
-}
-
-# Add .gitignore entries
-Add-Content -Path "$HOME\.gitignore" -Value ".cfg"
-Add-Content -Path "$HOME\.gitignore" -Value "install.bat"
-Add-Content -Path "$HOME\.gitignore" -Value ".config/powershell/bootstrap.ps1"
-
-# Create symbolic links
-Write-Host "Create symbolic links"
-Write-Host "----------------------------------------"
-
-# Visual Studio Code settings.json
-New-Item -Force -ItemType SymbolicLink $HOME\AppData\Roaming\Code\User\ -Name settings.json -Value $HOME\.config\Code\User\settings.json
-
-# Visual Studio Code keybindings
-New-Item -Force -ItemType SymbolicLink $HOME\AppData\Roaming\Code\User\ -Name keybindings.json -Value $HOME\.config\Code\User\keybindings.json
-
-# Function to install dotfiles
-function install_dotfiles {
- if (Test-Path -Path $dotfiles_dir) {
- config pull | Out-Null
- $update = $true
- } else {
- git clone --bare $dotfiles_url $dotfiles_dir | Out-Null
- $update = $false
- }
-
- $std_err_output = config checkout 2>&1
-
- if ($std_err_output -match "following untracked working tree files would be overwritten") {
- if (-not $update) {
- config checkout | Out-Null
+# Return $true if a package appears to be installed for the given manager
+function Test-PackageInstalled {
+ param(
+ [Parameter(Mandatory=$true)][string]$Manager,
+ [Parameter(Mandatory=$true)][string]$Name
+ )
+ switch ($Manager) {
+ "chocolatey" {
+ $out = choco list --local-only --exact $Name 2>$null
+ return ($out | Select-String -Pattern "^\s*$([regex]::Escape($Name))\s").Length -gt 0
+ }
+ "winget" {
+ # Winget list may return multiple rows; use --exact name match when possible
+ $out = winget list --name $Name 2>$null
+ return ($out | Select-String -SimpleMatch $Name).Length -gt 0
}
+ "scoop" {
+ # scoop list <name> returns 0 when installed
+ scoop list $Name *> $null
+ return $LASTEXITCODE -eq 0
+ }
+ default { return $false }
}
- config config status.showUntrackedFiles no
-
- git config --global include.path "$HOME\.gitconfig.aliases"
+}
- if ($update -or (Read-Host "Do you want to overwrite existing files and continue with the dotfiles setup? [Y/n]" -eq "Y")) {
- config fetch origin main:main | Out-Null
- config reset --hard main | Out-Null
- config checkout -f
- if ($?) {
- Write-Host "Successfully imported $dotfiles_dir."
+function Install-PackageManager {
+ Write-Header "Installing Package Manager"
+
+ if (-not (Test-CommandExists "choco")) {
+ Write-Info "Installing Chocolatey..."
+ Set-ExecutionPolicy Bypass -Scope Process -Force
+ [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
+ Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
+
+ if (Test-CommandExists "choco") {
+ Write-Success "Chocolatey installed successfully"
} else {
- handle_error "Mission failed."
+ Write-Error "Failed to install Chocolatey"
+ return $false
}
} else {
- handle_error "Aborted by user. Exiting..."
+ Write-Info "Chocolatey already installed"
}
+ return $true
}
-install_dotfiles
-
-# Install python
-Write-Host "Updating python packages" -ForegroundColor Cyan
-python -m pip install --upgrade pip
-
-# Enable WSL feature
-dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
-Write-Host "Enable WSL feature"
-wsl --install -d ubuntu
-wsl --set-default-version 2
-
-# Enable Virtual Machine feature
-#dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
-#Write-Host "Enable Virtual Machine feature"
-
-Write-Header "Installing Hyper-V"
-
-# Install Hyper-V
-Write-Host "Installing Hyper-V and restart"
-Enable-WindowsOptionalFeature -Online -FeatureName Containers -All -NoRestart
-Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart
-Install-WindowsFeature -Name Hyper-V -IncludeAllSubFeature -IncludeManagementTools -NoRestart
-
-# Configure Neovim
-Write-Host "Configuring Neovim"
-Write-Host "----------------------------------------"
-
-$neovimLocalPath = "$home\AppData\Local\nvim"
-$neovimConfigPath = "$home\.config\nvim"
+function Install-Packages {
+ param([string]$PackagesFile, [string]$Profile)
+
+ if (-not (Test-Path $PackagesFile)) {
+ Write-Warning "Packages file not found: $PackagesFile"
+ return
+ }
+
+ Write-Header "Installing Packages"
+
+ # Install powershell-yaml if not available
+ if (-not (Get-Module powershell-yaml -ListAvailable)) {
+ Write-Info "Installing powershell-yaml module..."
+ $policy = Get-PSRepository -Name 'PSGallery' | Select-Object -ExpandProperty InstallationPolicy
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
+ Install-Module powershell-yaml -Force
+ Set-PSRepository -Name 'PSGallery' -InstallationPolicy $policy
+ }
+
+ Import-Module powershell-yaml
+
+ # Helper: run custom_installs.<name>.windows if condition passes
+ function Invoke-CustomInstallsWindows {
+ param([Parameter(Mandatory=$true)]$Yaml)
+ if (-not $Yaml.custom_installs) { return }
+ foreach ($name in $Yaml.custom_installs.PSObject.Properties.Name) {
+ $entry = $Yaml.custom_installs.$name
+ if (-not $entry) { continue }
+ $winCmd = $entry.windows
+ if (-not $winCmd) { continue }
+ $shouldRun = $true
+ if ($entry.condition) {
+ $cond = [string]$entry.condition
+ if ($cond -match "!\s*command\s+-v\s+([A-Za-z0-9._-]+)") {
+ $shouldRun = -not (Test-CommandExists $Matches[1])
+ } elseif ($cond -match "command\s+-v\s+([A-Za-z0-9._-]+)") {
+ $shouldRun = (Test-CommandExists $Matches[1])
+ }
+ }
+ if (-not $shouldRun) { Write-Info "Skipping custom install: $name"; continue }
+ Write-Info "Running custom install: $name"
+ try { Invoke-Expression $winCmd; Write-Success "Custom install completed: $name" }
+ catch { Write-Warning "Custom install failed for '$name': $_" }
+ }
+ }
+
+ try {
+ $packages = Get-Content $PackagesFile | ConvertFrom-Yaml
+ $packageManager = Get-PackageManager
+
+ if (-not $packageManager) {
+ Write-Error "No package manager available"
+ return
+ }
+
+ # Get packages for current profile and OS
+ $profilePackages = @()
+ if ($packages.profiles.$Profile.windows) {
+ $profilePackages += $packages.profiles.$Profile.windows
+ }
+ if ($packages.profiles.$Profile.common) {
+ $profilePackages += $packages.profiles.$Profile.common
+ }
+
+ foreach ($package in $profilePackages) {
+ $packageName = if ($packages.packages.$package.windows) {
+ $packages.packages.$package.windows
+ } else {
+ $package
+ }
-# Check if nvim directory already exists in AppData\Local
-if (-not (Test-Path -Path $neovimLocalPath)) {
- New-Item -ItemType Junction -Force -Path $neovimLocalPath -Target $neovimConfigPath
-} else {
- Write-Host "Neovim directory ($neovimLocalPath) already exists."
-}
+ if (Test-PackageInstalled -Manager $packageManager -Name $packageName) {
+ Write-Info "Already installed: $packageName"
+ continue
+ }
-# Install Windows Terminal, and configure
-Write-Host "Install Windows Terminal, and configure"
-Write-Host "----------------------------------------"
+ Write-Info "Installing package: $packageName"
+
+ switch ($packageManager) {
+ "chocolatey" {
+ if (-not (choco list --local-only | Select-String -Pattern "^$packageName\s")) {
+ choco install $packageName -y
+ if ($LASTEXITCODE -eq 0) {
+ Write-Success "Installed: $packageName"
+ } else {
+ Write-Error "Failed to install: $packageName"
+ }
+ } else {
+ Write-Info "Already installed: $packageName"
+ }
+ }
+ "winget" {
+ winget install $packageName --accept-package-agreements --accept-source-agreements
+ }
+ "scoop" {
+ scoop install $packageName
+ }
+ }
+ }
-$windowsTerminalSettingsPath = "$home\AppData\Local\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json"
-$windowsTerminalConfigPath = "$home\.config\windows-terminal\settings.json"
+ # Also install top-level Windows packages list if present
+ if ($packages.windows) {
+ foreach ($pkg in $packages.windows) {
+ if ([string]::IsNullOrWhiteSpace($pkg)) { continue }
+ if (Test-PackageInstalled -Manager $packageManager -Name $pkg) { Write-Info "Already installed: $pkg"; continue }
+ Write-Info "Installing package: $pkg"
+ switch ($packageManager) {
+ "chocolatey" {
+ if (-not (choco list --local-only | Select-String -Pattern "^$([regex]::Escape($pkg))\s")) { choco install $pkg -y }
+ }
+ "winget" { winget install --id $pkg --silent --accept-package-agreements --accept-source-agreements }
+ "scoop" { scoop install $pkg }
+ }
+ }
+ }
-# Check if Windows Terminal settings.json already exists
-if (Test-Path -Path $windowsTerminalSettingsPath) {
- # Backup existing settings.json
- Move-Item -Force $windowsTerminalSettingsPath "$windowsTerminalSettingsPath.old"
-} else {
- Write-Host "Windows Terminal settings.json not found, no need to backup."
+ # Execute Windows custom installs from packages.yml
+ Invoke-CustomInstallsWindows -Yaml $packages
+ } catch {
+ Write-Error "Error processing packages: $_"
+ }
}
-# Create a hard link to the settings.json file in .config\windows-terminal
-New-Item -ItemType HardLink -Force -Path $windowsTerminalSettingsPath -Target $windowsTerminalConfigPath
-
-# Function to check if a registry key exists
-function Test-RegistryKeyExists {
- param ($path)
- return (Test-Path $path -PathType Container)
+# Dotfiles management functions
+function Install-Dotfiles {
+ Write-Header "Installing Dotfiles"
+
+ if (Test-Path $Script:Config.DotfilesDir) {
+ Write-Info "Updating existing dotfiles repository..."
+ & git --git-dir="$($Script:Config.DotfilesDir)" --work-tree="$($Script:Config.DotfilesDir)" pull origin main
+ } else {
+ Write-Info "Cloning dotfiles repository..."
+ git clone --bare $Script:Config.DotfilesUrl $Script:Config.DotfilesDir
+
+ if (-not (Test-Path $Script:Config.DotfilesDir)) {
+ Write-Error "Failed to clone dotfiles repository"
+ return $false
+ }
+ }
+
+ # Set up config alias for this session
+ function script:config {
+ git --git-dir="$($Script:Config.DotfilesDir)" --work-tree="$($Script:Config.DotfilesDir)" @args
+ }
+
+ # Configure repository
+ config config --local status.showUntrackedFiles no
+
+ # Checkout files to restore directory structure
+ Write-Info "Checking out dotfiles..."
+ try {
+ config checkout 2>$null
+ if ($LASTEXITCODE -ne 0) {
+ Write-Info "Forcing checkout to overwrite existing files..."
+ config checkout -f
+ }
+ Write-Success "Dotfiles checked out successfully"
+ } catch {
+ Write-Error "Failed to checkout dotfiles: $_"
+ return $false
+ }
+
+ return $true
}
-# Function to check if a registry property exists
-function Test-RegistryPropertyExists {
- param ($keyPath, $propertyName)
- if (Test-Path $keyPath) {
- $properties = Get-ItemProperty -Path $keyPath
- return $properties.PSObject.Properties.Name -contains $propertyName
+function Deploy-Dotfiles {
+ Write-Header "Deploying Dotfiles"
+
+ if (-not (Test-Path $Script:Config.DotfilesDir)) {
+ Write-Error "Dotfiles directory not found. Run Install-Dotfiles first."
+ return $false
+ }
+
+ # Source the config command from profile if available
+ $profilePath = "$HOME\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
+ if (Test-Path $profilePath) {
+ Write-Info "Loading config command from profile..."
+ . $profilePath
+ }
+
+ # Deploy using config command if available, otherwise manual deployment
+ if (Get-Command config -ErrorAction SilentlyContinue) {
+ Write-Info "Deploying dotfiles using config command..."
+ config deploy
+ } else {
+ Write-Warning "Config command not available, using manual deployment..."
+ # Manual deployment fallback would go here
}
- return $false
+
+ Write-Success "Dotfiles deployment completed"
+ return $true
}
-# Registry Tweaks
-Write-Host "Registry Tweaks"
-Write-Host "----------------------------------------"
-
-# Show hidden files
-$advancedKeyPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
-if (-not (Test-RegistryPropertyExists $advancedKeyPath "Hidden")) {
- Set-ItemProperty -Path $advancedKeyPath -Name Hidden -Value 1
+# Locate profile-specific packages.yml similar to Linux installer
+function Get-ProfilePackagesFile {
+ param([string]$Profile)
+ $candidates = @(
+ Join-Path $HOME ".cfg/profile/$Profile/packages.yml",
+ Join-Path $HOME "profile/$Profile/packages.yml",
+ Join-Path $HOME "dot_setup/profile/$Profile/packages.yml",
+ Join-Path $Script:Config.DotfilesDir "common/$($Script:Config.PackagesFile)"
+ )
+ foreach ($pf in $candidates) {
+ if (Test-Path $pf) { return $pf }
+ }
+ return $null
}
-# Show file extensions in Windows Explorer
-$hideFileExtPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
-if (-not (Test-RegistryPropertyExists $hideFileExtPath "HideFileExt")) {
- Set-ItemProperty -Path $hideFileExtPath -Name HideFileExt -Value 0
+# System configuration functions
+function Set-WindowsConfiguration {
+ param(
+ [string]$PackagesFile
+ )
+
+ Write-Header "Configuring Windows Settings"
+
+ if (-not $PackagesFile -or -not (Test-Path $PackagesFile)) {
+ Write-Warning "Packages file not found, skipping Windows configuration"
+ return
+ }
+
+ try {
+ # Load YAML content
+ $yamlContent = Get-Content $PackagesFile -Raw | ConvertFrom-Yaml
+ $registrySettings = $yamlContent.system_tweaks.windows.registry
+
+ if (-not $registrySettings) {
+ Write-Warning "No Windows registry settings found in packages.yml"
+ return
+ }
+
+ Write-Info "Applying registry settings from packages.yml..."
+
+ foreach ($setting in $registrySettings) {
+ try {
+ $path = $setting.path
+ $name = $setting.name
+ $value = $setting.value
+ $type = $setting.type
+ $description = $setting.description
+
+ Write-Info "Setting: $description"
+
+ # Ensure the registry path exists
+ $pathParts = $path -split '\\'
+ $currentPath = $pathParts[0]
+ for ($i = 1; $i -lt $pathParts.Length; $i++) {
+ $currentPath = "$currentPath\$($pathParts[$i])"
+ if (-not (Test-Path $currentPath)) {
+ New-Item -Path $currentPath -Force | Out-Null
+ }
+ }
+
+ # Set the registry value
+ Set-ItemProperty -Path $path -Name $name -Value $value -Type $type -Force
+ Write-Success "Applied: $description"
+
+ } catch {
+ Write-Warning "Failed to apply setting '$($setting.description)': $_"
+ }
+ }
+
+ Write-Success "Windows configuration applied"
+
+ # Restart explorer to apply changes
+ Write-Info "Restarting Windows Explorer..."
+ Stop-Process -Name explorer -Force
+ Start-Process explorer.exe
+
+ } catch {
+ Write-Warning "Failed to process Windows configuration: $_"
+ }
}
-# Never Combine taskbar buttons when the taskbar is full
-$taskbarGlomLevelPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
-if (-not (Test-RegistryPropertyExists $taskbarGlomLevelPath "TaskbarGlomLevel")) {
- Set-ItemProperty -Path $taskbarGlomLevelPath -Name TaskbarGlomLevel -Value 2
+function Enable-WindowsFeatures {
+ param(
+ [string]$PackagesFile
+ )
+
+ Write-Header "Enabling Windows Features"
+
+ if (-not $PackagesFile -or -not (Test-Path $PackagesFile)) {
+ Write-Warning "Packages file not found, skipping Windows features"
+ return
+ }
+
+ try {
+ # Load YAML content
+ $yamlContent = Get-Content $PackagesFile -Raw | ConvertFrom-Yaml
+ $features = $yamlContent.system_tweaks.windows.features
+
+ if (-not $features) {
+ Write-Warning "No Windows features found in packages.yml"
+ return
+ }
+
+ foreach ($feature in $features) {
+ $featureName = $feature.name
+ $description = $feature.description
+ $requiresAdmin = $feature.requires_admin
+
+ if ($requiresAdmin -and -not (Test-IsAdmin)) {
+ Write-Warning "Skipping '$description' - requires administrator privileges"
+ continue
+ }
+
+ try {
+ Write-Info "Enabling: $description"
+ dism.exe /online /enable-feature /featurename:$featureName /all /norestart
+ Write-Success "Enabled: $description"
+ } catch {
+ Write-Warning "Failed to enable '$description': $_"
+ }
+ }
+
+ Write-Success "Windows features processing complete (restart may be required)"
+
+ } catch {
+ Write-Warning "Failed to process Windows features: $_"
+ }
}
-# Taskbar small icons
-$taskbarSmallIconsPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
-if (-not (Test-RegistryPropertyExists $taskbarSmallIconsPath "TaskbarSmallIcons")) {
- Set-ItemProperty -Path $taskbarSmallIconsPath -Name TaskbarSmallIcons -Value 1
+function Install-PowerShellProfile {
+ Write-Header "Setting up PowerShell Profile"
+
+ $documentsPath = [System.Environment]::GetFolderPath('MyDocuments')
+ $powerShellProfileDir = "$documentsPath\PowerShell"
+ $profilePath = "$powerShellProfileDir\Microsoft.PowerShell_profile.ps1"
+
+ Write-Info "PowerShell profile directory: $powerShellProfileDir"
+
+ if (-not (Test-Path $powerShellProfileDir)) {
+ New-Item -ItemType Directory -Path $powerShellProfileDir -Force | Out-Null
+ Write-Success "Created PowerShell profile directory"
+ }
+
+ # Copy profile from dotfiles if it exists
+ $dotfilesProfile = "$($Script:Config.DotfilesDir)\windows\Documents\PowerShell\Microsoft.PowerShell_profile.ps1"
+ if (Test-Path $dotfilesProfile) {
+ Copy-Item $dotfilesProfile $profilePath -Force
+ Write-Success "PowerShell profile installed from dotfiles"
+ } else {
+ Write-Warning "PowerShell profile not found in dotfiles"
+ }
}
-# Set Windows to use UTC time instead of local time for system clock
-$timeZoneInfoPath = "HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
-if (-not (Test-RegistryPropertyExists $timeZoneInfoPath "RealTimeIsUniversal")) {
- Set-ItemProperty -Path $timeZoneInfoPath -Name RealTimeIsUniversal -Value 1
+# Main execution function
+function Start-Bootstrap {
+ param([string]$Profile, [switch]$Force, [switch]$Ask)
+
+ Write-Header "Windows Dotfiles Bootstrap"
+ Write-Info "Profile: $Profile"
+ Write-Info "Force mode: $Force"
+ Write-Info "Interactive mode: $Ask"
+
+ # Initialize logging
+ $logDir = Split-Path $Script:Config.LogFile
+ if (-not (Test-Path $logDir)) {
+ New-Item -ItemType Directory -Path $logDir -Force | Out-Null
+ }
+
+ Write-Log "Bootstrap started with profile: $Profile"
+
+ # Set Ask preference for all prompts
+ $Script:AskPreference = [bool]$Ask
+
+ # Check dependencies
+ Write-Header "Checking Dependencies"
+ $requiredCommands = @("git", "powershell")
+ $missingCommands = @()
+
+ foreach ($cmd in $requiredCommands) {
+ if (-not (Test-CommandExists $cmd)) {
+ $missingCommands += $cmd
+ Write-Error "Required command not found: $cmd"
+ } else {
+ Write-Success "Found: $cmd"
+ }
+ }
+
+ if ($missingCommands.Count -gt 0) {
+ Write-Error "Missing required dependencies. Please install: $($missingCommands -join ', ')"
+ return $false
+ }
+
+ # Install package manager (skippable)
+ if (Prompt-YesNo -Question "Install/check package manager?" -Default 'Y') {
+ if (-not (Install-PackageManager)) {
+ Write-Error "Failed to install package manager"
+ return $false
+ }
+ } else {
+ Write-Warning "Skipped package manager step by user choice"
+ }
+
+ # Install dotfiles
+ if (Prompt-YesNo -Question "Install or update dotfiles?" -Default 'Y') {
+ if (-not (Install-Dotfiles)) {
+ Write-Error "Failed to install dotfiles"
+ return $false
+ }
+ } else {
+ Write-Warning "Skipped dotfiles installation by user choice"
+ }
+
+ # Get packages file (profile-aware)
+ $packagesFile = Get-ProfilePackagesFile -Profile $Profile
+ if (-not $packagesFile) {
+ Write-Error "Failed to get packages file for profile '$Profile'"
+ return $false
+ }
+
+ # Install packages
+ if (Prompt-YesNo -Question "Install profile packages?" -Default 'Y') {
+ Install-Packages -PackagesFile $packagesFile -Profile $Profile
+ } else {
+ Write-Warning "Skipped package installation by user choice"
+ }
+
+ # Set up PowerShell profile
+ if (Prompt-YesNo -Question "Install PowerShell profile?" -Default 'Y') {
+ Install-PowerShellProfile
+ } else {
+ Write-Warning "Skipped PowerShell profile setup by user choice"
+ }
+
+ # Deploy dotfiles
+ if (Prompt-YesNo -Question "Deploy dotfiles to system locations?" -Default 'Y') {
+ if (-not (Deploy-Dotfiles)) {
+ Write-Error "Failed to deploy dotfiles"
+ return $false
+ }
+ } else {
+ Write-Warning "Skipped dotfiles deployment by user choice"
+ }
+
+ # Configure Windows
+ if (Prompt-YesNo -Question "Apply Windows configuration from packages.yml?" -Default 'N') {
+ Set-WindowsConfiguration -PackagesFile $packagesPath
+ } else {
+ Write-Warning "Skipped Windows configuration by user choice"
+ }
+
+ # Enable Windows features (if admin)
+ if (Prompt-YesNo -Question "Enable Windows optional features?" -Default 'N') {
+ Enable-WindowsFeatures -PackagesFile $packagesPath
+ } else {
+ Write-Warning "Skipped enabling Windows features by user choice"
+ }
+
+ Write-Header "Bootstrap Complete"
+ Write-Success "Windows dotfiles bootstrap completed successfully!"
+ Write-Info "Please restart your computer to apply all changes."
+ Write-Log "Bootstrap completed successfully"
+
+ return $true
}
-# Disable the search in taskbar
-$searchBoxTaskbarPath = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Search"
-if (-not (Test-RegistryPropertyExists $searchBoxTaskbarPath "SearchBoxTaskbarMode")) {
- New-ItemProperty -Path $searchBoxTaskbarPath -Name SearchBoxTaskbarMode -Value 0 -Type DWord -Force
+# Help function
+function Show-Help {
+ Write-Host @"
+Windows Dotfiles Bootstrap Script
+
+USAGE:
+ .\bootstrap.ps1 [-Profile <profile>] [-Force] [-Ask] [-Help]
+
+PARAMETERS:
+ -Profile <string> Installation profile (default: essentials)
+ Available: essentials, minimal, dev, server, full, or a custom profile.
+ Custom profile files are resolved from:
+ - %USERPROFILE%\.cfg\profile\<name>\packages.yml
+ - %USERPROFILE%\profile\<name>\packages.yml
+ - %USERPROFILE%\dot_setup\profile\<name>\packages.yml
+ -Force Force installation without prompts
+ -Ask Interactive mode with step-by-step prompts
+ -Help Show this help message
+
+EXAMPLES:
+ .\bootstrap.ps1 # Install with essentials profile
+ .\bootstrap.ps1 -Profile dev # Install development profile
+ .\bootstrap.ps1 -Profile full -Force # Force install full profile
+ .\bootstrap.ps1 -Ask # Interactive installation
+
+"@ -ForegroundColor Cyan
}
-# Dark mode:
-$personalizePath = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize"
-if (-not (Test-RegistryPropertyExists $personalizePath "AppsUseLightTheme")) {
- Set-ItemProperty -Path $personalizePath -Name AppsUseLightTheme -Value 0 -Type Dword -Force
-}
-if (-not (Test-RegistryPropertyExists $personalizePath "SystemUsesLightTheme")) {
- Set-ItemProperty -Path $personalizePath -Name SystemUsesLightTheme -Value 0 -Type Dword -Force
+# Script entry point
+if ($Help) {
+ Show-Help
+ exit 0
}
-# Restart explorer so the rest of the settings take effect:
-Stop-Process -f -ProcessName explorer
-Start-Process explorer.exe
-
-# Function to disable the Windows key
-function Disable-WindowsKey {
- $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Keyboard Layout"
- $regName = "Scancode Map"
-
- # Binary data to remap the Windows key to F24 (an unused key)
- $binaryValue = [byte[]](
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00,
- 0x02, 0x00, 0x00, 0x00,
- 0x3A, 0x00, 0x5B, 0xE0,
- 0x00, 0x00, 0x00, 0x00
- )
-
- # Create the registry key if it doesn't exist
- if (-not (Test-RegistryKeyExists $regPath)) {
- New-Item -Path $regPath -Force | Out-Null
- }
-
- # Set the Scancode Map value if it doesn't exist
- if (-not (Test-RegistryPropertyExists $regPath $regName)) {
- Set-ItemProperty -Path $regPath -Name $regName -Value $binaryValue
+# Run bootstrap
+try {
+ $result = Start-Bootstrap -Profile $Profile -Force:$Force -Ask:$Ask
+ if (-not $result) {
+ exit 1
}
-
- Write-Output "Windows key has been disabled from opening the start menu. Please restart your computer for the changes to take effect."
+} catch {
+ Write-Error "Bootstrap failed: $_"
+ Write-Log "Bootstrap failed: $_" "ERROR"
+ exit 1
}
-
-#Disable-WindowsKey
-
-Write-Host "Bootstrap script completed."
-Write-Host "Please Restart."
-
-# Clean up Bootstrap.log
-Write-Host "Clean up Bootstrap.log"
-Stop-Transcript
-$logSuppress = Get-Content $Env:USERPROFILE\Logs\Bootstrap.log | Where-Object { $_ -notmatch "Host Application: powershell.exe" }
-$logSuppress | Set-Content $Env:USERPROFILE\Logs\Bootstrap.log -Force