<# .SYNOPSIS Morning Masterpiece wallpaper for Windows: sets today's artwork as your desktop wallpaper and keeps it fresh. https://morningmasterpiece.com .DESCRIPTION Commands: update Fetch today's artwork, download it if new, set the wallpaper (default) install Copy this script to %LOCALAPPDATA%, add a Task Scheduler task, run once uninstall Remove the task and the installed script (-Purge: also cached images) info Print today's artwork credit and page link open Open today's artwork page in your browser status Show cache, settings and schedule Privacy: the only network requests are HTTPS GETs for today.json and the wallpaper image on morningmasterpiece.com. Nothing is collected or sent. Works with Windows PowerShell 5.1 (built into Windows 10/11) and PowerShell 7. .EXAMPLE powershell -ExecutionPolicy Bypass -File .\morning-masterpiece-wallpaper.ps1 install .NOTES Copyright (c) 2026 Morning Masterpiece. Released under the MIT License. #> [CmdletBinding()] param( [Parameter(Position = 0)] [ValidateSet('update', 'install', 'uninstall', 'info', 'open', 'status', 'help', 'version')] [string]$Command = 'update', [switch]$Force, [ValidateSet('fill', 'fit', 'stretch', 'center', 'tile')] [string]$Mode, [switch]$Phone, [switch]$Desktop, [switch]$DryRun, [switch]$Quiet, [switch]$Purge, [switch]$Info, [switch]$Version ) Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' $MMVersion = '1.0.0' $Prog = 'morning-masterpiece-wallpaper' $Site = 'https://morningmasterpiece.com' $MediaPrefix = 'https://morningmasterpiece.com/' $DefaultApiUrl = 'https://morningmasterpiece.com/api/today.json' $UserAgent = "$Prog/$MMVersion" $TaskName = 'Morning Masterpiece Wallpaper' # ---------------------------------------------------------------- output --- $script:QuietMode = $false $script:DryRunMode = $false $script:ForceMode = $false $script:MMMutex = $null $script:MetaSource = '' function Say([string]$Message) { if (-not $script:QuietMode) { Write-Host $Message } } function Note([string]$Message) { Write-Host $Message } function Warn([string]$Message) { [Console]::Error.WriteLine("${Prog}: $Message") } # ----------------------------------------------------------------- paths --- function Get-MMPaths { $root = Join-Path $env:LOCALAPPDATA 'MorningMasterpiece\Wallpaper' $api = $DefaultApiUrl if ($env:MM_WALLPAPER_API_URL) { $api = $env:MM_WALLPAPER_API_URL } @{ Root = $root Bin = Join-Path $root 'bin' Script = Join-Path (Join-Path $root 'bin') "$Prog.ps1" State = Join-Path $root 'state.json' Meta = Join-Path $root 'today.json' Config = Join-Path $root 'config' Lock = Join-Path $root '.lock' Api = $api } } # -------------------------------------------------------------- settings --- function New-MMSettings { @{ Mode = 'fill'; Variant = 'desktop'; Keep = 7 } } # Returns $true if the value was valid and stored. function Set-MMSetting($Settings, [string]$Key, [string]$Value, [string]$Source) { switch ($Key) { 'mode' { if (@('fill', 'fit', 'stretch', 'center', 'tile') -notcontains $Value) { Warn "${Source}: unknown mode '$Value' (fill, fit, stretch, center, tile)"; return $false } $Settings.Mode = $Value } 'variant' { if (@('desktop', 'phone') -notcontains $Value) { Warn "${Source}: variant must be desktop or phone"; return $false } $Settings.Variant = $Value } 'keep' { $n = 0 if (-not [int]::TryParse($Value, [ref]$n) -or $n -lt 1 -or $n -gt 365) { Warn "${Source}: keep must be a number between 1 and 365"; return $false } $Settings.Keep = $n } # Shared config keys that only matter on Linux/macOS. { @('backend', 'notify', 'command') -contains $_ } { } default { Warn "${Source}: unknown setting '$Key'"; return $false } } return $true } # Config file: "key = value" lines, '#' comments. function Read-MMConfig($Settings, [string]$Path) { $ok = $true if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $ok } $n = 0 foreach ($line in [IO.File]::ReadAllLines($Path)) { $n++ $line = $line.Trim() if ($line -eq '' -or $line.StartsWith('#')) { continue } $i = $line.IndexOf('=') if ($i -lt 1) { Warn "${Path}:${n}: expected key = value"; $ok = $false; continue } $key = $line.Substring(0, $i).Trim() $value = $line.Substring($i + 1).Trim().Trim('"') if (-not (Set-MMSetting $Settings $key $value "${Path}:${n}")) { $ok = $false } } return $ok } function Read-MMEnvSettings($Settings) { $ok = $true if ($env:MM_WALLPAPER_MODE) { $ok = (Set-MMSetting $Settings 'mode' $env:MM_WALLPAPER_MODE 'MM_WALLPAPER_MODE') -and $ok } if ($env:MM_WALLPAPER_VARIANT) { $ok = (Set-MMSetting $Settings 'variant' $env:MM_WALLPAPER_VARIANT 'MM_WALLPAPER_VARIANT') -and $ok } if ($env:MM_WALLPAPER_KEEP) { $ok = (Set-MMSetting $Settings 'keep' $env:MM_WALLPAPER_KEEP 'MM_WALLPAPER_KEEP') -and $ok } return $ok } # ------------------------------------------------------------- metadata --- # Scalar at a dotted path of a ConvertFrom-Json object, or $null. function Get-MMField($Object, [string]$Path) { $cur = $Object foreach ($part in $Path.Split('.')) { if ($null -eq $cur -or $cur -isnot [psobject]) { return $null } $prop = $cur.PSObject.Properties[$part] if ($null -eq $prop) { return $null } $cur = $prop.Value } if ($cur -is [datetime]) { return $cur.ToString('yyyy-MM-dd', [Globalization.CultureInfo]::InvariantCulture) } if ($cur -is [string] -or $cur -is [ValueType]) { return [string]$cur } return $null } function Get-CleanText($Value) { if ($null -eq $Value) { return '' } return ([string]$Value -replace '[\x00-\x1F\x7F]', ' ').Trim() } function Test-MMUrl([string]$Url) { if ([string]::IsNullOrEmpty($Url)) { return $false } if (-not $Url.StartsWith($MediaPrefix, [StringComparison]::Ordinal)) { return $false } if ($Url.Length -gt 2048) { return $false } return -not ($Url -cmatch '[^A-Za-z0-9._~:/?#@!$&()*+,;=%-]') } # Add utm_source=wallpaper (replacing any existing utm_source), keep the fragment. function Add-MMUtm([string]$Url) { $frag = '' $i = $Url.IndexOf('#') if ($i -ge 0) { $frag = $Url.Substring($i); $Url = $Url.Substring(0, $i) } $q = $Url.IndexOf('?') if ($q -lt 0) { return "${Url}?utm_source=wallpaper$frag" } $base = $Url.Substring(0, $q) $parts = New-Object System.Collections.Generic.List[string] $found = $false foreach ($p in $Url.Substring($q + 1).Split('&')) { if ($p -eq '') { continue } if ($p -eq 'utm_source' -or $p.StartsWith('utm_source=')) { if ($found) { continue } $p = 'utm_source=wallpaper'; $found = $true } $parts.Add($p) } if (-not $found) { $parts.Add('utm_source=wallpaper') } return $base + '?' + ($parts -join '&') + $frag } # Parse today.json text into a hashtable, or $null if unusable. function Read-MMMeta([string]$Json) { try { $o = $Json | ConvertFrom-Json } catch { return $null } if ($null -eq $o) { return $null } $date = Get-MMField $o 'publish_date' if ($date -notmatch '^\d{4}-[01]\d-[0-3]\d$') { return $null } $slug = (Get-CleanText (Get-MMField $o 'slug')).ToLowerInvariant() -replace '[^a-z0-9-]', '' if ($slug.Length -gt 80) { $slug = $slug.Substring(0, 80) } if ($slug -eq '') { $slug = 'artwork' } $page = Get-MMField $o 'url' if (-not (Test-MMUrl $page)) { $page = "$Site/$date" } @{ Object = $o Date = $date Slug = $slug Title = Get-CleanText (Get-MMField $o 'artwork.title') Artist = Get-CleanText (Get-MMField $o 'artwork.artist') Year = Get-CleanText (Get-MMField $o 'artwork.date') Medium = Get-CleanText (Get-MMField $o 'artwork.medium') Museum = Get-CleanText (Get-MMField $o 'artwork.museum') Credit = Get-CleanText (Get-MMField $o 'artwork.credit_line') Story = Get-CleanText (Get-MMField $o 'story_title') Page = $page } } # First allowed URL: wallpaper image, download link, then the large image. function Select-MMImageUrl($Meta, [string]$Variant) { foreach ($path in @("images.wallpaper_$Variant", "images.downloads.wallpaper_$Variant", 'images.large')) { $u = Get-MMField $Meta.Object $path if ($null -eq $u) { continue } if (Test-MMUrl $u) { return $u } Warn "ignoring image URL outside $MediaPrefix ($path)" } return $null } function Get-MMCredit($Meta) { $c = $Meta.Title if (-not $c) { $c = 'Untitled' } if ($Meta.Artist) { $c = "$c - $($Meta.Artist)" } if ($Meta.Museum) { $c = "$c ($($Meta.Museum))" } return $c } # --------------------------------------------------------------- network --- function Invoke-MMDownload([string]$Url, [string]$OutFile, [int]$TimeoutSec) { $ProgressPreference = 'SilentlyContinue' Invoke-WebRequest -Uri $Url -OutFile $OutFile -UserAgent $UserAgent -TimeoutSec $TimeoutSec ` -UseBasicParsing -MaximumRedirection 0 -ErrorAction Stop } function Test-MMJpeg([string]$Path) { if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $false } if ((Get-Item -LiteralPath $Path).Length -lt 1024) { return $false } $fs = [IO.File]::OpenRead($Path) try { $b = New-Object byte[] 3 $null = $fs.Read($b, 0, 3) } finally { $fs.Dispose() } return ($b[0] -eq 0xFF -and $b[1] -eq 0xD8 -and $b[2] -eq 0xFF) } # ---------------------------------------------------------------- state --- function Read-MMState([string]$Path) { $s = @{ publish_date = ''; slug = ''; variant = ''; image = ''; applied_image = '' } if (Test-Path -LiteralPath $Path) { try { $o = [IO.File]::ReadAllText($Path, [Text.Encoding]::UTF8) | ConvertFrom-Json foreach ($k in @($s.Keys)) { $v = Get-MMField $o $k if ($null -ne $v) { $s[$k] = $v } } } catch { Warn "ignoring unreadable state file $Path" } } return $s } function Save-MMState($State, [string]$Path) { $tmp = "$Path.tmp" $json = New-Object psobject -Property $State | ConvertTo-Json [IO.File]::WriteAllText($tmp, $json, (New-Object Text.UTF8Encoding $false)) Move-MMFile $tmp $Path } function Move-MMFile([string]$From, [string]$To) { if (Test-Path -LiteralPath $To) { Remove-Item -LiteralPath $To -Force } Move-Item -LiteralPath $From -Destination $To -Force } function Get-MMCachedImages([string]$Root) { if (-not (Test-Path -LiteralPath $Root)) { return @() } @(Get-ChildItem -LiteralPath $Root -File | Where-Object { $_.Name -match '^\d{4}-\d{2}-\d{2}_[a-z0-9-]+_(desktop|phone)\.jpg$' } | Sort-Object Name -Descending) } function Invoke-MMPrune($Paths, $State, [int]$Keep) { if ($script:DryRunMode) { return } $n = 0 foreach ($f in Get-MMCachedImages $Paths.Root) { $n++ if ($n -le $Keep) { continue } if ($f.FullName -eq $State.applied_image -or $f.FullName -eq $State.image) { continue } Remove-Item -LiteralPath $f.FullName -Force } } # ------------------------------------------------------------- wallpaper --- function Set-MMWallpaper([string]$Path, [string]$Mode) { $styles = @{ fill = @('10', '0'); fit = @('6', '0'); stretch = @('2', '0'); center = @('0', '0'); tile = @('0', '1') } $style = $styles[$Mode] if ($script:DryRunMode) { Note "dry-run: WallpaperStyle=$($style[0]) TileWallpaper=$($style[1]); SystemParametersInfo(SPI_SETDESKWALLPAPER, $Path)" return $true } $key = 'HKCU:\Control Panel\Desktop' Set-ItemProperty -Path $key -Name WallpaperStyle -Value $style[0] Set-ItemProperty -Path $key -Name TileWallpaper -Value $style[1] if (-not ('MMWallpaper.Native' -as [type])) { Add-Type -Namespace MMWallpaper -Name Native -MemberDefinition @' [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool SystemParametersInfo(int uiAction, int uiParam, string pvParam, int fWinIni); '@ } # SPI_SETDESKWALLPAPER = 0x14; SPIF_UPDATEINIFILE | SPIF_SENDCHANGE = 0x03 $ok = [MMWallpaper.Native]::SystemParametersInfo(0x14, 0, $Path, 0x03) if (-not $ok) { Warn ("SystemParametersInfo failed (error {0})" -f [Runtime.InteropServices.Marshal]::GetLastWin32Error()) } return [bool]$ok } # ------------------------------------------------------------- commands --- function Get-MMNewestCached($Paths, $State) { if ($State.image -and (Test-MMJpeg $State.image)) { return $State.image } foreach ($f in Get-MMCachedImages $Paths.Root) { if (Test-MMJpeg $f.FullName) { return $f.FullName } } return $null } function Invoke-MMFallback($Paths, $State, $Settings, [string]$Reason) { Note "${Reason}; keeping the cached wallpaper." $img = Get-MMNewestCached $Paths $State if (-not $img) { Note 'No artwork cached yet; will try again on the next run.'; return 0 } if ($State.applied_image -ne $img -or $script:ForceMode) { if (-not (Set-MMWallpaper $img $Settings.Mode)) { return 3 } $State.applied_image = $img if (-not $script:DryRunMode) { Save-MMState $State $Paths.State } Note "Wallpaper set from cache: $img" } return 0 } function Enter-MMLock($Paths) { $script:MMMutex = New-Object System.Threading.Mutex($false, 'Local\MorningMasterpieceWallpaper') try { return $script:MMMutex.WaitOne(0) } catch [System.Threading.AbandonedMutexException] { return $true } } function Exit-MMLock { if ($script:MMMutex) { try { $script:MMMutex.ReleaseMutex() } catch { } $script:MMMutex.Dispose() $script:MMMutex = $null } } function Invoke-MMUpdate($Paths, $Settings) { if (-not $script:DryRunMode) { $null = New-Item -ItemType Directory -Force -Path $Paths.Root } if (-not (Enter-MMLock $Paths)) { Say 'Another run is in progress; nothing to do.'; return 0 } try { $state = Read-MMState $Paths.State if (-not $Paths.Api.StartsWith('https://', [StringComparison]::Ordinal)) { Warn 'MM_WALLPAPER_API_URL must be an https:// URL' return Invoke-MMFallback $Paths $state $Settings 'Could not reach morningmasterpiece.com' } $tmpJson = [IO.Path]::GetTempFileName() try { try { Invoke-MMDownload $Paths.Api $tmpJson 20 $text = [IO.File]::ReadAllText($tmpJson, [Text.Encoding]::UTF8) } catch { return Invoke-MMFallback $Paths $state $Settings 'Could not reach morningmasterpiece.com' } $meta = Read-MMMeta $text if ($null -eq $meta) { return Invoke-MMFallback $Paths $state $Settings 'today.json from morningmasterpiece.com was not usable' } $url = Select-MMImageUrl $meta $Settings.Variant if ($null -eq $url) { return Invoke-MMFallback $Paths $state $Settings "today.json has no usable $($Settings.Variant) image" } $img = Join-Path $Paths.Root ("{0}_{1}_{2}.jpg" -f $meta.Date, $meta.Slug, $Settings.Variant) $new = $false if ($script:ForceMode -or -not (Test-MMJpeg $img)) { Say "New artwork for $($meta.Date): $(Get-MMCredit $meta)" if ($script:DryRunMode) { Note "dry-run: download $url -> $img" } else { $part = "$img.part" try { Invoke-MMDownload $url $part 180 } catch { if (Test-Path -LiteralPath $part) { Remove-Item -LiteralPath $part -Force } return Invoke-MMFallback $Paths $state $Settings "Could not download today's image" } if (-not (Test-MMJpeg $part)) { Remove-Item -LiteralPath $part -Force Warn 'downloaded file is not a JPEG image; ignoring it' return Invoke-MMFallback $Paths $state $Settings "Could not download today's image" } Move-MMFile $part $img } $new = $true } else { Say "Artwork unchanged ($($meta.Date), $($meta.Slug)): no download needed." } if (-not $script:DryRunMode) { [IO.File]::WriteAllText($Paths.Meta, $text, (New-Object Text.UTF8Encoding $false)) } $state.publish_date = $meta.Date $state.slug = $meta.Slug $state.variant = $Settings.Variant $state.image = $img $rc = 0 if ($new -or $script:ForceMode -or $state.applied_image -ne $img) { if (Set-MMWallpaper $img $Settings.Mode) { $state.applied_image = $img if ($script:DryRunMode) { Note 'Dry run: the wallpaper would be set.' } else { Note "Wallpaper set: $(Get-MMCredit $meta)" } } else { Warn 'could not set the wallpaper' $rc = 3 } } else { Say 'Wallpaper is already set.' } if (-not $script:DryRunMode) { Save-MMState $state $Paths.State } Invoke-MMPrune $Paths $state $Settings.Keep return $rc } finally { Remove-Item -LiteralPath $tmpJson -Force -ErrorAction SilentlyContinue } } finally { Exit-MMLock } } # Today's metadata, live if possible, else the cached copy. Sets $script:MetaSource. function Get-MMAnyMeta($Paths) { $script:MetaSource = 'live' $tmp = [IO.Path]::GetTempFileName() try { try { if ($Paths.Api.StartsWith('https://', [StringComparison]::Ordinal)) { Invoke-MMDownload $Paths.Api $tmp 20 $m = Read-MMMeta ([IO.File]::ReadAllText($tmp, [Text.Encoding]::UTF8)) if ($m) { return $m } } } catch { } } finally { Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue } $script:MetaSource = 'cached' if (Test-Path -LiteralPath $Paths.Meta) { return Read-MMMeta ([IO.File]::ReadAllText($Paths.Meta, [Text.Encoding]::UTF8)) } return $null } function Invoke-MMInfo($Paths) { $m = Get-MMAnyMeta $Paths if ($null -eq $m) { Warn "could not fetch today's artwork and nothing is cached yet"; return 1 } if ($script:MetaSource -eq 'cached') { Note '(offline: showing the last cached artwork)' } Note "Morning Masterpiece for $($m.Date)" $t = $m.Title if (-not $t) { $t = 'Untitled' } Note "Title: $t" if ($m.Artist) { Note "Artist: $($m.Artist)" } if ($m.Year) { Note "Date: $($m.Year)" } if ($m.Medium) { Note "Medium: $($m.Medium)" } if ($m.Museum) { Note "Museum: $($m.Museum)" } if ($m.Credit) { Note "Credit: $($m.Credit)" } if ($m.Story) { Note "Story: $($m.Story)" } Note "Page: $(Add-MMUtm $m.Page)" return 0 } function Invoke-MMOpen($Paths) { $m = Get-MMAnyMeta $Paths if ($m) { $link = Add-MMUtm $m.Page } else { $link = Add-MMUtm "$Site/" } if ($script:DryRunMode) { Note "dry-run: open $link"; return 0 } Start-Process $link Say "Opening $link" return 0 } function Invoke-MMInstall($Paths, $Settings) { $self = $PSCommandPath if (-not $self -or -not (Test-Path -LiteralPath $self)) { Warn 'cannot find the script file; download it and run it with -File' return 2 } if ($script:DryRunMode) { Note "dry-run: copy $self -> $($Paths.Script)" Note "dry-run: register scheduled task '$TaskName' (at logon + hourly)" return Invoke-MMUpdate $Paths $Settings } $null = New-Item -ItemType Directory -Force -Path $Paths.Bin if ([IO.Path]::GetFullPath($self) -ne [IO.Path]::GetFullPath($Paths.Script)) { Copy-Item -LiteralPath $self -Destination $Paths.Script -Force } Note "Installed $($Paths.Script)" if ($Mode) { Set-MMConfigValue $Paths.Config 'mode' $Mode } if ($Phone) { Set-MMConfigValue $Paths.Config 'variant' 'phone' } if ($Desktop) { Set-MMConfigValue $Paths.Config 'variant' 'desktop' } $rc = Invoke-MMUpdate $Paths $Settings $user = [Security.Principal.WindowsIdentity]::GetCurrent().Name # conhost --headless keeps a console window from flashing up every hour. $psArgs = "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$($Paths.Script)`" update -Quiet" $action = New-ScheduledTaskAction -Execute "$env:SystemRoot\System32\conhost.exe" ` -Argument "--headless `"$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe`" $psArgs" $logon = New-ScheduledTaskTrigger -AtLogOn -User $user $minute = Get-Random -Minimum 0 -Maximum 60 $hourly = New-ScheduledTaskTrigger -Daily -At ([datetime]::Today.AddMinutes($minute)) $hourly.Repetition = (New-ScheduledTaskTrigger -Once -At ([datetime]::Today) ` -RepetitionInterval (New-TimeSpan -Hours 1) -RepetitionDuration (New-TimeSpan -Hours 23 -Minutes 59)).Repetition $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -MultipleInstances IgnoreNew $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Limited $null = Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger @($logon, $hourly) -Settings $settings ` -Principal $principal -Description "Sets today's Morning Masterpiece artwork as the wallpaper. $Site" -Force Note "Scheduled: Task Scheduler task '$TaskName' (at logon and hourly; catches up after missed runs)." return $rc } function Set-MMConfigValue([string]$Path, [string]$Key, [string]$Value) { $lines = @() if (Test-Path -LiteralPath $Path) { $lines = @([IO.File]::ReadAllLines($Path) | Where-Object { $_ -notmatch "^\s*$Key\s*=" }) } else { $lines = @("# $Prog configuration (see README)") } $lines += "$Key = $Value" [IO.File]::WriteAllLines($Path, [string[]]$lines) } function Invoke-MMUninstall($Paths) { if ($script:DryRunMode) { Note "dry-run: unregister scheduled task '$TaskName'"; return 0 } if ((Get-Command Get-ScheduledTask -ErrorAction SilentlyContinue) -and (Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue)) { Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false Note "Removed scheduled task '$TaskName'" } if (Test-Path -LiteralPath $Paths.Bin) { Remove-Item -LiteralPath $Paths.Bin -Recurse -Force; Note "Removed $($Paths.Bin)" } if ($Purge) { if (Test-Path -LiteralPath $Paths.Root) { Remove-Item -LiteralPath $Paths.Root -Recurse -Force } Note "Removed cache and config ($($Paths.Root))." } else { Note "Kept cached images in $($Paths.Root) (use -Purge to delete them)." } Note 'Uninstalled. Your current wallpaper was left as it is.' return 0 } function Invoke-MMStatus($Paths, $Settings) { $state = Read-MMState $Paths.State Note "Morning Masterpiece wallpaper $MMVersion (Windows)" Note "Mode: $($Settings.Mode), $($Settings.Variant) image, keep $($Settings.Keep)" Note "Cache: $($Paths.Root) ($(@(Get-MMCachedImages $Paths.Root).Count) images)" if ($state.publish_date) { Note "Artwork: $($state.publish_date) $($state.slug)" } $sched = 'not installed' if (Get-Command Get-ScheduledTask -ErrorAction SilentlyContinue) { $task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue if ($task) { $sched = "Task Scheduler task '$TaskName' ($($task.State))" } } Note "Schedule: $sched" return 0 } function Invoke-Main { if ($Version) { Write-Host "$Prog $MMVersion"; return 0 } if ($Command -eq 'version') { Write-Host "$Prog $MMVersion"; return 0 } if ($Command -eq 'help') { Get-Help $PSCommandPath -Detailed | Out-Host; return 0 } if (-not $env:LOCALAPPDATA) { Warn 'LOCALAPPDATA is not set'; return 2 } try { [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 } catch { } $script:QuietMode = [bool]$Quiet $script:DryRunMode = [bool]$DryRun $script:ForceMode = [bool]$Force $paths = Get-MMPaths $settings = New-MMSettings $ok = Read-MMConfig $settings $paths.Config $ok = (Read-MMEnvSettings $settings) -and $ok if ($Mode) { $settings.Mode = $Mode } if ($Phone) { $settings.Variant = 'phone' } if ($Desktop) { $settings.Variant = 'desktop' } if (-not $ok) { return 2 } $cmd = $Command if ($Info) { $cmd = 'info' } switch ($cmd) { 'update' { return Invoke-MMUpdate $paths $settings } 'install' { return Invoke-MMInstall $paths $settings } 'uninstall' { return Invoke-MMUninstall $paths } 'info' { return Invoke-MMInfo $paths } 'open' { return Invoke-MMOpen $paths } 'status' { return Invoke-MMStatus $paths $settings } } return 2 } # Tests dot-source this file with MM_WALLPAPER_LIB=1 to call the functions. if ($env:MM_WALLPAPER_LIB -ne '1') { exit (Invoke-Main) }