greatDismal.psm1

function get-DismalLockscreen{
    param (
    [string[]]$adjectives = @(),
    [string[]]$nouns = @(),
    [string]$dismalFolder = (join-path $env:APPDATA "\great dismal\"),
    [string]$logfile = (join-path $dismalFolder "log.txt"),
    [int]$safeSearch = 0,
    [switch]$showpic,
    [switch]$showLog,
    [switch]$noLog,
    [switch]$install,
    [switch]$uninstall,
    [switch]$checkForUpdates
    )
    
    write-host "logfile at $logfile"
    $lastUpdate = $false;
    $shouldUpdate = $false;
    if (test-path $logfile){
        $lastUpdate = (Get-Content $logfile) | Where-Object {$_ -match "([^-]*)->\s*UpdateCheck"}
        if ($lastUpdate){
            # check every week, future versions might slow this down a bit
            $shouldUpdate = (get-date $Matches[1]) -lt ((get-date).AddDays(-7))
        } else {
            $shouldUpdate = $true;
        }
    } 
    if ($shouldUpdate) {
        write-GDLog "updating GD" -logfile $logfile
        Update-module "GreatDismal"
    }

    # do the stuffs
    # installation hoo-hah
    if (! (test-path $dismalFolder)){mkdir $dismalFolder}
    $dismalPic = Join-Path $dismalFolder "greatDismal.jpg"
    
    if ($install){ 
        install-GreatDismal -adjectives $adjectives -nouns $nouns -dismalFolder $dismalFolder -scriptPath $scriptPath -logfile $logfile -nolog $nolog -safeSearch = $safeSearch
    } 
    
    if ($showpic) {
        #user wants to see the current pic
        Invoke-Item $dismalPic
    } elseif ($showLog){
        #user wants to see the log
        Get-Content $logfile
    } elseif ($uninstall){
        #user wants to see the log
        uninstall-GreatDismal -logfile $logfile -dismalFolder $dismalFolder;
    }else {
        #get the pic and set the screen
        get-dismalpicFortheDay -adjectives $adjectives -nouns $nouns -dismalPic $dismalPic -logFile $logfile -nolog $nolog  -safeSearch $safeSearch;
    }
}

function get-dismalpicFortheDay {
    param (
    [string[]]$adjectives = @(),
    [string[]]$nouns = @(),
    [string]$dismalPic,
    [string]$logfile,
    [bool]$nolog,
    [int]$safeSearch = 0
    )
    Write-Host "getting some fresh despair for you"
    
    $key = "48efbaf66f9bf4b1bf2d0b04c46b02b1"
    $scrt = "9f5ab5c8abc77684"
    if ($adjectives.Length -eq 0){
        $adjectives = @(
        "dull", "ugly", "boring",
        "depressing", "abandoned", "neglected",
        "broken", "dismal", "wrecked",
        "crumbling", "decaying", "rotting",
        "ruined", "brutalist", "gloomy", 
        "dreary", "trashed", "dumped", 
        "stained", "desolate", "miserable",
        "muddy", "shattered"
        );
    }
    if ($nouns.Length -eq 0){
        $nouns=@(
        "landscape", "trash", "rubbish",
        "ruin", "wasteland", "building",
        "streetscape", "junk", "office",
        "apartment block", "dumpster", 
        "cubicle", "freeway", "factory",
        "cell", "dump", "garbage",
        "swamp", "concrete", "bunker",
        "vomit", "grave", "tower", 
        "concrete", "rubble", "field", 
        "paddock", "road", "town", "bin", 
        "puddle", "spill", "despair", "mud"
        );
    }
    $attempts = 0;
    if ($safeSearch -gt 0){
        $safeSearchStr = "&safe_search="+$safeSearch
    } else {
        $safeSearchStr = ""
    }

    while ((! $photogURL) -and ($attempts -lt 64)){
        $picfortheday = @($adjectives[(Get-Random($adjectives.Length))], $nouns[(Get-Random($nouns.Length))]) -join ",";
        $result = Invoke-RestMethod -URi  ("http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={0}&secret={1}&tags={2}&tag_mode=all&sort=interestingness-desc&media=photos&format=rest&extras=url_k{3}" -f $key, $scrt, $picfortheday, $safeSearchStr) -Method Get
        $pics = $result.rsp.photos.photo;
        if ($pics.length -gt 0){
            write-GDLog ("looking for pics of {0}, found {1}" -f ($picfortheday.replace(",", " and ")),  $pics.length) -logFile $logfile -nolog $nolog
            $randompics = 0;
            while (($null -eq $photogURL) -and ($randompics -lt $pics.length) ){
                $randoPhoto = Get-Random $pics.Length;
                $photogURL = $pics[$randoPhoto].url_k;
                $randompics++
            }
            if ($null -ne $photogURL){
                write-GDLog ("found a photo at {0}" -f $photogURL) -logFile $logfile -nolog $nolog
            } else {
                write-GDLog ("no url found. Trying another search") -logFile $logfile -nolog $nolog
            }
            $photoTitle = $pics[$randoPhoto].title;
        } else {
            write-GDLog ("{0} - didn't find any pics of {1}" -f (get-date), ($picfortheday.replace(",",  " and "))) -logFile $logfile -nolog $nolog
        }
        $attempts++;
    }
    (New-Object Net.webclient).DownloadFile($photogURL, $dismalPic)
    write-GDLog ("downloaded `"{0}`"" -f $photoTitle) -logFile $logfile -nolog $nolog
    
    Set-LockscreenWallpaper -LockScreenImageValue $dismalPic -logfile $logfile;
}

function Set-LockscreenWallpaper {
    # this was adapted from
    # https://abcdeployment.wordpress.com/2017/04/20/how-to-set-custom-backgrounds-for-desktop-and-lockscreen-in-windows-10-creators-update-v1703-with-powershell/
    # The Script sets custom background Images for the Lock Screen by leveraging the new feature of PersonalizationCSP that is only available in
    # the Windows 10 v1703 aka Creators Update and later build versions #
    # Applicable only for Windows 10 v1703 and later build versions #
    
    param(
    [string]$LockScreenImageValue,
    [string]$logfile
    )
    
    $RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP"
    
    $LockScreenPath = "LockScreenImagePath"
    $LockScreenStatus = "LockScreenImageStatus"
    $LockScreenUrl = "LockScreenImageUrl"
    $StatusValue = "1"
    
    If (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
        
        IF(!(Test-Path $RegKeyPath))
        
        {
            
            New-Item -Path $RegKeyPath -Force | Out-Null
            
            New-ItemProperty -Path $RegKeyPath -Name $LockScreenStatus -Value $StatusValue -PropertyType DWORD -Force | Out-Null
            New-ItemProperty -Path $RegKeyPath -Name $LockScreenPath -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null
            New-ItemProperty -Path $RegKeyPath -Name $LockScreenUrl -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null
            
        }
        
        ELSE {
            
            New-ItemProperty -Path $RegKeyPath -Name $LockScreenStatus -Value $value -PropertyType DWORD -Force | Out-Null
            New-ItemProperty -Path $RegKeyPath -Name $LockScreenPath -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null
            New-ItemProperty -Path $RegKeyPath -Name $LockScreenUrl -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null
        }
    } else {
        write-GDLog ("Error: not running as Admin, can't set the registry.") -logFile $logfile -nolog $nolog
    }
}

function install-GreatDismal {
    param(
    [String]$logfile = (join-path $env:APPDATA "\great dismal\log.txt"),
    [string]$dismalFolder = (join-path $env:APPDATA "\great dismal\"),
    [int]$safeSearch = 0,
    [string[]]$adjectives = @(),
    [string[]]$nouns = @(),
    [bool]$nolog,
    [bool]$phoneHome
    )
    # check to see if user is admin
    if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
        # is admin, we're good to install
        $divider = "`n" + ("-" * (Get-Host).ui.rawui.windowsize.width) + "`n"; # trick to make a row of dashes the width of the window
        write-host ($divider) -foregroundColor "yellow"
        Write-Host "This will install GreatDismal on your machine and it will download a random dismal login screen every time you log in" -ForegroundColor DarkYellow
        Write-Host "Note that the contents of the pictures are beyond the control of the developer, and may be " -NoNewline -ForegroundColor DarkYellow
        Write-Host "unsafe for work." -ForegroundColor DarkYellow -BackgroundColor DarkRed
        write-host ($divider) -foregroundColor "yellow"

        # define the workstation unlock as the trigger
        $stateChangeTrigger = Get-CimClass -Namespace ROOT\Microsoft\Windows\TaskScheduler -ClassName MSFT_TaskSessionStateChangeTrigger
        $trigger = New-CimInstance -CimClass $stateChangeTrigger -Property @{
            StateChange = 8  # TASK_SESSION_STATE_CHANGE_TYPE.TASK_SESSION_UNLOCK (taskschd.h)
        } -ClientOnly
        
        # Create a task scheduler event
        $argument = "-WindowStyle Hidden -command `"import-module 'GreatDismal'; get-DismalLockscreen -logfile {0} -dismalFolder {1}{2}{3}{4}{5}`"" -f `
            $logfile, `
            $dismalFolder, `
            $(if ($adjectives.Length -gt 0){" -adjectives ({0})" -f ($adjectives -join ", ")} else {""}), `
            $(if ($nouns.Length -gt 0){" -nouns ({0})" -f ($nouns -join ", ")} else {""}), `
            $(if ($phoneHome){" -checkForUpdates"} else {""}), `
            $(if ($safeSearch -gt 0){" -safeSearch " + $safeSearch} else {""})
        $action = New-ScheduledTaskAction -id "GreatDismal" -execute 'Powershell.exe' -Argument $argument
        $settings = New-ScheduledTaskSettingsSet -Hidden -StartWhenAvailable -RunOnlyIfNetworkAvailable
        Write-Host "for this script to work it needs elevated privileges" -BackgroundColor DarkBlue
        $Credential = Test-Credential
        if ($Credential){
            # actually install the shiz
            Write-Host "Username checks out." -ForegroundColor Green
            write-GDLog "Unregistering existing scheduled task" -logfile $logfile -nolog $nolog
            Unregister-ScheduledTask -TaskName "greatDismal" -ErrorAction SilentlyContinue
            Register-ScheduledTask `
            -TaskName "greatDismal" `
            -User $Credential.username `
            -Action $action `
            -Settings $settings `
            -Trigger $trigger -RunLevel Highest `
            -Password $Credential.GetNetworkCredential().Password `
            -taskPath "\pureandapplied\"
        }
        if ($? -and (Get-ScheduledTask -TaskName "GreatDismal" -ErrorAction SilentlyContinue)){
            write-GDLog "GreatDismal is installed" -colour "Green" -logFile $logfile -nolog $nolog
        } else {
            throw "Bollocks. Something went wrong. Computers suck."
        }
    }  else {
        # not admin
        Write-Host "You need run this script as an Admin to install it" -BackgroundColor Red -ForegroundColor Yellow
        throw "Computer says no."
    }
}

function uninstall-GreatDismal {
    param(
    [string]$logfile,
    [string]$dismalFolder
    )
    if (([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
        $RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP"
        remove-item -Path $RegKeyPath -Force -Recurse| Out-Null;
        Unregister-ScheduledTask -TaskName "greatDismal" -ErrorAction SilentlyContinue;
        Remove-Item  $dismalFolder -Recurse -ErrorAction SilentlyContinue;
        $scriptPath = (get-item $myInvocation.ScriptName).Directory
        # remove-module doesn't seem to work for psgallery modules. So we do it manually
        # just check that we're actually removing the greatdismal folder
        if ($scriptPath.name -eq "GreatDismal"){
            Write-host "You have to manually remove the module now. Just delete the GreatDismal folder." -BackgroundColor Yellow -ForegroundColor Red
            Invoke-Item $scriptPath; #open the folder containing the module folder (usually ~\Documents\WindowsPowershell\Modules)
        }
    } else {
        Write-host "you need to run this script as admin to uninstall it" -BackgroundColor Red -ForegroundColor Yellow
        throw "Computer says no."
    }
    
}
function Test-Credential {
    # check password, allowing multiple attemps
    $againWithThePassword = $true;
    $usernameChecksOut = $false;
    Add-Type -AssemblyName System.DirectoryServices.AccountManagement
    $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext('machine',$env:COMPUTERNAME)
    
    while ((! $usernameChecksOut) -and $againWithThePassword){
        $Credential = Get-Credential -ErrorAction SilentlyContinue
        if ($null -eq $Credential){
            Write-Warning "You didn't give me any credentials. I can't help you if you won't help me."
            $againWithThePassword = ((read-host "Again with the password? Y/n").ToLower() -ne "n")
        } else {
            $usernameChecksOut = $DS.ValidateCredentials($Credential.UserName, $Credential.GetNetworkCredential().Password)
            if ($usernameChecksOut){
                return $Credential
            } else {
                Write-Warning "Username and / or password is incorrect. Soz.";
                $againWithThePassword = ((read-host "Again with the password? Y/n").ToLower() -eq "n")
            }
        }
        if (! $againWithThePassword){
            return $false
        }
        Start-Sleep 1
    }
}

function write-GDLog  {
    param (
    [string]$Msg,
    [string]$colour = "White",
    [string]$logfile, 
    [bool]$nolog
    )

    if ((-not $nolog) -and ($null -ne $logfile)){
        $date = Get-date -f "dd/MM/yyyy HH:mm:ss"
        if (! (test-path $logfile )){set-content $logfile "The Great Dismal Log"}
        # trim the log if it gets too long 64k is long enough right?
        if ((get-item $logfile).length -gt 64kb){
            # get the last 20 lines
            $oldlog = (Get-Content $logfile)[-20..-1] 
            # carry over the last update check
            $lastUpdate = ""
            $lastUpdate = (Get-Content $logfile) | Where-Object {$_ -match "([^-]*)->\s*UpdateCheck"}
            if ($lastUpdate){$lastUpdate = $lastUpdate[-1]}
            Set-Content $logfile ("The Great Dismal Log`n" + $date + "-> " + "Trimmed log")
            Add-Content $logfile $oldlog
            Add-Content $logfile $lastUpdate
        }
        add-content $logfile ("" + $date + "-> " + $msg)
    }
    Write-Host $Msg -foregroundColor $colour
}