mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 18:34:46 +00:00
397 lines
12 KiB
PowerShell
397 lines
12 KiB
PowerShell
# Mole - Base Definitions and Utilities
|
|
# Core definitions, constants, and basic utility functions used by all modules
|
|
|
|
#Requires -Version 5.1
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# Prevent multiple sourcing
|
|
if ((Get-Variable -Name 'MOLE_BASE_LOADED' -Scope Script -ErrorAction SilentlyContinue) -and $script:MOLE_BASE_LOADED) { return }
|
|
$script:MOLE_BASE_LOADED = $true
|
|
|
|
# ============================================================================
|
|
# Color Definitions (ANSI escape codes for modern terminals)
|
|
# ============================================================================
|
|
$script:ESC = [char]27
|
|
$script:Colors = @{
|
|
Green = "$ESC[0;32m"
|
|
Blue = "$ESC[0;34m"
|
|
Cyan = "$ESC[0;36m"
|
|
Yellow = "$ESC[0;33m"
|
|
Purple = "$ESC[0;35m"
|
|
PurpleBold = "$ESC[1;35m"
|
|
Red = "$ESC[0;31m"
|
|
Gray = "$ESC[0;90m"
|
|
White = "$ESC[0;37m"
|
|
NC = "$ESC[0m" # No Color / Reset
|
|
}
|
|
|
|
# ============================================================================
|
|
# Icon Definitions
|
|
# ============================================================================
|
|
$script:Icons = @{
|
|
Confirm = [char]0x25CE # ◎
|
|
Admin = [char]0x2699 # ⚙
|
|
Success = [char]0x2713 # ✓
|
|
Error = [char]0x263B # ☻
|
|
Warning = [char]0x25CF # ●
|
|
Empty = [char]0x25CB # ○
|
|
Solid = [char]0x25CF # ●
|
|
List = [char]0x2022 # •
|
|
Arrow = [char]0x27A4 # ➤
|
|
DryRun = [char]0x2192 # →
|
|
NavUp = [char]0x2191 # ↑
|
|
NavDown = [char]0x2193 # ↓
|
|
Folder = [char]0x25A0 # ■ (folder substitute)
|
|
File = [char]0x25A1 # □ (file substitute)
|
|
Trash = [char]0x2718 # ✘ (trash substitute)
|
|
}
|
|
|
|
# ============================================================================
|
|
# Global Configuration Constants
|
|
# ============================================================================
|
|
$script:Config = @{
|
|
TempFileAgeDays = 7 # Temp file retention (days)
|
|
OrphanAgeDays = 60 # Orphaned data retention (days)
|
|
MaxParallelJobs = 15 # Parallel job limit
|
|
LogAgeDays = 7 # Log retention (days)
|
|
CrashReportAgeDays = 7 # Crash report retention (days)
|
|
MaxIterations = 100 # Max iterations for scans
|
|
ConfigPath = "$env:USERPROFILE\.config\mole"
|
|
CachePath = "$env:USERPROFILE\.cache\mole"
|
|
WhitelistFile = "$env:USERPROFILE\.config\mole\whitelist.txt"
|
|
}
|
|
|
|
# ============================================================================
|
|
# Default Whitelist Patterns (paths to never clean)
|
|
# ============================================================================
|
|
$script:DefaultWhitelistPatterns = @(
|
|
"$env:LOCALAPPDATA\Microsoft\Windows\Explorer" # Windows Explorer cache
|
|
"$env:LOCALAPPDATA\Microsoft\Windows\Fonts" # User fonts
|
|
"$env:APPDATA\Microsoft\Windows\Recent" # Recent files (used by shell)
|
|
"$env:LOCALAPPDATA\Packages\*" # UWP app data
|
|
"$env:USERPROFILE\.vscode\extensions" # VS Code extensions
|
|
"$env:USERPROFILE\.nuget" # NuGet packages
|
|
"$env:USERPROFILE\.cargo" # Rust packages
|
|
"$env:USERPROFILE\.rustup" # Rust toolchain
|
|
"$env:USERPROFILE\.m2\repository" # Maven repository
|
|
"$env:USERPROFILE\.gradle\caches\modules-2\files-*" # Gradle modules
|
|
"$env:USERPROFILE\.ollama\models" # Ollama AI models
|
|
"$env:LOCALAPPDATA\JetBrains" # JetBrains IDEs
|
|
)
|
|
|
|
# ============================================================================
|
|
# Protected System Paths (NEVER touch these)
|
|
# ============================================================================
|
|
$script:ProtectedPaths = @(
|
|
"C:\Windows"
|
|
"C:\Windows\System32"
|
|
"C:\Windows\SysWOW64"
|
|
"C:\Program Files"
|
|
"C:\Program Files (x86)"
|
|
"C:\Program Files\Windows Defender"
|
|
"C:\Program Files (x86)\Windows Defender"
|
|
"C:\ProgramData\Microsoft\Windows Defender"
|
|
"$env:SYSTEMROOT"
|
|
"$env:WINDIR"
|
|
)
|
|
|
|
# ============================================================================
|
|
# System Utilities
|
|
# ============================================================================
|
|
|
|
function Test-IsAdmin {
|
|
<#
|
|
.SYNOPSIS
|
|
Check if running with administrator privileges
|
|
#>
|
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
|
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
|
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
}
|
|
|
|
function Get-FreeSpace {
|
|
<#
|
|
.SYNOPSIS
|
|
Get free disk space on system drive
|
|
.OUTPUTS
|
|
Human-readable string (e.g., "100GB")
|
|
#>
|
|
param([string]$Drive = $env:SystemDrive)
|
|
|
|
$disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='$Drive'" -ErrorAction SilentlyContinue
|
|
if ($disk) {
|
|
return Format-ByteSize -Bytes $disk.FreeSpace
|
|
}
|
|
return "Unknown"
|
|
}
|
|
|
|
function Get-WindowsVersion {
|
|
<#
|
|
.SYNOPSIS
|
|
Get Windows version information
|
|
#>
|
|
$os = Get-WmiObject Win32_OperatingSystem
|
|
return @{
|
|
Name = $os.Caption
|
|
Version = $os.Version
|
|
Build = $os.BuildNumber
|
|
Arch = $os.OSArchitecture
|
|
}
|
|
}
|
|
|
|
function Get-CPUCores {
|
|
<#
|
|
.SYNOPSIS
|
|
Get number of CPU cores
|
|
#>
|
|
return (Get-WmiObject Win32_Processor).NumberOfLogicalProcessors
|
|
}
|
|
|
|
function Get-OptimalParallelJobs {
|
|
<#
|
|
.SYNOPSIS
|
|
Get optimal number of parallel jobs based on CPU cores
|
|
#>
|
|
param(
|
|
[ValidateSet('scan', 'io', 'compute', 'default')]
|
|
[string]$OperationType = 'default'
|
|
)
|
|
|
|
$cores = Get-CPUCores
|
|
switch ($OperationType) {
|
|
'scan' { return [Math]::Min($cores * 2, 32) }
|
|
'io' { return [Math]::Min($cores * 2, 32) }
|
|
'compute' { return $cores }
|
|
default { return [Math]::Min($cores + 2, 20) }
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Path Utilities
|
|
# ============================================================================
|
|
|
|
function Test-ProtectedPath {
|
|
<#
|
|
.SYNOPSIS
|
|
Check if a path is protected and should never be modified
|
|
#>
|
|
param([string]$Path)
|
|
|
|
$normalizedPath = [System.IO.Path]::GetFullPath($Path).TrimEnd('\')
|
|
|
|
foreach ($protected in $script:ProtectedPaths) {
|
|
$normalizedProtected = [System.IO.Path]::GetFullPath($protected).TrimEnd('\')
|
|
if ($normalizedPath -eq $normalizedProtected -or
|
|
$normalizedPath.StartsWith("$normalizedProtected\", [StringComparison]::OrdinalIgnoreCase)) {
|
|
return $true
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Test-Whitelisted {
|
|
<#
|
|
.SYNOPSIS
|
|
Check if path matches a whitelist pattern
|
|
#>
|
|
param([string]$Path)
|
|
|
|
# Check default patterns
|
|
foreach ($pattern in $script:DefaultWhitelistPatterns) {
|
|
$expandedPattern = [Environment]::ExpandEnvironmentVariables($pattern)
|
|
if ($Path -like $expandedPattern) {
|
|
return $true
|
|
}
|
|
}
|
|
|
|
# Check user whitelist file
|
|
if (Test-Path $script:Config.WhitelistFile) {
|
|
$userPatterns = Get-Content $script:Config.WhitelistFile -ErrorAction SilentlyContinue
|
|
foreach ($pattern in $userPatterns) {
|
|
$pattern = $pattern.Trim()
|
|
if ($pattern -and -not $pattern.StartsWith('#')) {
|
|
if ($Path -like $pattern) {
|
|
return $true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
function Resolve-SafePath {
|
|
<#
|
|
.SYNOPSIS
|
|
Resolve and validate a path for safe operations
|
|
#>
|
|
param([string]$Path)
|
|
|
|
try {
|
|
$resolved = [System.IO.Path]::GetFullPath($Path)
|
|
return $resolved
|
|
}
|
|
catch {
|
|
return $null
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Formatting Utilities
|
|
# ============================================================================
|
|
|
|
function Format-ByteSize {
|
|
<#
|
|
.SYNOPSIS
|
|
Convert bytes to human-readable format
|
|
#>
|
|
param([long]$Bytes)
|
|
|
|
if ($Bytes -ge 1TB) {
|
|
return "{0:N2}TB" -f ($Bytes / 1TB)
|
|
}
|
|
elseif ($Bytes -ge 1GB) {
|
|
return "{0:N2}GB" -f ($Bytes / 1GB)
|
|
}
|
|
elseif ($Bytes -ge 1MB) {
|
|
return "{0:N1}MB" -f ($Bytes / 1MB)
|
|
}
|
|
elseif ($Bytes -ge 1KB) {
|
|
return "{0:N0}KB" -f ($Bytes / 1KB)
|
|
}
|
|
else {
|
|
return "{0}B" -f $Bytes
|
|
}
|
|
}
|
|
|
|
function Format-Number {
|
|
<#
|
|
.SYNOPSIS
|
|
Format a number with thousands separators
|
|
#>
|
|
param([long]$Number)
|
|
return $Number.ToString("N0")
|
|
}
|
|
|
|
function Format-TimeSpan {
|
|
<#
|
|
.SYNOPSIS
|
|
Format a timespan to human-readable string
|
|
#>
|
|
param([TimeSpan]$Duration)
|
|
|
|
if ($Duration.TotalHours -ge 1) {
|
|
return "{0:N1}h" -f $Duration.TotalHours
|
|
}
|
|
elseif ($Duration.TotalMinutes -ge 1) {
|
|
return "{0:N0}m" -f $Duration.TotalMinutes
|
|
}
|
|
else {
|
|
return "{0:N0}s" -f $Duration.TotalSeconds
|
|
}
|
|
}
|
|
|
|
# ============================================================================
|
|
# Environment Detection
|
|
# ============================================================================
|
|
|
|
function Get-UserHome {
|
|
<#
|
|
.SYNOPSIS
|
|
Get the current user's home directory
|
|
#>
|
|
return $env:USERPROFILE
|
|
}
|
|
|
|
function Get-TempPath {
|
|
<#
|
|
.SYNOPSIS
|
|
Get the system temp path
|
|
#>
|
|
return [System.IO.Path]::GetTempPath()
|
|
}
|
|
|
|
function Get-ConfigPath {
|
|
<#
|
|
.SYNOPSIS
|
|
Get Mole config directory, creating it if needed
|
|
#>
|
|
$path = $script:Config.ConfigPath
|
|
if (-not (Test-Path $path)) {
|
|
New-Item -ItemType Directory -Path $path -Force | Out-Null
|
|
}
|
|
return $path
|
|
}
|
|
|
|
function Get-CachePath {
|
|
<#
|
|
.SYNOPSIS
|
|
Get Mole cache directory, creating it if needed
|
|
#>
|
|
$path = $script:Config.CachePath
|
|
if (-not (Test-Path $path)) {
|
|
New-Item -ItemType Directory -Path $path -Force | Out-Null
|
|
}
|
|
return $path
|
|
}
|
|
|
|
# ============================================================================
|
|
# Temporary File Management
|
|
# ============================================================================
|
|
|
|
$script:TempFiles = [System.Collections.ArrayList]::new()
|
|
$script:TempDirs = [System.Collections.ArrayList]::new()
|
|
|
|
function New-TempFile {
|
|
<#
|
|
.SYNOPSIS
|
|
Create a tracked temporary file
|
|
#>
|
|
param([string]$Prefix = "winmole")
|
|
|
|
$tempPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "$Prefix-$([Guid]::NewGuid().ToString('N').Substring(0,8)).tmp")
|
|
New-Item -ItemType File -Path $tempPath -Force | Out-Null
|
|
[void]$script:TempFiles.Add($tempPath)
|
|
return $tempPath
|
|
}
|
|
|
|
function New-TempDirectory {
|
|
<#
|
|
.SYNOPSIS
|
|
Create a tracked temporary directory
|
|
#>
|
|
param([string]$Prefix = "winmole")
|
|
|
|
$tempPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "$Prefix-$([Guid]::NewGuid().ToString('N').Substring(0,8))")
|
|
New-Item -ItemType Directory -Path $tempPath -Force | Out-Null
|
|
[void]$script:TempDirs.Add($tempPath)
|
|
return $tempPath
|
|
}
|
|
|
|
function Clear-TempFiles {
|
|
<#
|
|
.SYNOPSIS
|
|
Clean up all tracked temporary files and directories
|
|
#>
|
|
foreach ($file in $script:TempFiles) {
|
|
if (Test-Path $file) {
|
|
Remove-Item $file -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
$script:TempFiles.Clear()
|
|
|
|
foreach ($dir in $script:TempDirs) {
|
|
if (Test-Path $dir) {
|
|
Remove-Item $dir -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
$script:TempDirs.Clear()
|
|
}
|
|
|
|
# ============================================================================
|
|
# Exports (functions and variables are available via dot-sourcing)
|
|
# ============================================================================
|
|
# Variables: Colors, Icons, Config, ProtectedPaths, DefaultWhitelistPatterns
|
|
# Functions: Test-IsAdmin, Get-FreeSpace, Get-WindowsVersion, etc.
|