Last active
November 20, 2025 16:46
-
-
Save sahps/37fcfb1edc5241a879a0a7df4275cace to your computer and use it in GitHub Desktop.
Auto-update Netbird using PowerSHell
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ######################################################################### | |
| [cmdletbinding()] | |
| param ( | |
| [Parameter(Mandatory)] | |
| [string]$NetbirdManagementUrl, | |
| [Parameter()] | |
| [version]$ForcedMinimumVersion = "0.48.0", | |
| [Parameter()] | |
| [string]$NetbirdUrlScheme = "https", | |
| [Parameter()] | |
| [int]$NetbirdUiStartup = 1, | |
| [Parameter()] | |
| [string]$WorkingFolderName = "_Intune" | |
| ) | |
| ######################################################################### | |
| # Name of package, used in log file name | |
| $packageName = 'Netbird-Check' | |
| ######################################################################### | |
| # working directory | |
| $workingDir = ('{0}\{1}' -f $env:ProgramData, $WorkingFolderName) | |
| $workingTimeStamp = (Get-Date -UFormat '%Y%m%d-%H%M%S') | |
| # log files | |
| $logFolder = ('{0}\Logs' -f $workingDir) | |
| $logFileTemplate = '{0}_{1}.txt' | |
| $monthTimestamp = (Get-Date -UFormat '%Y%m') # this script runs hourly, so lets keep standard logs to 1 file per month | |
| $logFileName = ($logFileTemplate -f $packageName, $monthTimestamp) | |
| $logFilePath = ('{0}\{1}' -f $logFolder, $logFileName) | |
| # default var | |
| $isError = $false | |
| # start transcript | |
| [void](New-Item -ItemType Directory $logFolder -ErrorAction SilentlyContinue) | |
| Start-Transcript -Path $logFilePath -Force -Verbose -Append | |
| ######################################################################### | |
| # service details | |
| $serviceName = ("Netbird") | |
| # download details | |
| $downloadFolder = ('{0}\Downloads' -f $workingDir) | |
| # we get our updates direct from github | |
| $releasesUrl = 'https://api.github.com/repos/netbirdio/netbird/releases' | |
| # we only download releases that are at least this old (3 days) | |
| $minimumAge = 86400 * 3 | |
| # once we find an eligible release, we download the first asset that matches this | |
| $assetMatch = 'netbird*_windows_amd64.msi' | |
| # we download the installer to thie path | |
| $downloadFileName = ("Netbird.Client_{0}.msi" -f $workingTimeStamp) | |
| $downloadPath = ('{0}\{1}' -f $downloadFolder, $downloadFileName) | |
| # the msi log name | |
| $msiLogFileTemplate = '{0}\{1}_{2}_msi_{3}.txt' | |
| # netbird config file | |
| $netbirdConfigPath = ('{0}\Netbird\config.json' -f $env:ProgramData) | |
| # netbird executable | |
| $netbirdExePath = "C:\Program Files\Netbird\netbird.exe" | |
| # netbird ui executable | |
| $netbirdUiExePath = "C:\Program Files\Netbird\Netbird-ui.exe" | |
| # we populate this with the latest release data | |
| $latestRelease = {} | |
| # auto startup of ui | |
| $regKeys = @{ | |
| 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' = @{ | |
| Name = "netbird-ui" | |
| ActionType = "Set" | |
| ActionValue = @{ | |
| Type = "STRING" | |
| Value = $netbirdUiExePath | |
| } | |
| } | |
| } | |
| try { | |
| Function Write-Log { | |
| [cmdletbinding()] | |
| param ( | |
| [Parameter (Mandatory = $true, ValueFromPipeline = $true)] | |
| [string[]]$Message, | |
| [Parameter (Mandatory = $false)] | |
| [switch]$IsWarning, | |
| [Parameter (Mandatory = $false)] | |
| [switch]$IsError, | |
| [Parameter (Mandatory = $false)] | |
| [string]$Prefix, | |
| [Parameter (Mandatory = $false)] | |
| [System.Exception]$Exception, | |
| [Parameter (Mandatory = $false)] | |
| [switch]$Throw | |
| ) | |
| $timestamp = [DateTime]::Now | |
| $type = 'INFO' | |
| $param = @{} | |
| if (($null -ne $Exception) -or ($true -eq $Throw) -or ($true -eq $IsError)) { | |
| $param['ForegroundColor'] = "Red" | |
| $type = "CRIT" | |
| if ($null -ne $Exception) { | |
| $Message = @($Message) + @(($Exception.Message -split "\n")) | |
| } | |
| } elseif ($true -eq $isWarning) { | |
| $param['ForegroundColor'] = "Magenta" | |
| $type = "WARN" | |
| } | |
| if ($true -eq $Throw) { | |
| Throw @($Message)[0] | |
| } | |
| foreach ($string in $Message) { | |
| Write-Host @param ('[{0} | {1}] {2}{3}' -f $timestamp, $type, $Prefix, $string) | |
| } | |
| } | |
| Function Test-IsAdmin() { | |
| $currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) | |
| return $currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| } | |
| Function Invoke-Download { | |
| [cmdletbinding()] | |
| param ( | |
| [Parameter (Mandatory = $true)] | |
| [string]$Url, | |
| [Parameter (Mandatory = $true)] | |
| [string]$Path | |
| ) | |
| Write-Log "Downloading $Url -> $Path" | |
| # set tls | |
| [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 | |
| $webClient = New-Object Net.WebClient | |
| $webClient.DownloadFile($Url, $Path) | |
| if (!(Test-Path($Path))) { | |
| Write-Log -Throw "Download failed, file does not exist: $Path" | |
| } elseif ((Get-Item $Path).Length -eq 0) { | |
| Write-Log -Throw "Download failed, file zero length: $Path" | |
| } | |
| } | |
| Function Remove-ItemRepeated { | |
| param ( | |
| [Parameter (Mandatory = $true)] | |
| [string]$Path, | |
| [Parameter (Mandatory = $true)] | |
| [int]$Attempts | |
| ) | |
| $n = $Attempts | |
| While (Test-Path($Path)) { | |
| if ($n -le 0) { break; } | |
| Remove-Item -Path $Path -Verbose -Force | |
| Start-Sleep 1 | |
| $n-- | |
| } | |
| if (Test-Path($Path)) { Write-Log -IsWarning "Failed to remove item after $Attempts attempts: $Path" } | |
| } | |
| Function Invoke-PruneFolder { | |
| [cmdletbinding()] | |
| param ( | |
| [Parameter (Mandatory = $true)] | |
| [string]$Path, | |
| [Parameter (Mandatory = $false)] | |
| [string]$FileMatch = '*', | |
| [Parameter (Mandatory = $false)] | |
| [int]$DaysToKeep = 30 | |
| ) | |
| Get-ChildItem -Path $logFolder -Filter $FileMatch -File | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(($DaysToKeep * -1)) } | Remove-Item -Force -Verbose | |
| } | |
| Function Get-App-Version-From-Tag($tag_name) { | |
| # get the latest release tag_name (v8.5.2) | |
| if (($null -eq $tag_name) -or (-not ($tag_name))) { | |
| Write-Log -IsError "Failed to get latest tag_name from parsed Json" -Throw | |
| } | |
| # remove any non-version characters and cast to version | |
| try { | |
| return [version]($tag_name -Replace '[^0-9\.]') | |
| } catch { | |
| Write-Log -IsError "Failed to get parse version from tag_name from parsed Json" -Throw | |
| } | |
| } | |
| Function Get-App-Github-LatestRelease($url, $minimumAge = 0) { | |
| Write-Log "Checking for latest releases from url: $url" | |
| if ($minimumAge -gt 0) { | |
| Write-Log "Rejecting releases that were less than $(("{0:dd}d {0:hh}h {0:mm}m {0:ss}s" -f (New-TimeSpan -Seconds $minimumAge))) seconds old before a new release was published" | |
| } | |
| # fetch json from github api | |
| try { | |
| $releasesJson = Invoke-WebRequest -Uri $url -UseBasicParsing -ErrorAction Stop | |
| } catch { | |
| Write-Log -IsError "Failed to download releases data from url: $url" -Exception $_.Exception | |
| } | |
| # parse content from json | |
| try { | |
| $releases = $releasesJson.Content | ConvertFrom-Json -ErrorAction Stop | |
| } catch { | |
| Write-Log -IsError "Failed to parse json data from url: $url" -Exception $_.Exception | |
| } | |
| try { | |
| # we want the first release that had an age of >minimumAge before the next release was published | |
| $dateOfLastRelease = (Get-Date) # initial test, check against today | |
| foreach ($release in $releases) { | |
| # get the latest release tag_name (v8.5.2) | |
| $releaseTag = $release.tag_name | |
| if (($null -eq $releaseTag) -or (-not ($releaseTag))) { | |
| Write-Log -IsError "Failed to get latest tag_name from parsed Json: $url" | |
| continue | |
| } | |
| try { | |
| $releaseVersion = Get-App-Version-From-Tag -tag_name $releaseTag | |
| } catch { | |
| Write-Log -IsError "Failed to get parse version from tag_name from parsed Json: $url" | |
| continue | |
| } | |
| # how old was this release before the next one was released (or just simply how is the release if its the first one) | |
| $timeSinceNextRelease = (New-TimeSpan -Start $release.published_at -End $dateOfLastRelease) | |
| # does this release meet the age criteria? | |
| if ($timeSinceNextRelease.TotalSeconds -gt $minimumAge) { | |
| Write-Log "Found release: $releaseVersion published at $($release.published_at) [$("{0:dd}d {0:hh}h {0:mm}m {0:ss}s" -f $timeSinceNextRelease)]" | |
| break | |
| } elseif ($releaseVersion -eq $ForcedMinimumVersion) { | |
| Write-Log -IsWarning "Forced minimum release: $releaseVersion published at $($release.published_at) [$("{0:dd}d {0:hh}h {0:mm}m {0:ss}s" -f $timeSinceNextRelease)]" | |
| break | |
| } else { | |
| Write-Log -IsWarning "Reject $releaseVersion published at $($release.published_at) [$("{0:dd}d {0:hh}h {0:mm}m {0:ss}s" -f $timeSinceNextRelease)]" | |
| } | |
| $dateOfLastRelease = $release.published_at | |
| } | |
| } catch { | |
| Write-Log -IsError "Failed to determine appropriate release from github releases: $url" -Exception $_.Exception | |
| } | |
| # fetch the asset of this latest release that matches the 64-bit exe installer | |
| $latestAsset = $release.assets |Where-Object { $_.Name -like $assetMatch } | |
| if (($null -eq $latestAsset) -or (-not ($latestAsset))) { | |
| Write-Log -IsError "Failed to get latest asset (matching '$assetMatch') from parsed Json: $url" -Throw | |
| } | |
| # fetch the download url from matching asset | |
| $downloadUrl = $latestAsset.browser_download_url | |
| if (($null -eq $downloadUrl) -or (-not ($downloadUrl))) { | |
| Write-Log -IsError "Failed to get download url from latest asset: $url" -Throw | |
| } | |
| # return latest info | |
| return @{ | |
| "Version" = $releaseVersion | |
| "Url" = $downloadUrl | |
| } | |
| } | |
| Function Invoke-Download-and-Install-MSI-App { | |
| # create download folder | |
| if (!(Test-Path $downloadFolder)) { | |
| New-Item -Type Directory -Path $downloadFolder -ErrorAction Stop | |
| } | |
| # get latest release from previously populated variable | |
| $downloadUrl = ($script:latestRelease)['Url'] | |
| # download package | |
| Invoke-Download -Url $downloadUrl -Path $downloadPath | |
| $msiCount = 0 | |
| try { | |
| $msiCount++ | |
| $params = @{ | |
| FilePath = "msiexec" | |
| ArgumentList = @( | |
| "/i", | |
| "`"$downloadPath`"", | |
| "/qn", | |
| "/norestart", | |
| "/l*v", | |
| "`"$($msiLogFileTemplate -f $logFolder, $packageName, $workingTimeStamp, $msiCount)`"", | |
| "REBOOT=ReallySuppress" | |
| ) | |
| Wait = $true | |
| Passthru = $true | |
| } | |
| Write-Log "Installing app: $downloadPath $($params['ArgumentList'] -Join ' ')" | |
| $params | ConvertTo-Json | Write-Log | |
| $p = Start-Process @params | |
| Write-Log "Installation completed. Exit code: $($p.ExitCode)" | |
| } catch { | |
| Write-Log -IsError "Failed to install" | |
| Throw | |
| } | |
| Remove-ItemRepeated -Path $downloadPath -Attempts 10 | |
| } | |
| Function Test-And-Configure-Netbird() { | |
| if (-not (Test-Path $netbirdConfigPath)) { | |
| Write-Log -Throw "Unable to find netbird configuration file: $netbirdConfigPath" | |
| } | |
| try { | |
| Write-Log "Fetching Netbird config as json: $netbirdConfigPath" | |
| $configJson = Get-Content -Raw -Path $netbirdConfigPath | |
| } catch { | |
| Write-Log -IsError "Unable to read config file: $netbirdConfigPath" -Exception $_.Exception | |
| } | |
| try { | |
| $configData = ($configJson | ConvertFrom-Json) | |
| } catch { | |
| Write-Log -IsError "Unable to parse config file: $netbirdConfigPath" -Exception $_.Exception | |
| } | |
| try { | |
| if ( | |
| ($configData.ManagementURL.Host -ne $NetbirdManagementUrl) -or | |
| ($configData.ManagementURL.Scheme -ne $NetbirdUrlScheme) | |
| ) { | |
| Write-Log "Netbird configuration needs updating" | |
| # adjust config | |
| # management | |
| $configData.ManagementURL.Scheme = $NetbirdUrlScheme | |
| $configData.ManagementURL.Host = $NetbirdManagementUrl | |
| # create JSON from config | |
| $updatedConfigJson = $configData | ConvertTo-Json -Depth 10 | |
| # save config | |
| Write-Log "Writing to netbird config" | |
| Set-Content -Path $netbirdConfigPath -Value $updatedConfigJson | |
| } else { | |
| Write-Log "Netbird configuration meets requirements" | |
| Write-Log " ManagementURL.Scheme = $($configData.ManagementURL.Scheme)" | |
| Write-Log " ManagementURL.Host = $($configData.ManagementURL.Host)" | |
| } | |
| } catch { | |
| Write-Log -IsError "Failed to update config file: $netbirdConfigPath" -Exception $_.Exception | |
| } | |
| } | |
| Function Test-App-Service { | |
| try { | |
| Write-Log "Fetching service details: $serviceName" | |
| $service = Get-Service -Name $serviceName -ErrorAction Stop | |
| } catch { | |
| Write-Log -IsWarning "Service [$serviceName] not found" | |
| return "missing" | |
| } | |
| if (!$service) { Write-Log "Failed to get service object: $serviceName" -Throw } | |
| try { | |
| if ($service.StartType -ne "Automatic") { | |
| Write-Log "Setting service [$serviceName] to Automatic" | |
| $service = ($service | Set-Service -StartupType Automatic -PassThru -ErrorAction Stop) | |
| } | |
| } catch { | |
| Write-Log "Failed setting service to Automatic" -Exception $_.Exception -Throw | |
| } | |
| try { | |
| if ($service.Status -ne "Running") { | |
| Write-Log "Starting service [$serviceName]" | |
| $service = ($service | Start-Service -PassThru -Verbose -ErrorAction Stop) | |
| } | |
| } catch { | |
| Write-Log "Failed starting service" -Exception $_.Exception -Throw | |
| } | |
| Write-Log ("Service [{0}]: {1} ({2})" -f $service.Name, $service.Status, $service.StartType) | |
| if ($service.StartType -ne "Automatic") { | |
| Write-Log -IsWarning "Failed to set service [$serviceName] to Automatic" | |
| } | |
| if ($service.Status -ne "Running") { | |
| Write-Log -IsWarning "Failed to start service [$serviceName]" | |
| } | |
| } | |
| Function Get-App-CurrentVersion($path) { | |
| if (-not (Test-Path $path)) { | |
| Write-Log -IsWarning "Application executable not found: $path" | |
| return [version]"0.0.0.0" | |
| } | |
| try { | |
| $version = (Get-Item $path).VersionInfo.FileVersionRaw | |
| Write-Log "Current version: $($version.ToString())" | |
| return (Get-Item $path).VersionInfo.FileVersionRaw | |
| } catch { | |
| Write-Log -IsError "Unable to get version from file: $path" -Exception $_.Exception | |
| } | |
| } | |
| Function Test-App-UpdateAvailable() { | |
| $script:latestRelease = Get-App-Github-LatestRelease -url $releasesUrl -minimumAge $minimumAge | |
| $currentVersion = Get-App-CurrentVersion -path $netbirdExePath | |
| # check if we received data | |
| if (($script:latestRelease.Keys -contains 'Url') -and ($script:latestRelease.Keys -contains 'Version') -and ($script:latestRelease['Version'].Length -gt 0) -and ($script:latestRelease['Url'].Length -gt 0)) { | |
| if ($currentVersion -lt $script:latestRelease['Version']) { | |
| Write-Log "New update available" | |
| return $true | |
| } else { | |
| Write-Log "No update required" | |
| } | |
| } else { | |
| Write-Log -IsWarning "Failed to fetch data from github for latest release. Unable to check for updates." | |
| } | |
| return $false | |
| } | |
| Function Test-And-Promote-App-In-SystemTray($appFileName) { | |
| foreach ($user in (Get-ChildItem Registry::HKEY_USERS -ErrorAction SilentlyContinue)) { | |
| # filter the items we don't need | |
| if ($user.Name -notmatch '^.*\\S(-[0-9]+){7}$') { continue } | |
| # we are now left with real user SIDs | |
| $NotifyIconSettings = @(Get-ChildItem -Path ('{0}\Control Panel\NotifyIconSettings' -f $user.PSPath) -ErrorAction SilentlyContinue) | |
| if ($NotifyIconSettings.Count -eq 0) { Write-Log -IsWarning "Registry hive for SID [$($user.Name)] is missing data for NotifyIconSettings" } | |
| $found = $false | |
| foreach ($app in $NotifyIconSettings) { | |
| $appProp = Get-ItemProperty $app.PSPath | |
| if (($null -ne $appProp.ExecutablePath) -and ($appProp.ExecutablePath -like ('*\{0}' -f $appFileName))) { | |
| if ($appProp.IsPromoted -ne 1) { | |
| Write-Log "System tray icon being promoted: $appFileName" | |
| Set-RegValue -regKey $app.PSPath -regName 'IsPromoted' -regValue 1 -regType 'DWORD' | |
| } else { | |
| Write-Log "System tray icon already promoted: $appFileName" | |
| } | |
| $found = $true | |
| } | |
| } | |
| if ($false -eq $found) { Write-Log -IsWarning "Failed to find matching system tray app: $appFileName" } | |
| } | |
| } | |
| Function Set-RegValue($regKey, $regName, $regValue, $regType) { | |
| try { | |
| Write-Log ("Setting registry value: {0}: {1} = {2} [{3}]" -f $regKey, $regName, $regValue, $regType) | |
| Set-ItemProperty -Path $regKey -Name $regName -Value $regValue -Type $regType -ErrorAction Stop | |
| } catch { | |
| Write-Log -Throw "Failed to set registry value" -Exception $_.Exception | |
| } | |
| } | |
| Function Update-RegValues($regKeys) { | |
| foreach ($regKey in $regKeys.Keys) { | |
| $regCfg = $regKeys[$regKey] | |
| if ($regCfg.ContainsKey("Name")) { | |
| $regName = $regCfg["Name"] | |
| $regCurrentValue = (Get-ItemProperty -Path $regKey -Name $regName -ErrorAction SilentlyContinue | Select-Object -ExpandProperty $regName) | |
| } | |
| Switch ($regCfg["ActionType"]) { | |
| "Set" { | |
| $regType = $regCfg["ActionValue"]["Type"] | |
| $regValue = $regCfg["ActionValue"]["Value"] | |
| if ($regCurrentValue -ne $regValue) { | |
| if (-not(Test-Path -Path $regKey -PathType Container)) { | |
| try { | |
| Write-Log "Creating registry key: $regKey" | |
| New-Item -Path $regKey -ItemType Container -Force -ErrorAction Stop | |
| } catch { | |
| Write-Log -Throw "Failed to create registry key" -Exception $_.Exception | |
| } | |
| } | |
| Set-RegValue -regKey $regKey -regName $regName -regValue $regValue -regType $regType | |
| } | |
| } | |
| "Replace" { | |
| $regReplace = $regCfg["ActionValue"]["Replace"] | |
| $regWith = $regCfg["ActionValue"]["With"] | |
| if ($regCurrentValue -like "*$($regReplace)*") { | |
| $regType = (Get-Item -Path $regKey).GetValueKind($regName) | |
| $regValue = ($regCurrentValue -Replace $regReplace, $regWith) | |
| Set-RegValue -regKey $regKey -regName $regName -regValue $regValue -regType $regType | |
| } | |
| } | |
| "DeleteProperty" { | |
| if ((Test-Path $regKey) -and ((Get-Item $regKey | Select-Object -ExpandProperty Property) -contains $regName)) { | |
| Write-Log "Deleting property: $regKey | $regName" | |
| Remove-ItemProperty -Force -Path $regKey -Name $regName -Verbose | |
| } else { | |
| Write-Log "Property doesn't exist, nothing to do: $regKey | $regName" | |
| } | |
| } | |
| "DeleteKey" { | |
| if (Test-Path $regKey) { | |
| Write-Log "Deleting key: $regName" | |
| Remove-Item -Recurse -Force -Path $regKey -Verbose | |
| } else { | |
| Write-Log "Key doesn't exist, nothing to do: $regKey" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Function Invoke-Scriptblock-With-Timeout($ScriptBlock, $ArgumentList = @(), $Timeout = 0) { | |
| if ($Timeout -le 0) { $Timeout = 10 } | |
| $jobExpires = (Get-Date).AddSeconds($Timeout) | |
| $job = Start-Job -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList | |
| while (((Get-Date) -lt $jobExpires) -and ((Get-Job -Id $job.Id).State -ne 'Completed')) { | |
| Start-Sleep -Milliseconds 250 | |
| } | |
| $result = @{ 'completed' = ((Get-Job -Id $job.Id).State -eq 'Completed'); 'output' = @($job | Receive-Job) } | |
| Stop-Job $job -PassThru | Remove-Job | |
| return $result | |
| } | |
| Function Test-App-Response() { | |
| Write-Log "Fetching output from 'netbird status'.." | |
| $result = Invoke-Scriptblock-With-Timeout -ScriptBlock { netbird status } | |
| if ($true -ne $result.completed) { | |
| Write-Log "Timeout from 'netbird status'; Stopping service.." | |
| Get-Process -Name 'netbird' | Stop-Process -Force -Verbose | |
| Stop-Service 'netbird' -Force -Verbose -ErrorAction SilentlyContinue | |
| Test-App-Service | |
| } else { | |
| Write-Log " === Netbird service status ===" | |
| Write-Log -Prefix " " -Message @($result.output) | |
| Write-Log " ==============================" | |
| } | |
| } | |
| if (-not (Test-IsAdmin)) { | |
| Write-Log -IsError "This script needs to be run as administrator." -Throw | |
| } | |
| Invoke-PruneFolder -Path $logFolder | |
| # disable progress bar to significantly speed up Invoke-WebRequest | |
| # https://stackoverflow.com/questions/28682642/powershell-why-is-using-invoke-webrequest-much-slower-than-a-browser-download | |
| $ProgressPreference = 'SilentlyContinue' | |
| # check for updates; this will also install netbird if the executable is missing | |
| if (Test-App-UpdateAvailable) { Invoke-Download-and-Install-MSI-App } | |
| # check the service is in good order; this will also install netbird if the service is missing | |
| if ((Test-App-Service) -eq "missing") { | |
| if (Test-Path $netbirdExePath) { | |
| Write-Log "Installing Netbird service via executable: $($netbirdExePath) service install" | |
| & $netbirdExePath service install | |
| Test-App-Service | |
| } else { | |
| Invoke-Download-and-Install-MSI-App | |
| } | |
| } | |
| # check netbird is responsive | |
| Test-App-Response | |
| # check and fix config | |
| Test-And-Configure-Netbird | |
| if ($NetbirdUiStartup -eq 1) { | |
| # check the UI is configured to start on logon | |
| Update-RegValues -regKeys $regKeys | |
| # Ensure the netbird app isn't hidden in system tray | |
| Test-And-Promote-App-In-SystemTray -appFileName 'netbird-ui.exe' | |
| } | |
| } catch { | |
| $isError = $true | |
| Write-Log "Exception thrown" -Exception $_.Exception | |
| Throw | |
| } finally { | |
| if ($isError) { | |
| Write-Log -IsWarning "Completed with errors." | |
| } | |
| Stop-Transcript -Verbose | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is the code I am using to keep Windows clients up to date. It is based on a template that I use for all Intune scripts. The script is distributed using an Intune win32-app which schedules this script to run hourly as the SYSTEM user.
The script will
netbird service installif Netbird is installedC:\ProgramData\_Intune\LogsCoPilot summary
🛠️ Purpose
This script is designed to automate the update and configuration of Netbird, a secure networking tool, on Windows systems. It checks for updates from GitHub, installs them if needed, ensures the service is running, configures the application, and sets up the UI to start automatically.
🔍 Key Features & Workflow
1. Parameters
The script accepts several parameters:
NetbirdManagementUrl,NetbirdAdminUrl: URLs for Netbird configuration.ForcedMinimumVersion: Minimum version to enforce.NetbirdUrlScheme: URL scheme (default ishttps).NetbirdUiStartup: Whether to auto-start the UI.WorkingFolderName: Folder name for logs and downloads.2. Logging Setup
Start-Transcriptto capture all output.3. Functions Defined
The script defines many helper functions:
Write-Log: Custom logging with severity levels.Test-IsAdmin: Checks if the script is run as administrator.Invoke-Download: Downloads files from URLs.Remove-ItemRepeated: Tries to delete a file multiple times.Invoke-PruneFolder: Deletes old log files.Get-App-Github-LatestRelease: Fetches the latest Netbird release from GitHub.Invoke-Download-and-Install-MSI-App: Downloads and installs the MSI package.Test-And-Configure-Netbird: Validates and updates the Netbird config file.Test-App-Service: Ensures the Netbird service is running and set to auto-start.Get-App-CurrentVersion: Gets the installed Netbird version.Test-App-UpdateAvailable: Checks if an update is available.Test-And-Promote-App-In-SystemTray: Promotes the Netbird UI icon in the system tray.Set-RegValue&Update-RegValues: Registry manipulation for auto-start and tray visibility.4. Main Execution Flow
✅ Summary
This script is a robust automation tool for managing Netbird on Windows. It ensures: