Skip to content

Instantly share code, notes, and snippets.

@loopyd
Last active August 10, 2023 06:53
Show Gist options
  • Save loopyd/ae5f5a50b679b98e0899e4748e8f5ce0 to your computer and use it in GitHub Desktop.
Save loopyd/ae5f5a50b679b98e0899e4748e8f5ce0 to your computer and use it in GitHub Desktop.
[Powershell] Blep.ps1 - ShadowPC setup in the Draconic Way.
<#
----------------------------------------------
Blep - Draconic keystroke saver for ShadowPC
==============================================
Author: LoopyD <loopyd@github.com>
Version: 1.1.0
License: Unlicense (https://unlicense.org)
----------------------------------------------
INRODUCTION
============
This script's intent is to configure and set up a ShadowPC environment for development work in an unattended way, and save time over Shadow resets by allowing a developer to have their coffee and watch the world burn around them instead of sitting in front of their Windows Terminal typing commands.
GET A SHADOW
============
If you don't have a Shadow, you can use MY REFERRAL CODE for a 5 Euro discount: 814181C @ https://shadow.tech
RUN THE SCRIPT
==============
Download the script to any folder in your UserProile, and then open up a Powershell 2.0 terminal to it and run it.
CUSTOMIZE THE SCRIPT
====================
You can also add a blep-config.json file to go next to the script to customize the script's behavior. See the CONFIGURATION section for more details.
BLEP API
========
This script contains a full API that you can use to make non-destructive changes to your operating system in a data-driven way. You can use it to install packages, configure the registry, and modify the environment. You can also use the Blep API to create your own custom unattended scripts. The API is documented in the FUNCTONS section of this script.
SPECIAL THANKS
==============
- OpenAI for GPT-4, which I used to help me speed up some trickier refactoring tasks.
- Microsoft for Powershell 7, which I used to write this script.
- GitHub Copilot for Visual Studio Code, which I used to help me write code comments, and speed up some repetative coding tasks.
- The Powershell community for their help and support.
- The ShadowPC community for their help and support, and for just being #ShadowTeam !
#>
<##############################################
# PRE-FLIGHT CHECKS
##############################################>
# Block non-admin execution of the script. We need admin to install chocolatey, winget, and scoop, to make registry changes, and to install some packages.
$IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
if (-not $IsAdmin) {
Write-Error "You must run this script as an Administrator!"
exit 1
}
# Check and ensure the script is running as Powershell 7, if it is not, install it silently and reloaunch the script as Powershell 7. We use a legacy Powershell version to install Powershell 7 if we need to, so we use some old backwards-compatible code to do this.
if ($PSVersionTable.PSVersion.Major -lt 7) {
if (-not (Test-Path pwsh)) {
Write-Host -ForegroundColor Green $("-" * 80)
Write-Warning -Message "Powershell 7 is not installed, and required by the script, attempting to install it now..."
$url = "https://github.com/PowerShell/PowerShell/releases/download/v7.3.6/PowerShell-7.3.6-win-x64.msi"
$output = "$env:TEMP\PPowerShell-7.3.6-win-x64.msi"
$client = New-Object System.Net.WebClient
$client.DownloadFile($url, $output)
$arguments = "/i `"$output`" /quiet /qn /norestart"
Start-Process "msiexec.exe" -ArgumentList $arguments -Wait
if ($LASTEXITCODE -ne 0) {
Write-Error -Message "Powershell 7 installation failed, exiting script"
Write-Host -ForegroundColor Green $("-" * 80)
exit 1
}
}
Write-Warning -Message "This script requires Powershell 7 or higher to execute, attempting to relaunch script as Powershell 7..."
Write-Host -ForegroundColor Green $("-" * 80)
Start-Process pwsh -ArgumentList "-File $PSCommandPath" -Verb RunAs
exit $LASTEXITCODE
}
# Create a Powershell profile file if it doesn't exist.
if (-not $(Test-Path -Path "$PROFILE")) {
New-Item -ItemType File -Path "$PROFILE"
}
# Display the title. Its base64 encoded to provide a minor annoyance to changing it. You can unpack it with a base64 decoder
$title = "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgX19fX19fX19fX19fX18gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAsPT09OicuLCAgICAgICAgICAgIGAtLl8gICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBgOi5gLS0tLl9fICAgICAgICAgYC0uXyAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYDouICAgICBgLS0uICAgICAgICAgYC4gICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwuICAgICAgICBgLiAgICAgICAgIGAuICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICgsLCgsICAgIFwuICAgICAgICAgYC4gICBfX19fLC1gLiwgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgKCwnICAgICBgLyAgIFwuICAgLC0tLl9fX2AuJyAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICwgICwnICAsLS0uICBgLCAgIFwuOycgICAgICAgICBgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgIGB7RCwgeyAgICBcICA6ICAgIFw7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICBCTEVQID09PS0gICAgICBWLCwnICAgIC8gIC8gICAgLy8gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgIEFVVE9JTlNUQUxMRVIgICAgIGo7OyAgICAvICAsJyAsLS8vLiAgICAsLS0tLiAgICAgICwgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgXDsnICAgLyAgLCcgLyAgXyAgXCAgLyAgXyAgXCAgICwnLyAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBcICAgYCcgIC8gXCAgYCcgIC8gXCAgYC4nIC8gICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBgLl9fXywnICAgYC5fXywnICAgYC5fXywnICANCg0KPS09LT0tPS09LT0tPS09LT0tPS09LT0tPS09LT0tPS09LT0tPS09LT0tPS09LT0tPS09LT0tPS09LT0NCkkgTiBTIFQgQSBMIEwgQSBUIEkgTyBOICBJIE4gIFQgSCBFICBEIFIgQSBDIE8gTiBJIEMgIFcgQSBZDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLUxELQ0K"
Write-Host -ForegroundColor Red $([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($title)))
<##############################################
# CONFIGURATION
##############################################>
<#
Load the blep-config.json file if it exists for easy user
customization, otherwise use the built-in defaults.
The json file has the following format:
{
"ChocolateyPackages": [
"vcredist-all",
"nerd-fonts-SourceCodePro",
...
],
"WinGetPackages": [
"JanDeDobbeleer.OhMyPosh",
"ffmpeg",
...
],
"ScoopPackages": [
"neofetch"
],
"RegistryEntries": [
{
"path": "HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsUpdate",
"key": "TargetReleaseVersion",
"value": "1",
"keytype": "DWord"
},
...
],
"PyEnvVersions": [
{
"version": "3.9.13",
"default": false
},
...
]
}
You can customize your own blep-config.json file by copying the
example, and filling it out with what you'd like in a file named
"blep-config.json" that sits beside the script.
#>
$configPath = "$PSScriptRoot\blep-config.json"
if (Test-Path $configPath) {
$jsonContent = Get-Content $configPath
$config = $jsonContent | ConvertFrom-Json
$ChocolateyPackages = $config.ChocolateyPackages
$WinGetPackages = $config.WinGetPackages
$ScoopPackages = $config.ScoopPackages
$RegistryEntries = $config.RegistryEntries
$PyEnvVersions = $config.PyEnvVersions
} else {
Write-Warning -Message "blep-config.json file not found in the script directory, we will be using built in defaults."
# You can edit this string array to add or remove packages from the install list for chocolatey.
$ChocolateyPackages = @(
'vcredist-all',
'nerd-fonts-SourceCodePro',
'git',
'visualstudio-installer',
'visualstudio2019buildtools',
'visualstudio2019community',
'visualstudio2019-workload-azure',
'visualstudio2019-workload-nativedesktop',
'visualstudio2019-workload-nativecrossplat',
'visualstudio2019-workload-nativemobile',
'visualstudio2019-workload-nativegame',
'visualstudio2019-workload-manageddesktop',
'visualstudio2019-workload-managedgame',
'visualstudio2019-workload-vctools',
'visualstudio2019-workload-universal',
'visualstudio2019-workload-netweb',
'visualstudio2019-workload-data',
'visualstudio2019-workload-databuildtools',
'visualstudio2019-workload-universalbuildtools',
'visualstudio2019-workload-manageddesktopbuildtools',
'visualstudio2019-workload-xamarinbuildtools',
'visualstudio2019-workload-azurebuildtools',
'visualstudio2022buildtools',
'visualstudio2022community',
'visualstudio2022-workload-azure',
'visualstudio2022-workload-nativedesktop',
'visualstudio2022-workload-nativecrossplat',
'visualstudio2022-workload-nativemobile',
'visualstudio2022-workload-nativegame',
'visualstudio2022-workload-manageddesktop',
'visualstudio2022-workload-managedgame',
'visualstudio2022-workload-vctools',
'visualstudio2022-workload-universal',
'visualstudio2022-workload-netweb',
'visualstudio2022-workload-data',
'visualstudio2022-workload-databuildtools',
'visualstudio2022-workload-universalbuildtools',
'visualstudio2022-workload-manageddesktopbuildtools',
'visualstudio2022-workload-xamarinbuildtools',
'visualstudio2022-workload-azurebuildtools',
'vscode',
"7zip"
)
# You can edit this string array to add or remove packages from the install list for winget.
$WinGetPackages = @(
'JanDeDobbeleer.OhMyPosh',
'ffmpeg',
"Microsoft.WindowsTerminal",
"Microsoft.PowerToys",
"Microsoft.PowerShell"
)
# You can edit this string array to add or remove packages from the install list for scoop.
$ScoopPackages = @(
'neofetch'
)
# You can edit this array of hashtables to add or update registry entries
$RegistryEntries = @(
[hashtable]@{
path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
key = 'TargetReleaseVersion'
value = '1'
keytype = 'DWord'
},
[hashtable]@{
path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'
key = 'TargetReleaseVersionInfo'
value = '22H2'
keytype = 'String'
},
[hashtable]@{
path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize'
key = 'AppsUseLightTheme'
value = '0'
keytype = 'DWord'
},
[hashtable]@{
path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize'
key = 'ColorPrevalence'
value = '1'
keytype = 'DWord'
},
[hashtable]@{
path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize'
key = 'EnableTransparency'
value = '1'
keytype = 'DWord'
},
[hashtable]@{
path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell'
key = 'TabletMode'
value = '0'
keytype = 'DWord'
},
[hashtable]@{
path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ImmersiveShell'
key = 'ConvertibleSlateModePromptPreference'
value = '0'
keytype = 'DWord'
},
[hashtable]@{
path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
key = 'HideFastUserSwitching'
value = '1'
keytype = 'DWord'
}
)
# You can add specific python versions here to use with pyenv.
$PyEnvVersions = @(
[hashtable]@{
version = '3.9.13'
default = $false
},
[hashtable]@{
version = '3.10.11'
default = $true
},
[hashtable]@{
version = '3.11.4'
default = $false
}
)
}
<##############################################
# FUNCTIONS
##############################################>
<#
.SYNOPSIS
This function allows you to add or update registry entries.
.DESCRIPTION
This function allows you to add or update registry entries. You can specify the registry entries as a hashtable. This function offers a data-driven approach to editig your registry non-destructively.
.PARAMETER RegistryEntries
An array of hashtables containing the registry entries to add or update.
.EXAMPLE
$RegistryEntries = @(
[hashtable]@{
path = "HKLM:\PATH\to\registry\key1"
key = "keyname"
value = "1"
keytype = "DWord"
},
[hashtable]@{
path = "HKLM:\PATH\to\registry\key2"
key = "keyname"
value = "FF FF 01 17 00 FC FF FF"
keytype = "QWord"
},
)
Add-RegistryEntries -RegistryEntries $RegistryEntries
.NOTES
This function does not offer the capability to delete registry entries, as it is designed around working non-destructively.
#>
function Add-RegistryEntries {
param (
[hashtable[]]$RegistryEntries
)
$addedKeys = @()
$updatedKeys = @()
$failedKeys = @()
Write-Host -ForegroundColor Green $("-" * 80)
ForEach ($entry in $RegistryEntries) {
$path = $entry["path"]
$keyName = $entry["key"]
$value = $entry["value"]
$keyType = $entry["keytype"]
if (($keyType -eq "DWord") -and $value.StartsWith("0b")) {
$value = [Convert]::ToInt64($value.Substring(2), 2)
}
if ($keyType -eq "Binary" -or $keyType -eq "QWord") {
$value = $value.Split(' ') | ForEach-Object { [Convert]::ToByte($_, 16) }
}
try {
if (-Not (Test-Path $path)) {
Write-Host -ForegroundColor Yellow "Creating path: $path"
$newPath = New-Item -Path $path -Force -ErrorAction SilentlyContinue
if ($null -eq $newPath) {
Write-Error "Failed to create registry path: $path"
$failedKeys += $keyName
continue
}
}
$existingValue = Get-ItemProperty -Path $path -Name $keyName -ErrorAction SilentlyContinue
if ($null -eq $existingValue) {
Write-Host "Adding key: $keyName at path: $path with value: $value and type: $keyType"
New-ItemProperty -Path $path -Name $keyName -Value $value -PropertyType $keyType
$addedKeys += $keyName
} else {
Write-Host "Updating key: $keyName at path: $path with value: $value and type: $keyType"
Set-ItemProperty -Path $path -Name $keyName -Value $value
$updatedKeys += $keyName
}
} catch {
Write-Error "Failed to edit registry key: $keyName at path: $path"
$failedKeys += $keyName
}
}
Write-Host -ForegroundColor Green $("-" * 80)
if ($addedKeys.Count -gt 0) {
$addString = "Added $($addedKeys.Count) key(s):`n"
$addedKeys | ForEach-Object { $addString += $($_ + " ") }
Write-Host -ForegroundColor Green $addString
}
if ($updatedKeys.Count -gt 0) {
$updateString = "Updated $($updatedKeys.Count) key(s):`n"
$updatedKeys | ForEach-Object { $updateString += $($_ + " ") }
Write-Host -ForegroundColor Green $updateString
}
if ($failedKeys.Count -gt 0) {
$failString = "Failed to edit $($failedKeys.Count) key(s):`n"
$failedKeys | ForEach-Object { $failString += $($_ + " ") }
Write-Error $failString
}
Write-Host -ForegroundColor Green $("-" * 80)
}
<#
.SYNOPSIS
This function allows you to add lines uniquely to a file.
.DESCRIPTION
This function allows you to add lines uniquely to a file. It will not add a line if it already exists in the file. Status will be displayed in the terminal.
.PARAMETER FilePath
The path to the file to modify.
.PARAMETER LinesToAdd
A multiline string of lines to add to the file.
.EXAMPLE
$FilePath = "C:\PATH\to\file.txt"
$LinesToAdd = @"
Line 1
Line 2
Line 3
"@
Add-UniqueLines -FilePath $FilePath -LinesToAdd $LinesToAdd
.NOTES
This function does not offer the capability to delete lines from a file, as it is designed around working non-destructively.
#>
function Add-UniqueLines {
param (
[string]$FilePath,
[string]$LinesToAdd
)
Write-Host -ForegroundColor Green $("-" * 80)
Write-Host -ForegroundColor Yellow "Updating $FilePath..."
if (-Not (Test-Path -Path $FilePath)) {
Write-Error "Error: File '$FilePath' does not exist."
return
}
$linesAdded = 0
$LinesToAdd.Split([Environment]::NewLine) | ForEach-Object {
$line = $_.Trim()
if ($line -and ((Get-Content -Path $FilePath | Select-String -Pattern ([regex]::Escape($line)) -Quiet) -eq $false)) {
Add-Content -Path $FilePath -Value $line
$linesAdded++
}
}
if ($linesAdded -gt 0) {
Write-Host -ForegroundColor Green "Added $linesAdded lines to $FilePath"
} else {
Write-Warning "No lines added to $FilePath, leaving as-is."
}
Write-Host -ForegroundColor Green $("-" * 80)
}
<#
.SYNOPSIS
This function allows you to install packages from various package managers.
.DESCRIPTION
This function allows you to install packages from various package managers. It will not install a package if it is already installed, instead, it will try to update it. Status will be displayed in the terminal, along with a results summary. This function can install packages from Chocolatey, Winget, and Scoop.
.PARAMETER Packages
A string array of packages to install.
.PARAMETER PackageType
The package manager to use. Valid values are: choco, winget, scoop
.EXAMPLE
$Packages = @(
"7zip",
"git",
"notepadplusplus",
"vscode"
)
Install-Packages -Packages $Packages -PackageType "choco"
.NOTES
This function does not offer the capability to uninstall packages, as it is designed around working non-destructively.
#>
function Install-Packages {
param (
[string[]]$Packages,
[ValidateSet("choco", "winget", "scoop", IgnoreCase = $true)]
[string]$PackageType
)
$result = Install-PackageManager -PackageManager $PackageType
if ($result -eq $false) {
Write-Error -Message "An error occoured with the package manager check, the script will now exit."
exit 1
}
$succeededPackages = @()
$failedPackages = @()
ForEach ($Package in $Packages) {
Write-Host -ForegroundColor Green $("-" * 80)
switch ($PackageType.ToLower()) {
"choco" {
if ($null -eq $(choco list --local-only | Where-Object { $_ -like "$PackageName*" } )) {
Write-Host -Foreg roundColor Yellow "Installing $Package..."
choco install $Package --confirm
} else {
Write-Host -ForegroundColor Yellow "Package $Package is already installed, attempting to upgrade it, instead."
choco upgrade $Package --confirm
}
}
"winget" {
if ($null -eq $(winget list | Where-Object { $_ -like "$PackageName*" } )) {
Write-Host -ForegroundColor Yellow "Installing $Package..."
winget install $Package --disable-interactivity
} else {
Write-Host -ForegroundColor Yellow "Package $Package is already installed, attempting to upgrade it, instead."
winget upgrade $Package --disable-interactivity
}
}
"scoop" {
if ($null -eq $(scoop list | Where-Object { $_ -like "$PackageName*" } )) {
Write-Host -ForegroundColor Yellow "Installing $Package..."
scoop install $Package
} else {
Write-Host -ForegroundColor Yellow "Package $Package is already installed, attempting to upgrade it, instead."
scoop update $Package
}
}
}
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
Write-Host -ForegroundColor Green $("-" * 80)
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
Write-Error "Operation failed for $PackageType package: $Package"
$failedPackages += $Package
continue
}
}
$succeededPackages += $Package
refreshenv
. $PROFILE
}
Write-Host -ForegroundColor Green $("-" * 80)
if ($failedPackages.Count -gt 0) {
$errorString = "Failed to install $($failedPackages.Count) package(s):`n"
$failedPackages | ForEach-Object { $errorString += $($_ + " ") }
Write-Error $errorString
}
if ($succeededPackages.Count -gt 0) {
$successString = "Succeeded installing $($succeededPackages.Count) package(s):`n"
$succeededPackages | ForEach-Object { $successString += $($_ + " ") }
Write-Host -ForegroundColor Green $successString
}
Write-Host -ForegroundColor Green $("-" * 80)
}
<#
.SYNOPSIS
This function is able to install various package managers by using PowerShell.
.DESCRIPTION
This function is able to install various package managers by using PowerShell. It will not install a package manager if it is already installed. Status will be displayed in the terminal, along with a results summary. This function can install Chocolatey, Winget, and Scoop.
.PARAMETER PackageManager
The package manager to install. Valid values are: choco, winget, scoop
.EXAMPLE
Install-PackageManager -PackageManager "choco"
.NOTES
This function does not offer the capability to uninstall package managers, as it is designed around working non-destructively.
#>
function Install-PackageManager {
param (
[ValidateSet("choco", "winget", "scoop", IgnoreCase = $true)]
[string]$PackageManager
)
switch ($PackageManager) {
"scoop" {
if ($null -eq $(Get-Command -Name "scoop" -ErrorAction SilentlyContinue)) {
Write-Warning -Message "Scoop is not installed. Installing Scoop..."
Start-Process pwsh -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -Command `"& { iwr -useb get.scoop.sh | iex }`""
if ($LASTEXITCODE -ne 0 -or $null -eq $(Get-Command -Name "scoop" -ErrorAction SilentlyContinue)) {
Write-Error -Message "Failed to install Scoop."
return $false
}
. $PROFILE
} else {
Write-Host -ForegroundColor Green "Scoop is installed, proceeding"
return $true
}
}
"choco" {
if ($null -eq $(Get-Command -Name "choco" -ErrorAction SilentlyContinue)) {
Write-Warning "Chocolatey is not installed. Installing Chocolatey..."
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
if ($LASTEXITCODE -ne 0 -or -not $(Test-Path -Path "$env:ChocolateyInstall")) {
Write-Error -Message "Failed to install Chocolatey"
return $false
}
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
refreshenv
. $PROFILE
choco feature enable -n allowGlobalConfirmation
} else {
Write-Host -ForegroundColor Green "Chocolatey is installed, proceeding"
Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1
refreshenv
. $PROFILE
return $true
}
}
"winget" {
if ($null -eq $(Get-Command -Name "winget" -ErrorAction SilentlyContinue)) {
Write-Warning -Message "Winget is not installed. Installing Winget..."
Install-Module -Name Microsoft.WinGet.Client
if ($LASTEXITCODE -ne 0 -or $null -eq $(Get-Command -Name "winget" -ErrorAction SilentlyContinue)) {
Write-Error -Message "Failed to install Winget, the script will now exit."
return $false
}
} else {
Write-Host -ForegroundColor Green "Winget is installed, proceeding"
return $true
}
}
}
}
<#
.SYNOPSIS
This function allows you to install different python versions via pyenv-win.
.DESCRIPTION
This function allows you to install different python versions via pyenv-win. If pyenv-win is not installed, it will be installed automatically for you into your $USERPROFILE\.pyenv directory and added to your environment.
.PARAMETER PyEnvVersions
A hashtable specifying a list of python versions to isntall into pyenv, along with some properties.
.EXAMPLE
$PyEnvVersions = @(
[hashtable]@{
Version = "3.9.6"
Default = $true
},
[hashtable]@{
Version = "3.8.10"
Default = $false
}
)
Install-PyVersions -PyEnvVersions $PyEnvVersions
.NOTES
This function does not offer the capability to uninstall pyenv-win, as it is designed around working non-destructively.
#>
function Install-PyVersions() {
param(
[Parameter(Mandatory = $true)][hashtable[]]$PyEnvVersions
)
if ($PyEnvVersions.Count -eq 0) {
Write-Error -Message "No python versions specified to install, skipping python."
return
}
if ($null -eq $env:PYENV_HOME) {
Write-Warning -Message "pyenv-win is not installed. Installing pyenv-win..."
Invoke-WebRequest -UseBasicParsing -Uri "https://raw.githubusercontent.com/pyenv-win/pyenv-win/master/pyenv-win/install-pyenv-win.ps1" -OutFile "./install-pyenv-win.ps1"; &"./install-pyenv-win.ps1"
if ($LASTEXITCODE -ne 0) {
Write-Error -Message "Failed to install pyenv-win"
return
}
refreshenv
. $PROFILE
} else {
Write-Host -ForegroundColor Green "pyenv-win is installed, proceeding"
}
$succeededVersions = @()
$failedVersions = @()
ForEach ($PyEnvVersion in $PyEnvVersions) {
$version = $PyEnvVersion['version']
$default = $PyEnvVersion['default']
if ($null -eq $(pyenv versions | Where-Object { $_ -like "$PyEnvVersion*" } )) {
Write-Host -ForegroundColor Green $("-" * 80)
Write-Host -ForegroundColor Yellow "Installing $version..."
pyenv install $version
if ($LASTEXITCODE -ne 0) {
Write-Error -Message "Failed to install $version."
$failedVersions += $version
continue
}
if ($default) {
pyenv global $version
}
Write-Host -ForegroundColor Green "Successfully installed $version."
refreshenv
. $PROFILE
$succeededVersions += $version
} else {
Write-Warning -Message "Version $version is already installed."
$succeededVersions += $version
if ($default) {
Write-Host -ForegroundColor Green "Setting $version as the default python version."
pyenv global $version
refreshenv
. $PROFILE
}
continue
}
}
Write-Host -ForegroundColor Green $("-" * 80)
if ($succeededVersions.Count -gt 0) {
Write-Host -ForegroundColor Green "Successfully installed the following versions: $($succeededVersions -join " ")."
}
if ($failedVersions.Count -gt 0) {
Write-Error -Message "Failed to install the following python versions: $($failedVersions -join " ")."
}
Write-Host -ForegroundColor Green $("-" * 80)
}
<#
.SYNOPSIS
This function allows you to automate the install of the NVIdia driver for your graphics card in your Shadow.
.DESCRIPTION
This function allows you to automate the install of the NVIdia driver for your graphics card in your Shadow. It will search for the graphics card information using the Windows Management Instrumentation and your OS information located in the registry, and then use that information to download the latest driver from NVidia's website by calling the internal GeForce experience update check API. It will then extract the driver and install it.
This is a very advanced function but makes the process of updating your graphics driver super simple. GeForce Experience is no longer required to have on your system with blep!
.EXAMPLE
Update-NVidiaDriver
.NOTES
WARNING: By calling this function, you agree to the NVidia EULA located at https://www.nvidia.com/en-us/drivers/nv-ueula/ , This function is NOT SUPPORTED by NVidia. Use at your own risk.
#>
function Update-NVidiaDriver() {
Write-Host -ForegroundColor Green $("-" * 80)
Write-Host -ForegroundColor Blue "Reading system information..."
$videoCard = Get-CimInstance -ClassName Win32_VideoController | Select-Object -First 1
if ($videoCard.PNPDeviceID -match "VEN_10DE") {
$nvidiaInfo = [hashtable]@{
"vendor" = $videoCard.PNPDeviceID -replace '.*VEN_([^&]+)&.*', '$1'
"device" = $videoCard.PNPDeviceID -replace '.*DEV_([^&]+)&.*', '$1'
"subsys" = $videoCard.PNPDeviceID -replace '.*SUBSYS_([^&]+)&.*', '$1'
"rev" = $videoCard.PNPDeviceID -replace '.*REV_([^\\]+)\\.*', '$1'
'version' = ($videoCard.DriverVersion.Replace('.', '')[-5..-1] -join '').insert(3, '.')
'isWHQL' = $true
'isDCH' = $false
'uri' = [string]::Empty
}
Write-Host -ForegroundColor Red "`nGraphics Card Information:"
Write-Host -ForegroundColor Yellow " Vendor: $($nvidiaInfo.vendor)"
Write-Host -ForegroundColor Yellow " Device: $($nvidiaInfo.device)"
Write-Host -ForegroundColor Yellow " Subsystem: $($nvidiaInfo.subsys)"
Write-Host -ForegroundColor Yellow " Revision: $($nvidiaInfo.rev)"
Write-Host -ForegroundColor Yellow " Current Driver Version: $($nvidiaInfo.version)"
$osInfo = [hashtable]@{
'os_version' = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "ReleaseId").ReleaseId
'os_build' = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name "CurrentBuild").CurrentBuild
'x86_64' = $env:PROCESSOR_ARCHITECTURE -eq "AMD64" -or $env:PROCESSOR_ARCHITEW6432 -eq "x86_64"
'language_code' = (Get-Culture).LCID
}
Write-Host -ForegroundColor Red "Operating System Information:"
Write-Host -ForegroundColor Yellow " Os Version: $($osInfo.os_version)"
Write-Host -ForegroundColor Yellow " Os Build: $($osInfo.os_build)"
Write-Host -ForegroundColor Yellow " Is 64-bit? $($osInfo.x86_64)"
Write-Host -ForegroundColor Yellow " Language Code: $($osInfo.language_code)`n"
Write-Host -ForegroundColor Green $("-" * 80)
Write-Host -ForegroundColor Blue "Calling NVidia's internal GeForce Experience API to check for driver updates..."
# NOTE: Please keep in mind that this only checks for either Windows 10, or Windows 11 drivers. Remember that blep is an API for ShadowPC, in which only these two operating systems are supported. You'll need to integrate a full osC lookup table if you want to support other Windows versions.
$query_obj = @{
"dIDa" = @($("{0}_{1}_{2}_{3}" -f ($nvidiaInfo.device, $nvidiaInfo.vendor, $nvidiaInfo.subsys.Substring(0, 4), $nvidiaInfo.vendor)))
"osC" = if ($osInfo.os_version -eq "2009") { "10.0" } else { "11.0" }
"osB" = $osInfo.os_build
"is6" = if ($osInfo.x86_64) { "1" } else { "0" }
"lg" = [string]$osInfo.language_code
"iLp" = "0"
"prvMd" = "0"
"gcV" = "3.18.0.94"
"gIsB" = "0"
"GFPV" = $nvidiaInfo.version
"dch" = if ($nvidiaInfo.isDCH -eq $true) { "1" } else { "0" }
"upCRD" = "0"
"isCRD" = "0"
"isInst" = "1"
}
$ENDPOINT = 'https://gfwsl.geforce.com/nvidia_web_services/controller.gfeclientcontent.NG.php/com.nvidia.services.GFEClientContent_NG.getDispDrvrByDevid'
$url = "$ENDPOINT/$($query_obj | ConvertTo-Json -Compress)"
$headers = @{
'User-Agent' = 'NvBackend/36.0.0.0'
}
try {
<# Calling the GFEClientContent_NG.getDispDrvrByDevid endpoint returns a JSON object with a citeria object that allows us to conditionally see if there is a new driver available by checking criteria.IsDispDriverNewer.state for "true". If there is an update available, the DriverAttributes.DownloadURLAdmin property contains the URL to the direct download of the driver, and esponse.DriverAttributes.clientUX.DriverUpdateDesktopNotification contains a useful toast notification for us to pull from the REST API. This is the same URL that is used by the GeForce Experience client to download the driver. Because we have the API, the Admin version of the driver is accessable to us, meaning we can automate installation of the driver without the need for the GeForce Experience client.
You'd better be smashing that Star button, right now.
Not exactly a hack on my part, though.
Source: https://github.com/keylase/nvidia-patch/blob/master/tools/nv-driver-locator/gfe_get_driver.py
Another reverse engineerer left this code around, so I went ahead and updated it to work with the latest version of GFEClient API to emulate the presence of Geforce Experience, without installing bloatware, and pass compatible parameters to it so that it worked cleanly. :-) #>
$response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get -TimeoutSec 10 -RetryIntervalSec 3 -MaximumRetryCount 3
if ($response.criteria.IsDispDriverNewer.state -eq "true") {
$nvidiaInfo.url = $response.DriverAttributes.DownloadURLAdmin
Write-Host -ForegroundColor Green $("-" * 80)
Write-Host -ForegroundColor Green "$($response.DriverAttributes.clientUX.DriverUpdateDesktopNotification.titleText)"
$nvidiaTempFolder = "$folder\NVIDIA"
if (-not $(Test-Path -Path $nvidiaTempFolder)) {
New-Item -Path $nvidiaTempFolder -ItemType Directory | Out-Null
}
$dlFile = "$nvidiaTempFolder\nvidia-$($nvidiaInfo.version).exe"
Write-Host -ForegroundColor Yellow "Downloading installer..."
Start-BitsTransfer -Source $nvidiaInfo.url -Destination $dlFile
if ($?) {
Write-Host -ForegroundColor Green "Download complete."
}
else {
Write-Error -Message "Download failed!"
}
# NOTE: This should probably be its own function for the blep API, but I am a bit lazy right now...dergs are sometimes. When sequential execution comes out for blep, I will be adding an extractor as an Action.
$extractFolder = "$nvidiaTempFolder\nvidia-$($nvidiaInfo.version)"
$filesToExtract = "Display.Driver HDAudio NVI2 PhysX EULA.txt ListDevices.txt setup.cfg setup.exe"
# Find 7-zip. If it's not installed, install it.
if ((Test-path HKLM:\SOFTWARE\7-Zip\) -eq $true) {
$7zpath = Get-ItemProperty -path HKLM:\SOFTWARE\7-Zip\ -Name Path
$7zpath = $7zpath.Path
$7zpathexe = $7zpath + "7z.exe"
if ((Test-Path $7zpathexe) -eq $true) {
Write-Host -ForegroundColor Green "7zip installed at: $7zpathexe"
} else {
Write-Host -ForegroundColor Yellow "7zip not installed. Installing from chocolatey..."
Install-Packages -Packages @( "7zip" ) -PackageType "choco"
$7zpath = Get-ItemProperty -path HKLM:\SOFTWARE\7-Zip\ -Name Path
$7zpath = $7zpath.Path
$7zpathexe = $7zpath + "7z.exe"
}
} else {
Write-Host -ForegroundColor Yellow "7zip not installed. Installing from chocolatey..."
Install-Packages -Packages @( "7zip" ) -PackageType "choco"
$7zpath = Get-ItemProperty -path HKLM:\SOFTWARE\7-Zip\ -Name Path
$7zpath = $7zpath.Path
$7zpathexe = $7zpath + "7z.exe"
}
Write-Host -ForegroundColor Cyan "Extracting NVIDIA installer files..."
Start-Process -FilePath $7zpathexe -NoNewWindow -ArgumentList "x -bso0 -bsp1 -bse1 -aoa $dlFile $filesToExtract -o""$extractFolder""" -wait
if ($?) {
Write-Host -ForegroundColor Green "Extraction completed successfully."
(Get-Content "$extractFolder\setup.cfg") | Where-Object { $_ -notmatch 'name="\${{(EulaHtmlFile|FunctionalConsentFile|PrivacyPolicyFile)}}' } | Set-Content "$extractFolder\setup.cfg" -Encoding UTF8 -Force
Write-Host -ForegroundColor Cyan "Installing NVIDIA drivers..."
Write-Host -foregroundColor Magenta $("WARNING! " * 5)
Write-Host -ForegroundColor Magenta "`nThis will install the NVIDIA driver without any user interaction. This WILL cause your Shadow to CRASH FOR A WHILE, and you'll need to re-run blep after your shadow reboots.`nMultistage execution with a registry-driven instruction pointer is a future update.`nPlease re-run blep manually a second time to complete the rest of the installation steps, for now."
Write-Host -ForegroundColor Magenta $("`nWARNING! " * 5)
Write-Host -ForegroundColor -Red "`nThe operation will start in 10 seconds. !! HOLD ONTO YOUR BUTT !!"
Start-Sleep -Seconds 10
$install_args = "-passive -noreboot -noeula -nofinish -s -clean"
Start-Process -FilePath "$extractFolder\setup.exe" -ArgumentList $install_args -wait
Write-Host -ForegroundColor Red "Cleaning up..."
Remove-Item $nvidiaTempFolder -Recurse -Force
Write-Host -ForegroundColor Red "NVIDIA Driver installed!`nRebooting your Shaodow now..."
Restart-Computer -Force
if ($?) {
Write-Host -ForegroundColor Green "NVIDIA Driver installed!`nYou may need to reboot to finish installation."
} else {
Write-Error -Message "Installation failed!"
}
} else {
Write-Error -Message "Extraction failed!"
}
Write-Host -ForegroundColor Red "Cleaning up..."
Remove-Item $nvidiaTempFolder -Recurse -Force
} else {
Write-Host -ForegroundColor Red "No new driver updates available."
}
}
catch [System.Net.WebException] {
if ($_.Exception.Response.StatusCode -eq 404) {
throw "Retunred a 404 error when attempting to locate new driver information. This could mean that your graphics card isn't currently supported, or that it is reporting an invalid device ID. Please let the developoer know if you see this error, with script output."
} elseif ($_.Exception.Response.StatusCode -eq 500) {
throw "Retunred a 500 error when attempting to locate new driver information. This means the Geforce Experience servers are down. Please try again later. If this error persists, please let the developer know with the script output."
}
elseif ($_.Exception.Response.StatusCode -eq 503) {
throw "Retunred a 503 error when attempting to locate new driver information. This means the Geforce Experience servers have changed their API and this endpoint is no longer accessable. If this error persists, please let the developer know with the script output."
} else {
throw $_
}
}
catch {
throw $_
}
}
else {
Write-Warning -Message "No NVidia graphics card found."
}
Write-Host -ForegroundColor Green $("-" * 80)
}
<##########################################################
# MAIN SCRIPT
##########################################################>
Install-Packages -Packages $ChocolateyPackages -PackageType "choco"
Install-Packages -Packages $WinGetPackages -PackageType "winget"
Install-Packages -Packages $ScoopPackages -PackageType "scoop"
Add-RegistryEntries -RegistryEntries $RegistryEntries
# Add oh-my-posh to ps profile
$lines = @"
oh-my-posh init pwsh --config "$env:POSH_THEMES_PATH/hunk.omp.json" | Invoke-Expression
"@
Add-UniqueLines -FilePath $PROFILE -LinesToAdd $lines
# Install pyenv-win and python versions
Install-PyVersions -PyEnvVersions $PyEnvVersions
#>
# Update NVidia driver (NEW!)
Update-NVidiaDriver
@loopyd
Copy link
Author

loopyd commented Aug 8, 2023

Version 1.0.1:

New features:

  • Config file support via blep-config.json for full automation with included defaults.
  • Registry editor that allows you to set or update registry keys
  • Multi package manager support (Winget, chocolatey, scoop)
  • File editor that allows you to add or append to files (non-destructively)

Bugfixes:

  • Script blocks if it is not running as Administrator.
  • Script relaunches itself as an auto-installed Powershell 7 instance ift has been run as a Powershell 5 instance. Powershell 7.3.6 is auto installed if the $PsVersion.Major is not 7 in the current session, and the script is re-run.
  • Scoop and winget installers fixed.
  • Code refactor.

@loopyd
Copy link
Author

loopyd commented Aug 10, 2023

Version 1.1.0:

New features:

  • NVIDIA GRAPHICS DRIVER AUTO-UPDATE Automatically upgrades NVidia drivers to the latest version, and will reboot the Shadow automatically. This new feature is very advanced, and checks the currently installed graphics card using the WMI and the registry, along with a special NVIDIA API call to select the right driver.

Bugfixes:

  • Minor typo that doesn't specify "blep-config.json" correctly. Fixed.
  • Issue with scoop where a -y flag was being passed incorrectly during an upgrade.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment