├── .gitignore ├── API └── Update-NinjaOne-Organizational-Policies-by-Role │ ├── Get-NinjaOne-Orgs-List.ps1 │ ├── README.md │ └── Update-NinjaOne-Org-Policies.ps1 ├── AutoElevate ├── Deploy-AutoElevate-ScriptVariables.png ├── Deploy-AutoElevate.ps1 └── README.md ├── Blackpoint-Cyber-SNAP ├── Deploy-Blackpoint-Cyber-SNAP-Windows.ps1 └── Deploy-Blackpoint-Cyber-SNAP-macOS.sh ├── Browser-Update-Deadlines ├── Browser-Update-Deadline-ScriptVariables.png └── Browser-Update-Deadline.ps1 ├── Browser-and-Office-Autoupdate └── Browser-Office-Autoupdate.ps1 ├── ConnectSecure ├── Deploy-ConnectSecure-V3.ps1 ├── Deploy-ConnectSecure-V4.ps1 └── README.md ├── DefensX ├── Deploy-DefensX.ps1 └── README.md ├── ImmyBot └── Deploy-ImmyBot.ps1 ├── Manage-Visual-C++-Redistributables-in-Windows ├── README.md ├── VisualC-Redistributable-Cleanup-ScriptVariables.png └── VisualC-Redistributable-Cleanup.ps1 ├── Microsoft-ASR-Attack-Surface-Reduction-Rules ├── Microsoft-Defender-ASR-Output-Example.png ├── Microsoft-Defender-ASR-ScriptVariables.png ├── README.md └── Set-MicrosoftASR-Rule.ps1 ├── Microsoft-Updates ├── Extend WinRE Recovery Partition KB5034441 │ ├── Extend-WinRE-RecoveryPartition-CustomField-ScriptVariables.png │ ├── Extend-WinRE-RecoveryPartition-CustomField.ps1 │ ├── Extend-WinRE-RecoveryPartition-NoCustomFields.ps1 │ └── README.md └── Patch WinRE for KB5034232 With Download │ ├── PatchWinREScript_2004plus_withdownload_KB5034232.ps1 │ └── PatchWinREScript_2004plus_withdownload_KB5034232_ScriptVariables.png ├── Monitor-Office-365-Version ├── Get-OfficeUpdateInfo-Registry.ps1 ├── Monitor-Office-365-Version-ScriptVariables.png ├── Monitor-Office-365-Version.ps1 ├── Update-Microsoft-Office-Click2Run-Monthly-Channel.ps1 └── Update-Office-CDNBaseUrl-From-UpdateChannel.ps1 ├── README.md ├── ScreenConnect └── Manage-and-Remove │ └── Manage-ScreenConnect-Windows.ps1 ├── Stairwell-Forwarder ├── Deploy-Stairwell-Forwarder-Windows.ps1 └── README.md ├── UTILITIES └── Script-Variables-Parameters │ └── Convert-Script-Variables-To-Parameters.ps1 ├── Windows-Registry-Hardening └── Harden-Security-Windows-Registry.ps1 └── Zorus ├── Deploy-Zorus-Agent.ps1 └── Remove-Zorus-Archon-Agent.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | *.plist 2 | .DS_Store 3 | .fslckout 4 | .Ulysses* 5 | *.fossil 6 | -------------------------------------------------------------------------------- /API/Update-NinjaOne-Organizational-Policies-by-Role/Get-NinjaOne-Orgs-List.ps1: -------------------------------------------------------------------------------- 1 | <# Get-NinjaOne-Orgs-List.ps1 2 | 3 | Created by David Szpunar of Servant 42. 4 | No warranties expressed or implied. MIT license, free to use or modify. 5 | 6 | REQUIRES: Config module for secrets from https://discord.com/channels/676451788395642880/1126703581609865296 7 | config.ps1 file in same folder (see comments below) with the following three variables defined: 8 | $ClientID = 'op://vault/item/clientid' 9 | $ClientSecret = 'op://vault/item/credential' 10 | $RefreshToken = 'op://vault/item/refreshtoken' 11 | Alternately, define those variables directly in this script in lieu of loading from secrets manager, but 12 | keep in mind that lowers the security and this script becomes very poweful on its own. 13 | 14 | HOW TO USE: Get a refresh token, put in your secrets vault referenced from config, run script from a folder 15 | where you want to end up with the three files defined in the CONFIG section for review. To update policies 16 | with accompanying script, edit the fileOrgsAndPolicies file and set new policy numbers in the NEW_policy_id 17 | column for only those organizations you want to update and use the update script against that file. 18 | 19 | Version 0.1.0 - Released 2023-10-07 - initial release 20 | #> 21 | 22 | # Docs for NinjaOne API: https://app.ninjarmm.com/apidocs/ 23 | # Docs for NinjaOne PowerShell module: https://github.com/homotechsual/NinjaOne 24 | 25 | ##### CONFIG 26 | $fileOrgsAndPolicies = './OrgsAndPolicies.csv' 27 | $fileOrgs = './Orgs.csv' 28 | $filePolicies = './Policies.csv' 29 | 30 | ######################### DEFS 31 | # $POLICY_TYPES = @('1','201','202','22','205','206','11','12','31') 32 | $POLICY_TYPES = @('1','201','202') 33 | # $POLICY_NODES = @('MAC','CLOUD_MONITOR_TARGET','HYPERV_VMM_HOST','HYPERV_VMM_GUEST','WINDOWS_WORKSTATION','WINDOWS_SERVER') 34 | $POLICY_NODES = @('WINDOWS_WORKSTATION','WINDOWS_SERVER') 35 | ######################### END DEFS 36 | 37 | # Require the load-config file from https://discord.com/channels/676451788395642880/1126703581609865296 and load config.ps1 38 | . "./load-config.ps1" 'config.ps1' 39 | 40 | ##### END CONFIG 41 | 42 | # Splat the parameters - easier to read! 43 | $ReconnectionParameters = @{ 44 | Instance = 'us' 45 | ClientID = $ClientID 46 | ClientSecret = $ClientSecret 47 | RefreshToken = $RefreshToken 48 | UseTokenAuth = $True 49 | } 50 | 51 | Import-Module NinjaOne 52 | Connect-NinjaOne @ReconnectionParameters 53 | 54 | # To test with a single item, uncomment the rest of the lines temporarily. 55 | $Organisations = Get-NinjaOneOrganisations -detailed #| select-object -First 1 56 | $Roles = Get-NinjaOneRoles # | select-object -First 1 57 | $Policies = Get-NinjaOnePolicies # | select-object -First 1 58 | 59 | if (($Organisations | Measure-Object).Count -lt 1) { 60 | Write-Host "No items found in Organizations, something went wrong. Quitting." 61 | exit 1 62 | } 63 | 64 | 65 | $Organisations | select-object -Property id,name,description,nodeApprovalMode | Export-Csv -Path "$fileOrgs" -NoTypeInformation 66 | 67 | 68 | $output = @() 69 | ForEach ($org in $Organisations) { 70 | $org_entry = @{ 71 | 'org_id' = $org.id 72 | 'org_name' = $org.name 73 | 'org_description' = $org.description 74 | 'org_nodeApprovalMode' = $org.nodeapprovalmode 75 | } 76 | Write-Host "$($org.id),$($org.name)" 77 | 78 | foreach ($orgpolicy in ($org.policies | Where-Object -Property 'noderoleid' -In $POLICY_TYPES)) { 79 | $policy = $Policies | Where-Object -Property 'id' -eq $orgpolicy.policyid 80 | $role = $Roles | Where-Object -Property 'id' -eq $orgpolicy.noderoleid 81 | 82 | if ($role.chassisType -eq 'UNKNOWN') { 83 | $role_type = $role.nodeClass 84 | } else { 85 | $role_type = $role.nodeClass + ' ' + $role.chassisType 86 | } 87 | 88 | # Write-Host "Policy: " $policy.name " Node Role and Type: " $role.nodeClass " " $role.chassisType " (" $role.id ")" 89 | $lineitem = $org_entry 90 | $lineitem += @{ 91 | 'role_id' = $role.id 92 | 'role_type' = $role_type 93 | 'role_nodeClass' = $role.nodeClass 94 | 'policy_id' = $policy.id 95 | 'NEW_policy_id' = '' 96 | 'policy_name' = $policy.name 97 | 'policy_description' = $policy.description 98 | } 99 | $output += $lineitem 100 | } 101 | # Write-Host "Entry: " $lineitem 102 | } 103 | 104 | $Organisations 105 | | select-object -Property id,name,description,nodeApprovalMode 106 | | Export-Csv -Path "$fileOrgs" -NoTypeInformation 107 | 108 | #Write-host $output 109 | $output 110 | # | Select-Object -Property org_id,org_name,org_nodeApprovalMode,org_description,role_id,role_type,role_nodeClass,policy_id,NEW_policy_id,policy_name,policy_description 111 | | Select-Object -Property role_type,role_nodeClass,org_name,policy_id,NEW_policy_id,policy_name,org_id,role_id,policy_description 112 | | Sort-Object -Property role_nodeClass,org_id,role_type 113 | | Export-Csv -Path "$fileOrgsAndPolicies" -NoTypeInformation 114 | 115 | $Policies 116 | | select-object -Property id,nodeClass,name,description 117 | | Where-Object -Property 'nodeClass' -In $POLICY_NODES 118 | | Sort-Object -Property nodeClass,name 119 | | Export-Csv -Path "$filePolicies" -NoTypeInformation 120 | 121 | 122 | -------------------------------------------------------------------------------- /API/Update-NinjaOne-Organizational-Policies-by-Role/README.md: -------------------------------------------------------------------------------- 1 | # API: Update NinjaOne Organizational Policies by Role (& Retrieve Orgs and Policies for Edit First) 2 | This set of scripts lets you retrieve the organizations and policies (and the mappings between them) from your NinjaOne instance via the API. Written and tested in PowerShell 7 on macOS but should work on any system and probably backwards compatible. Requires NinjaOne API creds and refresh token that's active. 3 | 4 | Uses the secrets config loader (to be documented soon) but you can hardcode credentials yourself if you want to. 5 | 6 | Documentation for each script is at the top, one to get and one to update after you edit the CSV with the mappings you want to change. 7 | 8 | This is a relatively quick edit/document of older quick 'n dirty scripts I wrote a while back, so double check things yourself but it worked for my purpose when we onboarded to Ninja and needed to globally update many organizations to new policies we hadn't even created in some cases when we'd created organizations. Editing these by hand across multiple organizations in the UI is time-consuming as you can only edit one at a time, so this script speeds it up in bulk. 9 | 10 | The config at the top of the get script limits which policy types and device node types you want to retrieve; the commented out lines are all possible values in case you want to modify them, but they default to only Windows workstations, laptops, and servers, and the policies that apply to them--changing these around is left as an exercise for you. 11 | -------------------------------------------------------------------------------- /API/Update-NinjaOne-Organizational-Policies-by-Role/Update-NinjaOne-Org-Policies.ps1: -------------------------------------------------------------------------------- 1 | <# Update-NinjaOne-Org-Policies.ps1 2 | 3 | Created by David Szpunar of Servant 42. 4 | No warranties expressed or implied. MIT license, free to use or modify. 5 | 6 | REQUIRES: Config module for secrets from https://discord.com/channels/676451788395642880/1126703581609865296 7 | config.ps1 file in same folder (see comments below) with the following three variables defined: 8 | $ClientID = 'op://vault/item/clientid' 9 | $ClientSecret = 'op://vault/item/credential' 10 | $RefreshToken = 'op://vault/item/refreshtoken' 11 | Alternately, define those variables directly in this script in lieu of loading from secrets manager, but 12 | keep in mind that lowers the security and this script becomes very poweful on its own. 13 | 14 | HOW TO USE: Get a refresh token, put in your secrets vault referenced from config, run script from a folder 15 | where the fileOrgsAndPolicies CSV file exists with at least the columns from the $required_headers array exist 16 | and set new policy numbers in the NEW_policy_id column for only those organizations you want to update and 17 | run this script either the predefined default file name or pass the file name you're using as the 18 | only (-fileOrgsAndPolicies) parameter. 19 | 20 | You can use the accompanying Get-NinjaOne-Orgs-List.ps1 script to 21 | obtain a list of policies, orgs, and combined orgs and policies mapping with an empty NEW_policy_id column 22 | you can use to define your updates for use here. 23 | 24 | NOTE: You MUST pass the -ReallyChange flag to update the policy mappings! You'll get only a display of potential 25 | actions without it, for safety to ensure you intend the changes. 26 | 27 | Version 0.1.0 - Released 2023-10-07 - initial release 28 | #> 29 | param ( 30 | [string]$fileOrgsAndPolicies = './OrgsAndPolicies.csv', 31 | [switch]$ReallyChange 32 | ) 33 | 34 | # Define the CSV headers required at a minimum to make this work; the rest are optional for display/human sorting. 35 | $required_headers = @('org_id', 'NEW_policy_id', 'role_id'); 36 | 37 | ##### CONFIG 38 | # Require the load-config file from https://discord.com/channels/676451788395642880/1126703581609865296 and load config.ps1 39 | . "./load-config.ps1" 'config.ps1' 40 | 41 | ##### END CONFIG 42 | 43 | # Splat the parameters - easier to read! 44 | $ReconnectionParameters = @{ 45 | Instance = 'us' 46 | ClientID = $ClientID 47 | ClientSecret = $ClientSecret 48 | RefreshToken = $RefreshToken 49 | UseTokenAuth = $True 50 | } 51 | 52 | Import-Module NinjaOne 53 | Connect-NinjaOne @ReconnectionParameters 54 | 55 | # Import the CSV file with changes and verify required column names all exist. 56 | if(Test-Path -Path $fileOrgsAndPolicies -PathType leaf) { 57 | $Updates = Import-Csv -Path $fileOrgsAndPolicies 58 | } else { 59 | Write-Host "File '$fileOrgsAndPolicies' does not exist, quitting." 60 | exit 61 | } 62 | 63 | $headers = ($Updates | Get-Member -MemberType NoteProperty).Name 64 | Write-Host $headers 65 | foreach ($header in $required_headers) { 66 | if($header -notin $headers) { 67 | write-Host "Required column '$header' not found, quitting. Required columns:" 68 | write-host $required_headers 69 | exit 70 | } 71 | } 72 | 73 | # Read in organizations so we can pull name or other information as needed for display. 74 | $Organisations = Get-NinjaOneOrganisations 75 | if (($Organisations | Measure-Object).Count -lt 1) { 76 | Write-Host "No items found in Organizations, something went wrong. Quitting." 77 | exit 1 78 | } 79 | 80 | $countChanged = 0 81 | $countTotal = 0 82 | foreach ($update in $Updates) { 83 | $countTotal++ 84 | $org = $Organisations | Where-Object -Property 'id' -Like $update.org_id 85 | if(!$org) { 86 | Write-Host "No organization found with ID $($update.org_id), skipping line $countTotal." 87 | continue 88 | } 89 | if($update.NEW_policy_id -gt 0 -and $update.NEW_policy_id -ne $update.policy_id) { # Only update if new is non-blank and different from existing policy. 90 | Write-Host "For org '$($org.name)' (ID $($update.org_id)) change policy $($update.policy_id) to $($update.NEW_policy_id) for Role ID $($update.role_id)." 91 | if($ReallyChange) { 92 | Update-NinjaOneNodeRolePolicyAssignment -organisationId $update.org_id -nodeRoleId $update.role_id -policyId $update.NEW_policy_id 93 | } 94 | $countChanged++ 95 | } 96 | } 97 | 98 | if($ReallyChange) { 99 | Write-Host "`nUpdated $countChanged record(s) of $countTotal." 100 | } else { 101 | Write-Host "`nAdd parameter `$ReallyChange to actually make changes, just testing. NO change(s) made." 102 | Write-Host "Would have updated $countChanged record(s) of $countTotal." 103 | } 104 | -------------------------------------------------------------------------------- /AutoElevate/Deploy-AutoElevate-ScriptVariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/AutoElevate/Deploy-AutoElevate-ScriptVariables.png -------------------------------------------------------------------------------- /AutoElevate/Deploy-AutoElevate.ps1: -------------------------------------------------------------------------------- 1 | <# Deploy-AutoElevate.ps1 2 | Deploy (or Remove) AutoElevate Agent to Windows systems. 3 | 4 | Review the CONFIG section of the script before deploying! 5 | 6 | Optionally create one or more Ninja Custom Fields, all standard text fields, with Automation Read permissions, with the names listed in the CONFIG section under "NinjaRMM Custom Field Names" (or ajdust these variables to match your custom field names). If you aren't using any of these custom fields, set the field names to a blank string. Parameters or Script Variables, if you provide them, will override any values pulled from custom fields. 7 | 8 | You can mix and match, using some custom fields, some Script Variables/Parameters (which will override matching custom fields), and some defaults. 9 | 10 | The arguments to this script are defined at: 11 | https://support.cyberfox.com/115000883892-New-to-AutoElevate-START-HERE/115003703811-System-Agent-Installation 12 | 13 | Most of the arguements are passed to the MSI installer, and the defaults 14 | 15 | This script accepts the following arguments, which may also be set as Custom Fields or Script Variables: 16 | -LicenseKey (string) 17 | Pull from custom field "autoelevateLicenseKey" (or as defined in CONFIG) if not provided as parameter or Script Variable. 18 | 19 | -CompanyName (string) 20 | Default to the Ninja Organization Name, or pull from custom field "autoelevateCompanyName" (or as defined in CONFIG). 21 | 22 | -CompanyInitials (string) 23 | Pull from custom field "autoelevateCompanyInitials" (or as defined in CONFIG), or parameter/script variable. This is an OPTIONAL setting and can be left blank/excluded, per AutoElevate documentation. 24 | 25 | -LocationName (string) 26 | Default to the Ninja Organization Location Name, or Pull from custom field "autoelevateLocationName" 27 | 28 | -AgentMode (string, optional) 29 | One of the following string values as defined in the AutoElevate documentation, or pull from custom field "autoelevateAgentMode" (or as defined in CONFIG), or leave blank to assume default from AutoElevate documentation OR to not change the existing value if (re)installing/upgrading an already-deployed agent with -Force: 30 | audit (default) 31 | live 32 | policy 33 | 34 | -BlockerMode (string, optional) 35 | One of the following string values as defined in the AutoElevate documentation, or pull from custom field "autoelevateBlockerMode" (or as defined in CONFIG), or leave blank to assume default from AutoElevate documentation OR to not change the existing value if (re)installing/upgrading an already-deployed agent with -Force: 36 | disabled (default) 37 | live 38 | audit 39 | 40 | -Force (boolean/checkbox) 41 | Adding this switch or Script Variables Checkbox will attempt (re)installation even if the service already exists on the system. 42 | 43 | -Uninstall (boolean/checkbox) 44 | Use this switch or Script Variables Checkbox to attempt to uninstall the agent if it's currently installed on the system. If it fails using the registry MSI Uninstall command, it will download the installation MSI and attempt to uninstall with it. If that fails, it's up to you to troubleshoot, but first try a reboot and attempt uninstall again. 45 | 46 | -Verbose 47 | Pass the -Verbose parameter to output additional debugging information when running the script. Do not use this as a Script Variable, only a Parameter. NOTE: Prints details such as the license key or other parameter/custom field values to STDOUT. 48 | 49 | Output from each command is provided for feedback. Every parameter or switch can be set via Script Variables, and the first four also support Custom Fields. 50 | With minor adjustment, they could also use NinjaRMM Custom Documentation Fields. 51 | 52 | Version 0.0.1 - 2024-03-05 - Initial Version by David Szpunar 53 | Version 0.0.2 - 2024-03-05 - Added AgentMode switch, fixed several logic errors in first draft, MSI uninstallation 54 | #> 55 | [CmdletBinding()] 56 | param( 57 | [Parameter(Mandatory = $false)][string] $LicenseKey, 58 | [Parameter(Mandatory = $false)][string] $CompanyName, 59 | [Parameter(Mandatory = $false)][string] $CompanyInitials, 60 | [Parameter(Mandatory = $false)][string] $LocationName, 61 | [Parameter(Mandatory = $false)][string] $AgentMode, 62 | [Parameter(Mandatory = $false)][string] $BlockerMode, 63 | [switch] $Force, 64 | [switch] $Uninstall 65 | ) 66 | 67 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 68 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 69 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 70 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters 71 | foreach ($param in $switchParameters.keys) { 72 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue 73 | if ($var) { 74 | $envVarName = $var.Name.ToLower() 75 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 76 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { 77 | # Checkbox variables 78 | $PSBoundParameters[$envVarName] = $true 79 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 80 | } 81 | elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { 82 | # non-Checkbox string variables 83 | $PSBoundParameters[$envVarName] = $envVarValue 84 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 85 | } 86 | } 87 | } 88 | ### END PROCESS SCRIPT VARIABLES 89 | 90 | ##### CONFIG 91 | if (!$CompanyName) { 92 | # Default to the Ninja Organization Name, if not passed in via argument 93 | $CompanyName = $env:NINJA_ORGANIZATION_NAME 94 | } 95 | if (!$LocationName) { 96 | # Default to the Ninja Organization Location Name, if not passed in via argument 97 | $LocationName = $env:NINJA_LOCATION_NAME 98 | } 99 | 100 | $InstallLocation = $env:TEMP # Temporary downloaded installer location (can leave as temp folder) 101 | $InstallFilename = 'AutoElevate.msi' # Temporary downloaded installer filename (can leave as-is) 102 | 103 | # NinjaRMM Custom Field Names (third can be secure, all must have Script Read permissions) - leave blank or delete to ignore/not use 104 | $customLicenseKey = 'autoelevateLicenseKey' 105 | $customCompanyName = 'autoelevateCompanyName' 106 | $customCompanyInitials = 'autoelevateCompanyInitials' 107 | $customLocationName = 'autoelevateLocationName' 108 | $customAgentMode = 'autoelevateAgentMode' 109 | $customBlockerMode = 'autoelevateBlockerMode' 110 | 111 | # Service Name (from Windows Services list) 112 | $ServiceName = "AutoElevate Agent" 113 | 114 | # Application Name (from Add/Remove Programs) 115 | $AppName = "AutoElevate" 116 | 117 | # Agent Installer URL 118 | $URL = "https://autoelevate-installers.s3.us-east-2.amazonaws.com/current/AESetup.msi" 119 | ##### END CONFIG 120 | 121 | function Test-IsElevated { 122 | $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() 123 | $p = New-Object System.Security.Principal.WindowsPrincipal($id) 124 | $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) 125 | } 126 | # If not elevated error out. Admin priveledges are required to install or remove the application. 127 | if (-not (Test-IsElevated)) { 128 | Write-Error -Message "Access Denied. Please run with Administrator or SYSTEM privileges." 129 | exit 1 130 | } 131 | 132 | ##### FUNCTIONS 133 | function Uninstall-App-ViaMsi { 134 | $ErrorActionPreference = "Stop" 135 | # $InstallerFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".msi") 136 | $InstallerFile = "$InstallLocation\$InstallFilename.msi" 137 | (New-Object System.Net.WebClient).DownloadFile($url, $InstallerFile) 138 | $InstallerLogFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".log") 139 | Write-Verbose "Installer File for Uninstallation: $InstallerFile" 140 | # LICENSE_KEY="123456789ABCDEFGYOURLICENSEKEYHERE" COMPANY_NAME="Contoso, Inc." COMPANY_INITIALS="CI" LOCATION_NAME="Main Office" AGENT_MODE="live" BLOCKER_MODE="disabled" 141 | $Arguments = " /c msiexec /x `"$InstallerFile`" /qn /norestart /l*v `"$InstallerLogFile`" REBOOT=REALLYSUPPRESS " 142 | Write-Host "UninstallerLogFile: $InstallerLogFile" 143 | Write-Verbose "Uninstaller Arguments: $Arguments" 144 | $Process = Start-Process -Wait cmd -ArgumentList $Arguments -PassThru 145 | if ($Process.ExitCode -ne 0) { 146 | Get-Content $InstallerLogFile -ErrorAction SilentlyContinue | Select-Object -Last 100 147 | } 148 | Write-Host "Exit Code: $($Process.ExitCode)" 149 | Write-Host "ComputerName: $($env:ComputerName)" 150 | 151 | # Return the exit code from the installation as the script exit code: 152 | exit $($Process.ExitCode) 153 | } 154 | 155 | 156 | function Uninstall-App { 157 | [CmdletBinding()] 158 | param( 159 | [Parameter(Mandatory = $true)][string] $AppName 160 | ) 161 | $InstallInfo = ( 162 | Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* ` 163 | | Select-Object DisplayName, DisplayVersion, UninstallString, Publisher, InstallDate, InstallLocation ` 164 | | Where-Object { $_.DisplayName -eq $AppName }) + ( 165 | Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* ` 166 | | Select-Object DisplayName, DisplayVersion, UninstallString, Publisher, InstallDate, InstallLocation ` 167 | | Where-Object { $_.DisplayName -eq $AppName }) 168 | $AppVersion = $InstallInfo.DisplayVersion 169 | Write-Verbose "Installed Version: $AppVersion" 170 | $UninstallString = "$(($InstallInfo.UninstallString).Replace('/I{', '/X{')) /quiet /norestart" 171 | Write-Verbose "Uninstall String: $UninstallString" 172 | 173 | if ($InstallInfo) { 174 | Write-Host "Uninstalling now." 175 | Write-Verbose $InstallInfo | Format-List 176 | $Process = Start-Process cmd.exe -ArgumentList "/c $UninstallString" -Wait -PassThru 177 | if ($Process.ExitCode -eq 1603) { 178 | Write-Host "Uninstallation attempt failed with error code: $($Process.ExitCode). Please review manually." 179 | Write-Host "Trying to install via MSI..." 180 | Uninstall-App-ViaMsi 181 | } 182 | elseif ($Process.ExitCode -ne 0) { 183 | Write-Host "Uninstallation attempt failed with error code: $($Process.ExitCode). Trying to uninstall with MSI..." 184 | Uninstall-App-ViaMsi 185 | } 186 | else { 187 | Write-Host "Uninstallation attempt completed without error." 188 | } 189 | exit $($Process.ExitCode) 190 | } 191 | else { 192 | Write-Host "The application" $AppName "is not installed based on a search of the registry. Quitting" 193 | exit 0 194 | } 195 | exit 0 196 | } 197 | 198 | function Install-App { 199 | $ErrorActionPreference = "Stop" 200 | # $InstallerFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".msi") 201 | $InstallerFile = "$InstallLocation\$InstallFilename" 202 | (New-Object System.Net.WebClient).DownloadFile($url, $InstallerFile) 203 | $InstallerLogFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".log") 204 | Write-Verbose "Installer File: $InstallerFile" 205 | # EXAMPLE: LICENSE_KEY="123456789ABCDEFGYOURLICENSEKEYHERE" COMPANY_NAME="Contoso, Inc." COMPANY_INITIALS="CI" LOCATION_NAME="Main Office" AGENT_MODE="audit" BLOCKER_MODE="disabled" 206 | $Arguments = " /c msiexec /i `"$InstallerFile`" /qn /norestart /l*v `"$InstallerLogFile`" REBOOT=REALLYSUPPRESS LICENSE_KEY=`"$LicenseKey`" COMPANY_NAME=`"$CompanyName`" LOCATION_NAME=`"$LocationName`"" 207 | if(!([String]::IsNullOrEmpty($AgentMode))) { 208 | $Arguments += " AGENT_MODE=`"$AgentMode`"" 209 | } 210 | if(!([String]::IsNullOrEmpty($BlockerMode))) { 211 | $Arguments += " BLOCKER_MODE=`"$BlockerMode`"" 212 | } 213 | if(!([String]::IsNullOrEmpty($CompanyInitials))) { 214 | $Arguments += " COMPANY_INITIALS=`"$CompanyInitials`"" 215 | } 216 | Write-Host "InstallerLogFile: $InstallerLogFile" 217 | Write-Verbose "Installer Arguments: $Arguments" 218 | $Process = Start-Process -Wait cmd -ArgumentList $Arguments -PassThru 219 | if ($Process.ExitCode -ne 0) { 220 | Get-Content $InstallerLogFile -ErrorAction SilentlyContinue | Select-Object -Last 100 221 | } 222 | Write-Host "Exit Code: $($Process.ExitCode)" 223 | Write-Host "ComputerName: $($env:ComputerName)" 224 | 225 | # Return the exit code from the installation as the script exit code: 226 | exit $($Process.ExitCode) 227 | } 228 | 229 | ##### SCRIPT LOGIC 230 | 231 | if ($Uninstall) { 232 | Write-Verbose "Attemtping app uninstall." 233 | Uninstall-App $AppName 234 | exit 0 235 | } 236 | 237 | if (!$LicenseKey -and !([string]::IsNullOrEmpty($customLicenseKey))) { 238 | $LicenseKey = Ninja-Property-Get $customLicenseKey 239 | Write-Host "License Key from $customCompanyName Custom Field: [masked for output]" 240 | } 241 | If (!$CompanyName -and !([string]::IsNullOrEmpty($customCompanyName))) { 242 | $CompanyName = Ninja-Property-Get $customCompanyName 243 | Write-Host "Company Name From $customLicenseKey Custom Field: $CompanyName" 244 | } 245 | If (!$CompanyInitials -and $null -eq $customCompanyInitials) { # Blank is OK for this optional field 246 | $CompanyInitials = Ninja-Property-Get $customCompanyInitials 247 | Write-Host "Company Initials from $customCompanyInitials Custom Field: $CompanyInitials" 248 | } 249 | if (!$LocationName -and !([string]::IsNullOrEmpty($customLocationName))) { 250 | $LocationName = Ninja-Property-Get $customLocationName 251 | Write-Host "Location Name from $customLocationName Custom Field: $LocationName" 252 | } 253 | if (!$AgentMode -and !([string]::IsNullOrEmpty($customAgentMode))) { 254 | $AgentMode = Ninja-Property-Get $customAgentMode -ErrorAction SilentlyContinue 255 | Write-Host "Location Name from $customAgentMode Custom Field: $AgentMode" 256 | } 257 | if (!$BlockerMode -and !([string]::IsNullOrEmpty($customBlockerMode))) { 258 | $BlockerMode = Ninja-Property-Get $customBlockerMode -ErrorAction SilentlyContinue 259 | Write-Host "Location Name from $customBlockerMode Custom Field: $BlockerMode" 260 | } 261 | if ($AgentMode -ne '' -and $AgentMode -ne "live" -and $AgentMode -ne "policy" -and $AgentMode -ne "audit") { 262 | $AgentMode = "" # Default to audit if not explicitly set (also default if not provided to installer) (use documented default) 263 | } 264 | if ($BlockerMode -ne '' -and $BlockerMode -ne "disabled" -and $BlockerMode -ne "live" -and $BlockerMode -ne "audit") { 265 | $BlockerMode = "" # Default to disabled if not explicitly set (use documented default) 266 | } 267 | 268 | if (!$Uninstall -and ([string]::IsNullOrEmpty($LicenseKey) -or [string]::IsNullOrEmpty($CompanyName) -or [string]::IsNullOrEmpty($LocationName))) { 269 | Write-Host "One or more required fields (LicenseKey, CompanyName, LocationName) are empty. Exiting." 270 | exit 1 271 | } 272 | 273 | $IsInstalled = (Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { $_.DisplayName -eq $AppName }) + (Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { $_.DisplayName -eq $AppName }) 274 | 275 | If ( !$Force -and ((Get-Service $ServiceName -ErrorAction SilentlyContinue) -or $IsInstalled) ) { 276 | Write-Host "The service $ServiceName or app $AppName is already installed. Retry with -Force to attempt install anyway." 277 | exit 0 278 | } 279 | 280 | # Install if we get here and have not already completed 281 | Install-App 282 | -------------------------------------------------------------------------------- /AutoElevate/README.md: -------------------------------------------------------------------------------- 1 | # Deploy or Remove CyberFox AutoElevate Agent 2 | A basic PowerShell script that uses parameters, Script Variables, or Custom Fields to deploy the [AutoElevate](https://autoelevate.com/) agent from CyberFox using the parameters you define as documented in their support system at: [https://support.cyberfox.com/115000883892-New-to-AutoElevate-START-HERE/115003703811-System-Agent-Installation?from_search=141643848](https://support.cyberfox.com/115000883892-New-to-AutoElevate-START-HERE/115003703811-System-Agent-Installation?from_search=141643848) 3 | 4 | See comments at top of script for documentation and CONFIG section to make any adjustments. Create Custom Fields with the provided (customizable) field names and Automation Read permission in NinjaRMM if you want to use custom fields for some or all values (recommended for at least license key; this could be changed to a Documentation Custom Field pretty easily but is NOT currently, it's just a regular one. 5 | 6 | The `-Uninstall` parameter is provided to uninstall the agent if it already exists, and the `-Force` parameter will attempt (re) installation if the service already exists (with out this, it will quit if it finds the agent service to already exist!). 7 | 8 | ## Download Location 9 | The hardcoded download location in the script is the agent installer URL as provided by CyberFox directly, and they maintain it with the current version of the installer at the provided URL. You are welcome to edit the CONFIG section variable $URL to your own hosted version if you prefer for security or convenience reasons. 10 | 11 | ## Script Parameters 12 | The Script Parameters in NinjaRMM might look something like this (alternately, use parameters and/or custom fields only): 13 | ![Deploy-AutoElevate-ScriptVariables](Deploy-AutoElevate-ScriptVariables.png) 14 | 15 | Version 0.0.2 is the first public release. Thanks to @itnorm on Discord for the inspiration to put this together! -------------------------------------------------------------------------------- /Blackpoint-Cyber-SNAP/Deploy-Blackpoint-Cyber-SNAP-Windows.ps1: -------------------------------------------------------------------------------- 1 | #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!# 2 | #.______ __ ___ ______ __ ___ .______ ______ __ .__ __. .___________.## 3 | #| _ \ | | / \ / || |/ / | _ \ / __ \ | | | \ | | | |## 4 | #| |_) | | | / ^ \ | ,----'| ' / | |_) | | | | | | | | \| | `---| |----`## 5 | #| _ < | | / /_\ \ | | | < | ___/ | | | | | | | . ` | | |####### 6 | #| |_) | | `----./ _____ \ | `----.| . \ | | | `--' | | | | |\ | | |####### 7 | #|______/ |_______/__/ \__\ \______||__|\__\ | _| \______/ |__| |__| \__| |__|####### 8 | ##################################################################################################### 9 | ####################################╔═╗╔╗╔╔═╗╔═╗///╔╦╗╔═╗╔═╗╔═╗╔╗╔╔═╗╔═╗############################# 10 | ####################################╚═╗║║║╠═╣╠═╝/// ║║║╣ ╠╣ ║╣ ║║║╚═╗║╣ ###### Ver 2.2 04/09/2021 ### 11 | ####################################╚═╝╝╚╝╩ ╩╩/////═╩╝╚═╝╚ ╚═╝╝╚╝╚═╝╚═╝############################# 12 | 13 | <# 14 | Version 1.2.2 - 2023-11-10 - Add -Force switch parameter/Script Variable to reinstall even if service already exists 15 | Version 1.2.1 - 2023-08-24 - Fixes custom field input validation routines to use -Match rather than -NotMatch 16 | Version 1.2.0 - 2023-08-09 - Modified by David Szpunar separate documentation template/instance and custom field names into separate variables 17 | Version 1.1.0 - Modified by David Szpunar to add NinjaRMM code to pull from custom documentation field and validate the UID and EXE name 18 | Version 1.0.0 - Original script form Blackpoint Cyber docs at https://support.blackpointcyber.com/article/89-ninjaone-snap-installation 19 | (note 2023-08-09: directions no longer include script and recommend creating a new deployment for each client, but they still publish 20 | a PowerShell version at https://support.blackpointcyber.com/article/41-configuring-the-powershell-script that this adds custom fields 21 | integration to) 22 | #> 23 | 24 | [CmdletBinding()] 25 | param( 26 | [Parameter(Mandatory=$false)][switch] $Force 27 | ) 28 | if($env:force -eq 'true') { 29 | $Force = $true 30 | } 31 | 32 | ########### 33 | # EDIT ME 34 | ########### 35 | # The name of your NinjaRMM Documentation document that contains the custom fields below, assumes that there is both a template 36 | # and a single instance of the document with the same name (otheriwse change to Ninja-Property-Docs-Get calls below with separate 37 | # template and instance names): 38 | $CustomNinjaDocumentation = 'Deployments' 39 | 40 | # The UID of the customer from the download URL to the .exe file, from Blackpoint Cyber Portal, without slashes: 41 | $CustomerUIDCustomField = 'blackpointCyberSnapCustomerUid' 42 | 43 | # The name of the Custom NinjaRMM Documentation Field .exe file (without path, with extension) from the Blackpoint Cyber portal: 44 | $CustomerEXECustomField = 'blackpointCyberSnapExecutableName' 45 | 46 | # Create a Documentation template with the above two field names, and for each Organization, add values to the two fields from the 47 | # Blackpoint Cyber portal before running this script. 48 | ########### 49 | # EDIT EDIT 50 | ########### 51 | 52 | ########### 53 | # CUSTOM FIELD CHECKS 54 | ########### 55 | 56 | #Customer UID found in URL From Blackpoint Portal 57 | #Example CustomerUID format: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" 58 | $CustomerUID = Ninja-Property-Docs-Get-Single $CustomNinjaDocumentation $CustomerUIDCustomField 59 | write-host "CustomerUID from $CustomNinjaDocumentation Documentation Field: $CustomerUID" 60 | if ($CustomerUID -Match "^[a-zA-Z00-9]{8}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{12}$") { 61 | write-host "Customer UID passed basic format validation, continuing to install using this value." 62 | } else { 63 | write-host "No CustomerUID field defined or invalid format, set valid blackpointCyberSnapCustomerUid in documentation" 64 | write-host "Format should be: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (36 characters long)" 65 | exit 1 66 | } 67 | 68 | #Snap Installer name 69 | #Example CompanyEXE format: "COMPANYNAME_installer.exe" 70 | $CompanyEXE = Ninja-Property-Docs-Get-Single $CustomNinjaDocumentation $CustomerEXECustomField 71 | write-host "CompanyEXE from $CustomNinjaDocumentation Documentation field: $CompanyEXE" 72 | if ($CompanyEXE -Like '*_installer.exe') { 73 | write-host "Filename passed basic format validation, continuing to install using this value." 74 | } else { 75 | write-host "No CompanyEXE field defined ending in _installer.exe, set valid blackpointCyberSnapExecutableName in documentation" 76 | exit 2 77 | } 78 | 79 | ############################## 80 | # DO NOT EDIT PAST THIS POINT 81 | ############################## 82 | 83 | #Installer Name 84 | $InstallerName = "snap_installer.exe" 85 | 86 | #InstallsLocation 87 | $InstallerPath = Join-Path $env:TEMP $InstallerName 88 | 89 | #Snap URL 90 | $DownloadURL = "https://portal.blackpointcyber.com/installer/$CustomerUID/$CompanyEXE" 91 | 92 | #Service Name 93 | $SnapServiceName = "Snap" 94 | 95 | #Enable Debug with 1 96 | $DebugMode = 1 97 | 98 | #Failure message 99 | $Failure = "Snap was not installed Successfully. Contact support@blackpointcyber.com if you need more help." 100 | 101 | function Get-TimeStamp { 102 | return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) 103 | } 104 | 105 | 106 | #Checking if the Service is Running 107 | function Snap-Check($service) 108 | { 109 | if (Get-Service $service -ErrorAction SilentlyContinue) 110 | { 111 | return $true 112 | } 113 | return $false 114 | } 115 | 116 | #Debug 117 | function Debug-Print ($message) 118 | { 119 | if ($DebugMode -eq 1) 120 | { 121 | Write-Host "$(Get-TimeStamp) [DEBUG] $message" 122 | } 123 | } 124 | 125 | #Checking .NET Ver 4.6.1 126 | function Net-Check { 127 | #Left in to help with troubleshooting 128 | #$$cimreturn = (Get-CimInstance Win32_Operatingsystem | Select-Object -expand Caption -ErrorAction SilentlyContinue) 129 | #$windowsfull = $cimreturn 130 | #$WindowsSmall = $windowsfull.Split(" ") 131 | #[string]$WindowsSmall[0..($WindowsSmall.Count-2)] 132 | #If ($WindowsSmall -eq $Windows10) { 133 | 134 | Debug-Print("Checking for .NET 4.6.1+...") 135 | #Calls Net Ver 136 | If (! (Get-ItemProperty "HKLM:SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full").Release -gt 394254){ 137 | 138 | 139 | $NetError = "SNAP needs 4.6.1+ of .NET...EXITING" 140 | Write-Host "$(Get-TimeStamp) $NetError" 141 | exit 0 142 | } 143 | 144 | { 145 | Debug-Print ("4.6.1+ Installed...") 146 | } 147 | 148 | } 149 | 150 | 151 | 152 | #Downloads file 153 | function Download-Installer { 154 | Debug-Print("Downloading from provided $DownloadURL...") 155 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 156 | $Client = New-Object System.Net.Webclient 157 | try 158 | { 159 | $Client.DownloadFile($DownloadURL, $InstallerPath) 160 | } 161 | catch 162 | { 163 | $ErrorMsg = $_.Exception.Message 164 | Write-Host "$(Get-TimeStamp) $ErrorMsg" 165 | } 166 | If ( ! (Test-Path $InstallerPath) ) { 167 | $DownloadError = "Failed to download the SNAP Installation file from $DownloadURL" 168 | Write-Host "$(Get-TimeStamp) $DownloadError" 169 | throw $Failure 170 | } 171 | Debug-Print ("Installer Downloaded to $InstallerPath...") 172 | 173 | 174 | } 175 | 176 | #Installation 177 | function Install-Snap { 178 | Debug-Print ("Verifying AV did not steal exe...") 179 | If (! (Test-Path $InstallerPath)) { 180 | { 181 | $AVError = "Something, or someone, deleted the file." 182 | Write-Host "$(Get-TimeStamp) $AVError" 183 | throw $Failure 184 | } 185 | } 186 | Debug-Print ("Unpacking and Installing agent...") 187 | Start-Process -NoNewWindow -FilePath $InstallerPath -ArgumentList "-y" 188 | 189 | } 190 | 191 | 192 | function runMe { 193 | Debug-Print("Starting...") 194 | Debug-Print("Checking if SNAP is already installed...") 195 | If ( !$Force -and (Snap-Check($SnapServiceName)) ) 196 | { 197 | $ServiceError = "SNAP is Already Installed...Bye." 198 | Write-Host "$(Get-TimeStamp) $ServiceError" 199 | exit 0 200 | } 201 | Net-Check 202 | Download-Installer 203 | Install-Snap 204 | # Error-Test 205 | Write-Host "$(Get-TimeStamp) Snap Installed..." 206 | } 207 | 208 | try 209 | { 210 | runMe 211 | } 212 | catch 213 | { 214 | $ErrorMsg = $_.Exception.Message 215 | Write-Host "$(Get-TimeStamp) $ErrorMsg" 216 | exit 1 217 | } 218 | -------------------------------------------------------------------------------- /Blackpoint-Cyber-SNAP/Deploy-Blackpoint-Cyber-SNAP-macOS.sh: -------------------------------------------------------------------------------- 1 | #!bin/sh 2 | # Deployment script for Blackpoint Cyber SNAP agent for macOS for NinjaRMM 3 | 4 | # v0.1 - 2023-09-05 - Logic by David Szpunar, based on and calls Blackpoint Cyber-provided script for installation. 5 | 6 | #SOURCE instructions: https://support.blackpointcyber.com/article/107-mac-agent-ninjaone-installation 7 | 8 | # Locate the Auth Token from the Blackpoint Cyber Portal->Customer->Install & Deploy with the Download macOS Setup Script button. 9 | # Save the .sh file, open it, and find the line starting with "export AUTH_TOKEN=" and copy the auth token from inside the quotes 10 | # and place it below as the value for AUTH_TOKEN. The blackpointCyberSnapCustomerUid Custom Documentation field under the 11 | # Deployments template Deployments instance should be script-readable and set to the CUSTOMER_ID value from the same script, 12 | # which is the same one required for Windows deployment. Otherwise this script just verifies those values are long enough 13 | # and then downloads and runs the deployment script provided by Blackpoint Cyber on the final line of the script you 14 | # downloaded in the above instructions. If snap-agent executable is located on system, script quits without installing. 15 | 16 | # Make sure Custom Documentation Field blackpointCyberSnapCustomerUid exists, or make your own and change it, or switch 17 | # to using Global or Role Custom fields with slight change to ninjarmm-cli call, left as an exercise for the reader. 18 | 19 | ######################### 20 | # EDIT AUTH_TOKEN BELOW # 21 | ######################### 22 | 23 | # Hardcode Auth Token as it's global for all tenants under a single MSP, but optionally pull from Documentation Custom Field if desired: 24 | export AUTH_TOKEN="PLACE_AUTH_TOKEN_HERE" 25 | #export AUTH_TOKEN=$(/Applications/NinjaRMMAgent/programdata/ninjarmm-cli get 'Deployments' 'Deployments' blackpointCyberSnapAuthToken) 26 | 27 | # Uncomment for debug output: 28 | #echo "Blackpoint Cyber AUTH_TOKEN: $AUTH_TOKEN" 29 | 30 | export CUSTOMER_ID=$(/Applications/NinjaRMMAgent/programdata/ninjarmm-cli get 'Deployments' 'Deployments' blackpointCyberSnapCustomerUid) 31 | # Uncomment for debug output: 32 | #echo "Blackpoint Cyber CUSTOMER_ID: $CUSTOMER_ID" 33 | 34 | export DOWNLOAD_BASE_URL="https://agent-sega-production-snap.bpcyber.net" 35 | 36 | if [ ${#AUTH_TOKEN} != 281 ] 37 | then 38 | echo "Blackpoint Cyber Auth Token value not found or not the right length, quitting." 39 | exit 40 | else 41 | echo "Auth Token located, continuing." 42 | fi 43 | 44 | if [ ${#CUSTOMER_ID} != 36 ] 45 | then 46 | echo "Blackpoint Cyber Customer ID value not found or too short, quitting." 47 | exit 48 | else 49 | echo "Customer ID located, continuing." 50 | fi 51 | 52 | if [ -f /usr/local/bin/snap-agent ] 53 | then 54 | echo "snap-agent is already installed, quitting." 55 | exit 56 | else 57 | echo "snap-agent NOT found, continuing to install." 58 | fi 59 | 60 | echo "" 61 | echo "Running installation script..." 62 | curl -s https://bpc-deploy-scripts.s3.amazonaws.com/macos/download-install.sh | sh 63 | echo "Installation script run complete." 64 | -------------------------------------------------------------------------------- /Browser-Update-Deadlines/Browser-Update-Deadline-ScriptVariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/Browser-Update-Deadlines/Browser-Update-Deadline-ScriptVariables.png -------------------------------------------------------------------------------- /Browser-and-Office-Autoupdate/Browser-Office-Autoupdate.ps1: -------------------------------------------------------------------------------- 1 | <# Browser-Office-Autoupdate.ps1 2 | NOTE: Only covers Local Machine policy, not per-user registry keys that may override these settings on a per-user basis. 3 | 4 | Version 0.0.1 - 2024-01-15 - Initial version by David Szpunar 5 | Version 0.0.2 - 2024-01-16 - Added Office Click-To-Run Autoupdate Blocker Removal at request of @teamitpd 6 | Version 0.0.3 - 2024-01-16 - Adjusted Office Click-To-Run to check for UpdatesEnabled to equal "False" and update to True (rather than deleting if exists) 7 | Version 0.0.4 - 2024-02-10 - Adjusted Firefox check to accept forced update settings (but report) so as to not fail when autoupdate is configured. 8 | Version 0.0.5 - 2024-02-20 - Added some Write-Verbose statements to show changes NOT being made, if called with -Verbose parameter. 9 | Version 0.0.6 - 2024-10-24 - Resolved bug in Chrome update with typo in registry key name reported by GitHub user @lyttek 10 | 11 | LICENSE: Provided without warranty or guarantee of fitness for any purposes. Code freely available for use or reuse. 12 | 13 | USAGE: 14 | With no arguments, this script will report on whether any browser updates are disabled at the system level. 15 | 16 | With the -AllowAllUpdates switch, it will remove any browser autoupdate blocker registry keys for Chrome, Edge, and Firefox. 17 | 18 | With the -AllowChromeUpdate switch, it will enable the Chrome autoupdate policy registry keys only, if they are disabled. 19 | With the -AllowEdgeUpdate switch, it will enable the Edge autoupdate policy registry key, if they are disabled. 20 | With the -AllowFirefoxUpdate switch, it will enable the Firefox autoupdate policy registry key, if they are disabled. 21 | With the -AllowOfficeUpdate switch, it will remove the Click-To-Run Office Apps for Business/Enterprise update configuration key, if it exists. 22 | 23 | Script Variables with the argument names may also bet set as checkboxes for NinjaRMM in the GUI. 24 | 25 | Links provided in comments to specific documentation for most registry keys at https://admx.help/ for review. 26 | 27 | Initial Chrome SOURCE: https://discord.com/channels/676451788395642880/1072879047329202258/1156585935962640446 28 | Original basic Chrome script from Steven at NinjaOne: 29 | I've additionally added DisableAutoUpdateChecksCheckboxValue as one of the registry keys in this process - it also has an impact on automatic updates, with the dword being set to 1 it will never apply the updates automatically. 30 | 31 | Evaluation script (you can uncomment the #Set-ItemProperty lines to automatically flip the values of the keys without the secondary script being needed): 32 | Set Result Code to any, With Output to "Some Browser Updates Disabled", then hit apply. 33 | #> 34 | [CmdletBinding()] 35 | param( 36 | [switch] $AllowAllUpdates, 37 | [switch] $AllowChromeUpdate, 38 | [switch] $AllowEdgeUpdate, 39 | [switch] $AllowFirefoxUpdate, 40 | [switch] $AllowOfficeUpdate 41 | ) 42 | 43 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 44 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 45 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 46 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters 47 | foreach ($param in $switchParameters.keys) { 48 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue 49 | if ($var) { 50 | $envVarName = $var.Name.ToLower() 51 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 52 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { 53 | # Checkbox variables 54 | $PSBoundParameters[$envVarName] = $true 55 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 56 | } 57 | elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { 58 | # non-Checkbox string variables 59 | $PSBoundParameters[$envVarName] = $envVarValue 60 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 61 | } 62 | } 63 | } 64 | ### END PROCESS SCRIPT VARIABLES 65 | 66 | # Set Error Action to Silently Continue for the remainder of the script 67 | $ORIG_ErrorActionPreference = $ErrorActionPreference 68 | $ErrorActionPreference = "SilentlyContinue" 69 | 70 | # Initialize exit code variable, set to initial 0 for success (autoupdate is NOT disabled) 71 | $AreUpdatesDisabled = 0 72 | 73 | if ($AllowAllUpdates) { 74 | Write-Host "Will adjust any automatic updates to enabled (if disabled)." 75 | } 76 | if ($AllowChromeUpdate) { 77 | Write-Host "Will adjust Chrome updates to enabled (if disabled)." 78 | } 79 | if ($AllowEdgeUpdate) { 80 | Write-Host "Will adjust Edge updates to enabled (if disabled)." 81 | } 82 | if ($AllowFirefoxUpdate) { 83 | Write-Host "Will adjust Firefox updates to enabled (if disabled)." 84 | } 85 | if ($AllowOfficeUpdate) { 86 | Write-Host "Will adjust Microsoft Office CTR updates to enabled (if disabled)." 87 | } 88 | if (!$AllowAllUpdates -and !$AllowChromeUpdate -and !$AllowEdgeUpdate -and !$AllowFirefoxUpdate -and !$AllowOfficeUpdate) { 89 | Write-Host "Report-only mode, will not adjust any registry values." 90 | } 91 | 92 | <# 93 | CHROME: https://admx.help/?Category=ChromeEnterprise 94 | #> 95 | Write-Verbose "Checking registry path HKLM:\SOFTWARE\Policies\Google\Update for disabled updates..." 96 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Google\Update" -Name "Update{8A69D345-D564-463C-AFF1-A69D9E530F96}") -eq '0') { 97 | Write-Host "Chrome Updates Disabled" 98 | $AreUpdatesDisabled++ 99 | if ($AllowAllUpdates -or $AllowChromeUpdate) { 100 | Write-Host "Setting Chrome Update registry key Update{8A69D345-D564-463C-AFF1-A69D9E530F96} to enabled." 101 | Set-ItemProperty "HKLM:\SOFTWARE\Policies\Google\Update" "Update{8A69D345-D564-463C-AFF1-A69D9E530F96}" 1 102 | $AreUpdatesDisabled-- 103 | } 104 | } 105 | else { 106 | Write-Verbose "Chrome updates are not disabled, not changing Update{8A69D345-D564-463C-AFF1-A69D9E530F96} key." 107 | } 108 | 109 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Google\Update" -Name "Update{4DC8B4CA-1BDA-483E-B5FA-D3C12E15B62D}") -eq '0') { 110 | Write-Host "Chrome Updates Disabled" 111 | $AreUpdatesDisabled++ 112 | if ($AllowAllUpdates -or $AllowChromeUpdate) { 113 | Write-Host "Setting Chrome Update registry key Update{4DC8B4CA-1BDA-483E-B5FA-D3C12E15B62D} to enabled." 114 | Set-ItemProperty "HKLM:\SOFTWARE\Policies\Google\Update" "Update{4DC8B4CA-1BDA-483E-B5FA-D3C12E15B62D}" 1 115 | $AreUpdatesDisabled-- 116 | } 117 | } 118 | else { 119 | Write-Verbose "Chrome updates are not disabled, not changing Update{4DC8B4CA-1BDA-483E-B5FA-D3C12E15B62D} key." 120 | } 121 | 122 | # Chrome General Autoupdate Allowed https://admx.help/?Category=GoogleUpdate&Policy=Google.Policies.Update::Pol_DefaultUpdatePolicy 123 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Google\Update" -Name "UpdateDefault") -eq '0') { 124 | Write-Host "Chrome Update Override Disabled" 125 | $AreUpdatesDisabled++ 126 | if ($AllowAllUpdates -or $AllowChromeUpdate) { 127 | Write-Host "Setting Chrome Update Override registry key UpdateDefault to always allow." 128 | Set-ItemProperty "HKLM:\SOFTWARE\Policies\Google\Update" "UpdateDefault" 1 129 | $AreUpdatesDisabled-- 130 | } 131 | } 132 | else { 133 | Write-Verbose "Chrome update override is not disabled, not changing UpdateDefault key." 134 | } 135 | 136 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Google\Update" -Name "DisableAutoUpdateChecksCheckboxValue") -eq '1') { 137 | Write-Host "Chrome Updates Disabled by Checkbox" 138 | $AreUpdatesDisabled++ 139 | if ($AllowAllUpdates -or $AllowChromeUpdate) { 140 | Write-Host "Removing Chrome Update registry key DisableAutoUpdateChecksCheckboxValue to allow for automatic updates." 141 | Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Google\Update" -Name "DisableAutoUpdateChecksCheckboxValue" 142 | $AreUpdatesDisabled-- 143 | } 144 | } 145 | else { 146 | Write-Verbose "Chrome updates are not disabled by checkbox, not removing DisableAutoUpdateChecksCheckboxValue key." 147 | } 148 | 149 | <# 150 | EDGE: https://learn.microsoft.com/en-us/deployedge/microsoft-edge-update-policies 151 | #> 152 | Write-Verbose "Checking registry path HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate for disabled updates..." 153 | # Edge Updates Default https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Update::Pol_DefaultUpdatePolicy 154 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" -Name "UpdateDefault") -eq '0') { 155 | Write-Host "Edge Default Updates Disabled" 156 | $AreUpdatesDisabled++ 157 | if ($AllowAllUpdates -or $AllowEdgeUpdate) { 158 | Write-Host "Setting Edge Default Update registry key UpdateDefault to enabled." 159 | Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" "UpdateDefault" 1 160 | $AreUpdatesDisabled-- 161 | } 162 | } 163 | else { 164 | Write-Verbose "Edge updates are not disabled, not changing UpdateDefault key." 165 | } 166 | 167 | # Edge Stable Updates https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Update::Pol_UpdatePolicyMicrosoftEdge 168 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" -Name "Update{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}") -eq '0') { 169 | Write-Host "Edge Stable Updates Disabled" 170 | $AreUpdatesDisabled++ 171 | if ($AllowAllUpdates -or $AllowEdgeUpdate) { 172 | Write-Host "Setting Edge Update registry key Update{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062} to enabled." 173 | Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" "Update{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}" 1 174 | $AreUpdatesDisabled-- 175 | } 176 | } 177 | else { 178 | Write-Verbose "Edge Stable updates are not disabled, not changing Update{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062} key." 179 | } 180 | 181 | # Edge WebView Updates https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Update::Pol_UpdatePolicyMicrosoftEdgeWebView 182 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" -Name "Update{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}") -eq '0') { 183 | Write-Host "Edge WebView Updates Disabled" 184 | $AreUpdatesDisabled++ 185 | if ($AllowAllUpdates -or $AllowEdgeUpdate) { 186 | Write-Host "Setting Edge WebView Update registry key Update{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} to enabled." 187 | Set-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" "Update{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" 1 188 | $AreUpdatesDisabled-- 189 | } 190 | } 191 | else { 192 | Write-Verbose "Edge WebViewupdate override is not disabled, not changing Update{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5} key." 193 | } 194 | 195 | # Remove Edge Target Version Override: https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Update::Pol_TargetVersionPrefixMicrosoftEdge 196 | if (![string]::IsNullOrEmpty((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" -Name "TargetVersionPrefix{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}"))) { 197 | Write-Host "Edge Targeted Release Version Configured" 198 | $AreUpdatesDisabled++ 199 | if ($AllowAllUpdates -or $AllowEdgeUpdate) { 200 | Write-Host "Removing Edge Targeted Version Override registry key TargetVersionPrefix{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}." 201 | Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" -Name "TargetVersionPrefix{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062}" 202 | $AreUpdatesDisabled-- 203 | } 204 | } 205 | else { 206 | Write-Verbose "Edge Targetd Version Override is not disabled, not removing TargetVersionPrefix{56EB18F8-B008-4CBD-B6D2-8C97FE7E9062} key." 207 | } 208 | 209 | <# 210 | Firefox: https://admx.help/?Category=Firefox&Policy=Mozilla.Policies.Firefox::DisableAppUpdate 211 | #> 212 | # Firefox Updates Block https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Update::Pol_DefaultUpdatePolicy 213 | Write-Verbose "Checking Firefox Autoupdate Blocker..." 214 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" -Name "DisableAppUpdate") -eq '1') { 215 | Write-Host "Firefox Updates Disabled" 216 | $AreUpdatesDisabled++ 217 | if ($AllowAllUpdates -or $AllowFirefoxUpdate) { 218 | Write-Host "Setting Edge Default Update registry key UpdateDefault to enabled." 219 | Set-ItemProperty "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" "DisableAppUpdate" 0 220 | $AreUpdatesDisabled-- 221 | } 222 | } 223 | else { 224 | Write-Verbose "Firefox updates are not disabled, not changing DisableAppUpdate key." 225 | } 226 | 227 | # Firefox Autoupdate Block: https://admx.help/?Category=Firefox&Policy=Mozilla.Policies.Firefox::AppAutoUpdate 228 | # if (![string]::IsNullOrEmpty((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" -Name "AppAutoUpdate"))) { 229 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" -Name "AppAutoUpdate") -eq '0') { 230 | Write-Host "Firefox Autoupdate is configured to download but not automatically install updates (user can choose when)." 231 | $AreUpdatesDisabled++ 232 | if ($AllowAllUpdates -or $AllowFirefoxUpdate) { 233 | Write-Host "Removing Firefox Auto Update configuration registry key AppAutoUpdate to allow user autoupdate config to take effect." 234 | Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" -Name "AppAutoUpdate" 235 | $AreUpdatesDisabled-- 236 | } 237 | } elseif ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" -Name "AppAutoUpdate") -eq '1') { 238 | Write-Host "Firefox Autoupdate is already configured to download and force-install updates (not changing)." 239 | } elseif (![string]::IsNullOrEmpty((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" -Name "AppAutoUpdate"))) { 240 | Write-Host "Firefox Autoupdate is configured but not to a known value." 241 | $AreUpdatesDisabled++ 242 | if ($AllowAllUpdates -or $AllowFirefoxUpdate) { 243 | Write-Host "Removing Firefox Auto Update configuration registry key AppAutoUpdate to allow user autoupdate config to take effect." 244 | Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Mozilla\Firefox" -Name "AppAutoUpdate" 245 | $AreUpdatesDisabled-- 246 | } 247 | } 248 | else { 249 | Write-Verbose "Firefox autoupdate is not disabled, not removing the AppAutoUpdate key." 250 | } 251 | 252 | <# 253 | Microsoft Office Click-To-Run 254 | Microsoft Office 2016 option, NOT supported here but could be: https://admx.help/?Category=Office2016&Policy=office16.Office.Microsoft.Policies.Windows::L_EnableAutomaticUpdates 255 | For Intune controls, review: https://learn.microsoft.com/en-us/mem/intune/configuration/administrative-templates-update-office 256 | #> 257 | Write-Verbose "Checking Microsoft Office Click-To-Run Autoupdate Blocker..." 258 | if ((Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name "UpdatesEnabled") -eq 'False') { 259 | Write-Host "Microsoft Office Click-To-Run Autoupdate Blocker is set to False" 260 | $AreUpdatesDisabled++ 261 | if ($AllowAllUpdates -or $AllowOfficeUpdate) { 262 | Write-Host "Updating Microsoft Office Click-To-Run Autoupdate ClickToRun\Configuration\UpdatesEnabled registry key to True from False" 263 | Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name "UpdatesEnabled" 264 | Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name "UpdatesEnabled" -Type String -Value 'True' 265 | $AreUpdatesDisabled-- 266 | } 267 | } 268 | else { 269 | Write-Verbose "Microsoft Office Click-To-Run update blocker is not configured, not changing UpdatesEnabled key." 270 | } 271 | 272 | # Restore original ErrorActionPreference 273 | $ErrorActionPreference = $ORIG_ErrorActionPreference 274 | 275 | # Exit with result, 0 only if there are no updates disabled (regardless of any updates/changes): 276 | if ($AreUpdatesDisabled -eq 0) { 277 | Write-Host "No browser updates were found to be disabled in checks AND/OR the ones found were removed." 278 | } 279 | else { 280 | Write-Host "SUMMARY: Some Browser Updates Disabled" 281 | } 282 | if ($AreUpdatesDisabled -le 0) { 283 | # Exit with 0 if all updates were disabled (including changes implemented) (success) 284 | exit 0 285 | } 286 | else { 287 | # Exit with the number of updates that were found to be disabled if any updates were disabled (failure) 288 | exit $AreUpdatesDisabled 289 | } 290 | 291 | -------------------------------------------------------------------------------- /ConnectSecure/README.md: -------------------------------------------------------------------------------- 1 | # ConnectSecure Deployment scripts 2 | These scripts allow for the deployment of [ConnectSecure](https://www.connectsecure.com) (formerly CyberCNS) agents for either the version 3 or version 4 platforms to Windows endpoints, and optionally allow for the removal/uninstallation of the other version if you choose (and it’s installed). 3 | 4 | Both [scripts](https://github.com/dszp/NinjaOne-Scripts/tree/main/ConnectSecure) use the public download endpoints provided by ConnectSecure for their own agent installers that should always be the most current version of each agent. You will need to customize the tenant and client fields, either in the script config statically or via things like parameters or NinjaRMM Custom Fields or Script Variables. Documentation for the separate v3 and v4 installer scripts is below: 5 | 6 | - [Docs for Version 4 Deployment Script](#platform-version-4) 7 | - [Docs for Version 3 Deployment Script](#platform-version-3) 8 | - [Warranty](#warranty) 9 | 10 | # Platform Version 4 11 | The [Deploy-ConnectSecure-V4.ps1](https://github.com/dszp/NinjaOne-Scripts/blob/main/ConnectSecure/Deploy-ConnectSecure-V4.ps1) script will attempt to deploy (or remove) the ConnectSecure Vulnerability Scan Agent to Windows systems. Basic usage documentation is below or at the top of the script: 12 | 13 | Running this script without arguments will attempt to install the agent using the EXE installer and the Company ID located in Custom Fields or Script Variables (if any), only if the agent service does not already exist on the system. 14 | 15 | **Review the CONFIG section of the script before deploying!** 16 | 17 | ## Parameters or Script Variables 18 | This script accepts the following arguments, which may also be set as Custom Fields or Script Variables in NinjaRMM: 19 | 20 | ### CompanyID 21 | This is the ConnectSecure Company ID. Can pass it or hardcode it under the CONFIG section, or use Ninja Script Variable or Custom Fields. It's a number 22 | 3 to 5 digits long. This variable is unique for each customer managed in the ConnectSecure environment. 23 | A Script Variable version will take precedence, followed by parameter, followed by Documentation Custom Field, followed by the standard Custom Field, 24 | if those are all blank. The default NinjaOne field name is "connectsecureCompanyId" unless you customize the field name in the CONFIG section. 25 | 26 | A Script Variable version will take precedence, followed by parameter, followed by Documentation Custom Field if those are both blank. 27 | 28 | ### TenantID 29 | This value is your ConnectSecure tenant ID, global for your entire instance. Can pass it or hardcode it under the CONFIG section. Should be 18 digits. 30 | 31 | ### UserSecret 32 | This is the ConnectSecure User Secret. Can pass it or hardcode it under the CONFIG section. You can also provide this with the NinjaOne Custom Field 33 | or NinjaOne Custom Documentation Field if you configure the field name under the CONFIG section as the value of the #customUserSecret variable. 34 | A Script Variable version will take precedence, followed by parameter, followed by Documentation Custom Field, followed by the standard Custom Field, 35 | if those are all blank. 36 | 37 | The value is an alphanumeric string that ConnectSecure's Agent Download page provides as the value of the "-j" parameter inside the deployment 38 | script for Windows agents (select the whole string following the "-j" in the command that's surrounded by spaces, NOT including the "-j" itself or the spaces). 39 | See [example screenshot in the documentation](https://cybercns.atlassian.net/wiki/spaces/CVB/pages/2111242438/How+To+Install+V4+Agent+Using+RMM+Script#Obtain-Company-ID%2C-Tenant-ID%2C-and-User-Secret-Information). 40 | 41 | The User Secret ties the installation to the user who generated the installer on the ConnectSecure back-end system, but it may be reused for all 42 | installations without restriction just like the TenantID, only the CompanyID will be different for each company being scanned/managed. 43 | The default NinjaOne field name is "connectsecureUserSecret" unless you customize the field name in the CONFIG section. 44 | 45 | ### Once 46 | Use this switch to run the vulnerability scan once, without installing the agent permanently on the system. Can also be a Script Variables Checkbox. 47 | **NOTE: This option is not yet implemented or available in v4 yet.** 48 | 49 | ### Force 50 | Adding this switch or Script Variables Checkbox will either force installation (even if the service already exists) on the endpoint, or alternately, if the `-Uninstall` flag is called, will attempt to run the internal removal batch script regardless of whether the uninstall.bat file provided in the installation folder for the agent exists on the system or not (so this flag works with installation or removal). 51 | 52 | ### UninstallPrevious 53 | Use this switch or Script Variables Checkbox to attempt to remove the V3 agent if it's currently installed on the system. If the V3 service exists, this will be run automatically before installing the V4 agent. 54 | 55 | ### Uninstall 56 | Use this switch or Script Variables Checkbox to attempt to locate the `uninstall.bat` file inside the agent installation folder and run it if it exists, to remove the installed agent. If the batch file does not exist, nothing is done unless the `-Force` switch is also provided, in which case the contents of the batch script on an existing system has been embedded in this script (as of 2023-11-10) and will be executed anyway to attempt to remove the agent anyway via the internal uninstall method. 57 | 58 | Output from each command is provided for feedback. Every parameter or switch can be set via Script Variables, and the first one also supports a NinjaOne Custom 59 | Field, or NinjaOne Custom Documentation Field, that will only be used if another value is not provided. 60 | 61 | ## Version History 62 | - Version 0.0.1 - 2023-10-17 - Initial Version by David Szpunar 63 | - Version 0.1.0 - 2023-11-10 - Updated to include removal/uninstallation options and documentation. 64 | - Version 0.1.1 - 2023-11-10 - Fixed spelling error in documentation. 65 | - Version 0.1.2 - 2023-11-13 - Switched to processing Switch Variable Checkboxes automatically from Named Parameters using new handling method 66 | - Version 0.1.3 - 2023-11-13 - Fix logic bug in new Script Variables handling method 67 | - Version 0.2.0 - 2023-12-07 - Update to support ConnectSecure v4 beta and removing the v3 agent if it exists 68 | - Version 0.2.1 - 2024-03-28 - Add a different supported TLS version check before download to attempt and fix 69 | - Version 0.2.2 - 2024-10-25 - Add support for new -j, user secret, parameter described here - https://cybercns.atlassian.net/wiki/spaces/CVB/pages/2111242438/How+To+Install+V4+Agent+Using+RMM+Script 70 | - Version 0.2.3 - 2024-10-25 - Add additional error checking for User Secret, add custom field configuration for it, and add hardcoded override for User Secret as an option 71 | - Version 0.3.0 - 2024-10-25 - Update documentation at top of script to cover User Secret and provide additional clarifications/details generally. 72 | - Version 0.3.1 - 2024-10-28 - Update documentation to clarify how to select the User Secret from the ConnectSecure Agent Download page. 73 | 74 | # Platform Version 3 75 | The [Deploy-ConnectSecure-V3.ps1](https://github.com/dszp/NinjaOne-Scripts/blob/main/ConnectSecure/Deploy-ConnectSecure-V3.ps1) script will attempt to deploy (or remove) the ConnectSecure Vulnerability Scan Agent to Windows systems. Basic usage documentation is below or at the top of the script. 76 | 77 | Running this script without arguments will attempt to install the agent using the EXE installer and the Company ID, Client ID, and Client Secret located in 78 | Custom Fields or Script Variables (if any), only if the agent service does not already exist on the system. 79 | 80 | **NOTE as of 2024-10-25: Version 3 of ConnectSecure/CyberCNS is deprecated and will be shut down at the end of 2024. Please use version 4 instead. This script is available 81 | for reference or temporary use only prior to migration. The Version 4 deployment script has an option to remove the version 3 agent if it is installed. 82 | The connectsecureCompanyID custom field name (documentation or regular) for NinjaOne is reused by the Version 4 script but has a DIFFERENT value in each 83 | system. Please keep this in mind; you may wish to use a Script Variable or parameter or other method with this v3 script to avoid conflicts with v4 84 | configuration prior to retirement of v3, or while you're moving to deploy v4. You can also rename the custom field in the $customCompanyID variable in 85 | either script to a non-conflicting name.** 86 | 87 | **Review the CONFIG section of the script before deploying!** 88 | 89 | This script accepts the following arguments, which may also be set as Custom Fields or Script Variables: 90 | 91 | ## Parameters or Script Variables 92 | ### CompanyID 93 | ### ClientID 94 | ### ClientSecret 95 | These three values are provided by ConnectSecure CyberCNS in their deployment instructions for each client. 96 | In the CONFIG section below, you can configure the NinjaRMM Custom Field names in `$customCompanyID`, `$customClientID`, and `$customClientSecret` 97 | to pull the values from custom fields (we use org fields, text for the first two and secure for the client secret), making sure permissions for 98 | script read are configured! The format of these values is validated via regex and the script will quit if they are not valid. 99 | 100 | ### CompanyName 101 | This value is your CyberCNS instance name, global for your entire instance. Can pass it or hardcode it under the CONFIG section. 102 | 103 | ### EXE 104 | ### MSI 105 | Use either of these switches to specify the type of installation. The default is EXE if you provide none. Script Variables Checkboxes (one or both) can be created in the NinjaRMM to control these as well, if preferred. 106 | 107 | ### Once 108 | Use this switch to run the vulnerability scan once, without installing the agent permanently on the system. Can also be a Script Variables Checkbox. 109 | 110 | 111 | ### Force 112 | Adding this switch or Script Variables Checkbox will either force installation (even if the service already exists) on the endpoint, or alternately, if the `-Uninstall` flag is called, will attempt to run the internal removal batch script regardless of whether the `uninstall.bat` file provided in the installation folder for the agent exists on the system or not (so this flag works with installation or removal). 113 | 114 | ### UninstallNew 115 | Use this switch or Script Variables Checkbox to attempt to remove the V4 agent if it's currently installed on the system. If the V4 service exists, 116 | this will be run automatically before installing the V3 agent. 117 | 118 | ### Uninstall 119 | Use this switch or Script Variables Checkbox to attempt to locate the `uninstall.bat` file inside the agent installation folder and run it if it exists, 120 | to remove the installed agent. If the batch file does not exist, nothing is done unless the `-Force` switch is also provided, in which case the contents 121 | of the batch script on an existing system has been embedded in this script (as of 2023-11-10) and will be executed anyway to attempt to remove the 122 | agent anyway via the internal uninstall method. 123 | 124 | Output from each command is provided for feedback. Every parameter or switch can be set via Script Variables, and the first three also support Custom Fields. With minor adjustment, they could also use NinjaRMM Custom Documentation Fields. 125 | 126 | ## Version History 127 | - Version 0.0.1 - 2023-10-17 - Initial Version by David Szpunar 128 | - Version 0.1.0 - 2023-11-10 - Updated to include removal/uninstallation options and documentation. 129 | - Version 0.1.1 - 2023-11-10 - Fixed spelling error in documentation. 130 | - Version 0.1.2 - 2023-11-13 - Switched to processing Switch Variable Checkboxes automatically from Named Parameters using new handling method 131 | - Version 0.1.3 - 2023-11-13 - Fix logic bug in new Script Variables handling method 132 | - Version 0.1.4 - 2023-12-07 - Update to support removing the v4 agent if it exists 133 | - Version 0.1.5 - 2024-03-28 - Add a different supported TLS version check before download to attempt and fix 134 | 135 | # Warranty 136 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 137 | 138 | -------------------------------------------------------------------------------- /DefensX/Deploy-DefensX.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Install, Upgrade, or Uninstall DefenseX provided a valid DefensX KEY from the PowerShell Deployment for a given tenant. 4 | 5 | .DESCRIPTION 6 | Install, Upgrade, or Uninstall DefenseX provided a valid DefensX KEY from the PowerShell Deployment for a given tenant with KEY provided via parameter, environment variables, or NinjaRMM Custom Field. 7 | 8 | With no parameters, installation will be attempted, but only if the DefenseX service does not exist. Version will be ignored without the -Upgrade parameter. 9 | 10 | This script uses NinjaRMM Custom Fields to get the KEY by default (see CONFIG), but they can be passed in as parameters or Script Variables (environment variables) if desired. 11 | 12 | The Ninja Custom Field should be a single string with either a 16-character (alphanumeric) Short KEY or 224-character Long KEY (alphanumeric plus limited symbols such as -, _, and period), pulled out of the DefensX Deployments RMM field for a given client. The short key is recommended, configurable from the RMM popup. 13 | 14 | As long as you pass in a -KEY parameter, or provide a KEY environment variable, this script will function entirely separate from NinjaRMM. 15 | 16 | The additional parameters in the param() array that are not individually documented are all switches that are true if they exist and false if they are not provided. They can be checkboxes in NinjaRMM Script Variables (environment variables) or passed in on the command line, and will be converted to a 1 or 0 and passed to the appropriate argument to the MSI installer, as defined in the DefensX documentation and user interface. The argument names are simplified for ease of typing, and don't correspond precisely to the MSI arguments, but the mapping should be relatively simple to understand. 17 | 18 | .PARAMETER KEY 19 | If this parameter is passed to the DefensX PowerShell Deployment Script, it will be used instead of NinjaRMM Custom Fields. This key is located in the the DefensX Customer Console under Policies->Policy Groups->Deployments->RMM button, then turn on "Use Short Deployment Key" and then get the 16-character key at the very end of the command after the equals sign. The parameter should also accept the default 224-character key, but the 16-character short key is recommended. Required for install or upgrade unless supplied via $env:KEY (like via Ninja Script Variables) or NinjaRMM Custom Field. Not required for uninstall or Info check. 20 | 21 | .PARAMETER Upgrade 22 | Install Reinstall/Upgrade DefensX if it's already installed and at an older version than the current version available online, or if it's not installed at all (will also install from scratch, just won't quit if it's already installed and will check if it's outdated and upgrade if it is). 23 | 24 | If the most current version is already installed, the agent will not be reinstalled unless you add the -Force parameter. 25 | 26 | .PARAMETER Force 27 | Add to the -Upgrade parameter to attempt to reinstall even if the same version is already installed. 28 | 29 | .PARAMETER Uninstall 30 | If DefensX is already installed, uninstall it, using the uninstall GUID from the Windows Registry. Other parameters will be ignored. 31 | 32 | .PARAMETER Info 33 | Confirm installation status and print version info, then exit. Also queries the version number of the cloud installer file and reports the current installer version. Other parameters will be ignored. 34 | 35 | .PARAMETER SpecificVersion 36 | If you provide a specifica installer version that the DefensX cloud has available to download, the installation will use this version of the MSI file to install the agent. This is an untested feature without much error checking or reporting, but uses the installer version URL provided directly in DefensX documentation. 37 | 38 | .EXAMPLE 39 | Deploy-DefensX.ps1 40 | .EXAMPLE 41 | Deploy-DefensX.ps1 -KEY 'yourKEY' 42 | .EXAMPLE 43 | Deploy-DefensX.ps1 -KEY 'yourKEY' -Upgrade 44 | .EXAMPLE 45 | Deploy-DefensX.ps1 -KEY 'yourKEY' -Upgrade -Force 46 | .EXAMPLE 47 | Deploy-DefensX.ps1 -Info 48 | .EXAMPLE 49 | Deploy-DefensX.ps1 -KEY 'yourKEY' -SpecificVersion 1.9.70 50 | .EXAMPLE 51 | Deploy-DefensX.ps1 -Uninstall 52 | .EXAMPLE 53 | Deploy-DefensX.ps1 -Upgrade 54 | 55 | .LINK 56 | https://github.com/dszp/NinjaOne-Scripts/tree/main/DefensX 57 | 58 | .NOTES 59 | Version 0.1.1 - 2024-05-29 - by David Szpunar - Added Chromium, Brave, and Vivaldi private browser configuration parameters in addition to exisitng Chrome, Edge, and Firefox 60 | Version 0.1.0 - 2024-03-21 - by David Szpunar - Initial released version 61 | Version 0.0.3 - 2024-03-21 - by David Szpunar - Updated comments and formatting to better describe where to obtain the KEY, refactor some logic (internal) 62 | Version 0.0.2 - 2024-03-21 - by David Szpunar - Updated comment docs and made slight code adjustments (internal) 63 | Version 0.0.1 - 2024-03-20 - by David Szpunar - Initial version by David Szpunar (internal) 64 | #> 65 | [CmdletBinding()] 66 | param( 67 | [string] $KEY, 68 | [switch] $Upgrade, 69 | [switch] $Force, 70 | [switch] $Uninstall, 71 | [switch] $Info, 72 | [switch] $EnablePrivateChrome, 73 | [switch] $EnablePrivateEdge, 74 | [switch] $EnablePrivateFirefox, 75 | [switch] $EnablePrivateChromium, 76 | [switch] $EnablePrivateBrave, 77 | [switch] $EnablePrivateVivaldi, 78 | [switch] $DisableLogonUser, 79 | [switch] $EnableIamUser, 80 | [switch] $HideFromAddRemove, 81 | [switch] $DisableUninstall, 82 | [version] $SpecificVersion 83 | ) 84 | 85 | ##### CONFIG ##### 86 | # The page name (such as Deployments) for the custom Ninja Documentation page with the KEY field in it. 87 | # Leave commented or set blank if you're using Device/Role Custom Fields and NOT Documentation Custom Fields, or if you aren't using NinjaRMM custom fields at all. 88 | # $NinjaCustomDocumentationPage = 'Deployments' 89 | 90 | # The custom field name of the Ninja Custom field that contains the KEY from the DefensX Customer Console under Poilicies->Policy Group->sDeployments->RMM button, then 91 | # turn on "Use Short Deployment Key" and then get the 16-character key at the very end of the command after the equals sign, then place it in this Custom Field for each 92 | # customer/location/device. 93 | # This can be a Device or Role Custom Field, or a Documenation Custom Field (if using the Documentation Template above), as long as Automation Read access is enabled. 94 | # Leave BLANK/empty string (or comment it out) if you're passing in the -KEY parameter to the script directly rather than using NinjaRMM custom fields. 95 | $NinjaCustomFieldName = 'defensxKey' 96 | ##### END CONFIG ##### 97 | 98 | #Service Name and Application Name 99 | $ServiceName = "DefensX Agent" 100 | $Application = "DefensX Agent" 101 | 102 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 103 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 104 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 105 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters 106 | foreach ($param in $switchParameters.keys) { 107 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue 108 | if ($var) { 109 | $envVarName = $var.Name.ToLower() 110 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 111 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { 112 | # Checkbox variables 113 | $PSBoundParameters[$envVarName] = $true 114 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 115 | } 116 | elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { 117 | # non-Checkbox string variables 118 | $PSBoundParameters[$envVarName] = $envVarValue 119 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 120 | } 121 | } 122 | } 123 | ### END PROCESS SCRIPT VARIABLES 124 | 125 | ##### FUNCTIONS ##### 126 | function Get-DefensXCurrentVersion { 127 | # TODO 128 | <# 129 | .SYNOPSIS 130 | Given the result of WebResponseObject with the DefensX agent, determine the current version of DefensX from the file name. 131 | .DESCRIPTION 132 | Given the result of WebResponseObject with the DefensX agent, determine the current version of DefensX from the file name. 133 | .EXAMPLE 134 | $version = Get-DefensXCurrentVersion 135 | #> 136 | [CmdletBinding()] 137 | param( 138 | 139 | ) 140 | 141 | $uri = "https://cloud.defensx.com/defensx-installer/latest.msi" 142 | Write-Verbose "URI for latest version information: $uri" 143 | 144 | try { 145 | # Manually invoke a web request 146 | $Request = [System.Net.WebRequest]::Create($uri) 147 | $Request.AllowAutoRedirect = $false 148 | $Response = $Request.GetResponse() 149 | # Write-Verbose "Response Headers returned from version check:" 150 | # Write-Verbose ($Response.Headers | Format-List * | Out-String) 151 | # Write-Verbose "Location header value: $($Response.Headers['Location'])" 152 | } 153 | catch { 154 | Write-Error 'Error: Web request failed.' -ErrorAction Stop 155 | } 156 | Write-Verbose "RESPONSE CODE: $($Response.StatusCode.value__)" 157 | if ($Response.StatusCode.value__ -eq '307' -or $Response.StatusCode.value__ -eq '302') { 158 | $redirectUrl = $($Response.Headers['Location']) 159 | Write-Verbose "Redirect URL: $RedirectUrl" 160 | $FileName = [System.IO.Path]::GetFileName($redirectUrl) 161 | Write-Verbose "FileName: $FileName" 162 | if (-not $FileName) { Write-Error 'Error: Failed to resolve file name from URI.' -ErrorAction Stop } 163 | $FileName -match 'DefensXInstaller\-(\d+\.\d+\.\d+)\.msi' | Out-Null 164 | $version = $matches[1] 165 | Write-Verbose "Version: $version" 166 | return $version 167 | } 168 | return $null 169 | } 170 | 171 | function Show-DefensXCurrentInfo { 172 | $AgentInstall=("HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall","HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") | % { Get-ChildItem -Path $_ | % { Get-ItemProperty $_.PSPath } | ? { $_.DisplayName -match "$Application" } } 173 | $AgentVersion = $AgentInstall.DisplayVersion 174 | $AgentGUID = $AgentInstall.PSChildName 175 | 176 | if ($AgentInstall) { 177 | Write-Host "$Application is currently installed." 178 | Write-Host "Agent GUID: $AgentGUID" 179 | Write-Host "Installed version: $AgentVersion" 180 | } 181 | else { 182 | Write-Host "$Application is not installed." 183 | } 184 | } 185 | 186 | function Check-DefensXKey { 187 | param( 188 | [string] $KEY 189 | ) 190 | if ([string]::IsNullOrWhiteSpace($KEY)) { 191 | # Make sure this Documentation form, template, and field exist, are script readable, and have the corresponding client KEY saved! 192 | if ([string]::IsNullOrWhiteSpace($NinjaCustomDocumentationPage)) { 193 | if (![string]::IsNullOrWhiteSpace($NinjaCustomFieldName)) { 194 | Write-Host "Attempting to retreive Key from NinjaRMM Custom Field..." 195 | $KEY = Ninja-Property-Get $NinjaCustomFieldName 196 | } 197 | else { 198 | Write-Host "Ninja Custom Field Name not provided, not attempting to retrieve KEY from NinjaRMM, must use -Key parameter." 199 | } 200 | } 201 | else { 202 | Write-Host "Attempting to retreive KEY from NinjaRMM Documentation Custom Field..." 203 | $KEY = Ninja-Property-Docs-Get-Single $NinjaCustomDocumentationPage $NinjaCustomFieldName 204 | } 205 | } 206 | Write-Verbose "KEY Value: $KEY" 207 | 208 | #write-host "DefensX Key from Documentation Field: $KEY" 209 | # LEN 224 (alphanumeric plus _ and . and -) OR 16 (alphanumeric) 210 | $regex = "([a-zA-Z0-9]{16})|(\S{224})" 211 | $KEY -match $regex | Out-Null 212 | if ($Matches) { 213 | $KEY = $Matches[1] 214 | 215 | #write-host "Installing with KEY=" $KEY 216 | $KEYmasked = $KEY.SubString(0, 10) + "******" 217 | Write-Host "Installing with KEY:" $KEYmasked 218 | 219 | Write-Host "(identifiers partially masked for log)" 220 | } 221 | else { 222 | Write-Host "Nothing found in DefensX KEY field that matches a short or long deployment key, can't deploy for this tenant...Bye." 223 | exit 1 224 | } 225 | return $KEY 226 | } 227 | 228 | function Install-DefensX { 229 | param( 230 | [Parameter(Mandatory = $true, Position = 0)] 231 | [string] $Key 232 | ) 233 | 234 | # Preconfigure variables for installer arguments from parameters passed to script: 235 | if($EnablePrivateChrome) { 236 | $PRIV_CHROME = 0 237 | } else { 238 | $PRIV_CHROME = 1 239 | } 240 | 241 | if($EnablePrivateEdge) { 242 | $PRIV_EDGE = 0 243 | } else { 244 | $PRIV_EDGE = 1 245 | } 246 | 247 | if($EnablePrivateFirefox) { 248 | $PRIV_FIREFOX = 0 249 | } else { 250 | $PRIV_FIREFOX = 1 251 | } 252 | 253 | if($EnablePrivateChromium) { 254 | $PRIV_CHROMIUM = 0 255 | } else { 256 | $PRIV_CHROMIUM = 1 257 | } 258 | 259 | if($EnablePrivateBrave) { 260 | $PRIV_BRAVE = 0 261 | } else { 262 | $PRIV_BRAVE = 1 263 | } 264 | 265 | if($EnablePrivateVivaldi) { 266 | $PRIV_VIVALDI = 0 267 | } else { 268 | $PRIV_VIVALDI = 1 269 | } 270 | 271 | if($DisableLogonUser) { 272 | $ENABLE_LOGON_USER = 0 273 | } else { 274 | $ENABLE_LOGON_USER = 1 275 | } 276 | 277 | if($EnableIamUser) { 278 | $ENABLE_IAM_USER = 1 279 | } else { 280 | $ENABLE_IAM_USER = 0 281 | } 282 | 283 | if($HideFromAddRemove) { 284 | $SYSTEM_COMPONENT = 1 285 | } else { 286 | $SYSTEM_COMPONENT = 0 287 | } 288 | 289 | if($DisableUninstall) { 290 | $DISABLE_UNINSTALL = 1 291 | } else { 292 | $DISABLE_UNINSTALL = 0 293 | } 294 | 295 | # Download the latest installer, or a specific version instead if the -SpecificVersion parameter is used 296 | if($SpecificVersion) { 297 | Write-Verbose "Attempting to download and install specific version: $SpecificVersion" 298 | $url = "https://cloud.defensx.com/defensx-installer/latest.msi?v=$SpecificVersion" 299 | } else { 300 | Write-Verbose "Attempting to download and install latest version" 301 | $url = "https://cloud.defensx.com/defensx-installer/latest.msi" 302 | } 303 | # Ensure a secure TLS version is used. 304 | $ProtocolsSupported = [enum]::GetValues('Net.SecurityProtocolType') 305 | if ( ($ProtocolsSupported -contains 'Tls13') -and ($ProtocolsSupported -contains 'Tls12') ) { 306 | # Use only TLS 1.3 or 1.2 307 | [Net.ServicePointManager]::SecurityProtocol = ( 308 | [Enum]::ToObject([Net.SecurityProtocolType], 12288) -bOR [Enum]::ToObject([Net.SecurityProtocolType], 3072) 309 | ) 310 | } 311 | else { 312 | # Use only 1.2 313 | try { 314 | # In certain .NET 4.0 patch levels, SecurityProtocolType does not have a TLS 1.2 entry. 315 | # Rather than check for 'Tls12', we force-set TLS 1.2 and catch the error if it's truly unsupported. 316 | [Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([Net.SecurityProtocolType], 3072) 317 | } 318 | catch { 319 | $msg = $_.Exception.Message 320 | $err = "ERROR: Unable to use a secure version of TLS. Please verify Hotfix KB3140245 is installed." 321 | Write-Host "$err : $msg" 322 | Write-Error "$err : $msg" 323 | exit 1 324 | } 325 | } 326 | 327 | $ErrorActionPreference = "Stop" 328 | $InstallerFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".msi") 329 | (New-Object System.Net.WebClient).DownloadFile($url, $InstallerFile) 330 | $InstallerLogFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".log") 331 | $Arguments = " /c msiexec /i `"$InstallerFile`" /qn /norestart /l*v `"$InstallerLogFile`" KEY=$Key ENABLE_LOGON_USER=$ENABLE_LOGON_USER ENABLE_IAM_USER=$ENABLE_IAM_USER EDGE_DISABLE_PRIVATE_WINDOW=$PRIV_EDGE CHROME_DISABLE_PRIVATE_WINDOW=$PRIV_CHROME FIREFOX_DISABLE_PRIVATE_WINDOW=$PRIV_FIREFOX BRAVE_DISABLE_PRIVATE_WINDOW=$PRIV_BRAVE VIVALDI_DISABLE_PRIVATE_WINDOW=$PRIV_VIVALDI CHROMIUM_DISABLE_PRIVATE_WINDOW=$PRIV_CHROMIUM DISABLE_UNINSTALL=$DISABLE_UNINSTALL SYSTEM_COMPONENT=$SYSTEM_COMPONENT /q" 332 | Write-Verbose "Installer Arguments: $Arguments" 333 | Write-Host "Installer Log File: $InstallerLogFile" 334 | $Process = Start-Process -Wait cmd -ArgumentList $Arguments -PassThru 335 | if ($Process.ExitCode -ne 0) { 336 | Get-Content $InstallerLogFile -ErrorAction SilentlyContinue | Select-Object -Last 100 337 | Write-Host "Current cloud installer version for reference: " (Get-DefensXCurrentVersion | Out-String) 338 | } 339 | Write-Host "Exit Code: $($Process.ExitCode)" 340 | Write-Host "ComputerName: $($env:ComputerName)" 341 | 342 | # Return the exit code from the installation as the script exit code: 343 | exit $($Process.ExitCode) 344 | } 345 | 346 | function Uninstall-DefensX { 347 | $AgentInstall=("HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall","HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") | % { Get-ChildItem -Path $_ | % { Get-ItemProperty $_.PSPath } | ? { $_.DisplayName -match "$Application" } } 348 | $AgentVersion = $AgentInstall.DisplayVersion 349 | $AgentGUID = $AgentInstall.PSChildName 350 | # $UninstallString = "$($AgentInstall.UninstallString) /quiet /norestart" 351 | $UninstallString = "MsiExec.exe /x$AgentGUID /quiet /norestart" 352 | Show-DefensXCurrentInfo 353 | 354 | if ($AgentInstall) { 355 | Write-Host "Uninstalling now." 356 | Write-Verbose "Uninstall String: $UninstallString" 357 | $Process = Start-Process cmd.exe -ArgumentList "/c $UninstallString" -Wait -PassThru 358 | if ($Process.ExitCode -eq 1603) { 359 | Write-Host "Uninstallation attempt failed with error code: $($Process.ExitCode). Please review manually." 360 | Write-Host "Hint: This exit code likely requires the system to reboot prior to installation." 361 | } 362 | elseif ($Process.ExitCode -ne 0) { 363 | Write-Host "Uninstallation attempt failed with error code: $($Process.ExitCode). Please review manually." 364 | } 365 | else { 366 | Write-Host "Uninstallation attempt completed." 367 | } 368 | exit $($Process.ExitCode) 369 | } 370 | exit 0 371 | } 372 | 373 | ##### BEGIN SCRIPT ##### 374 | 375 | if ($Info) { 376 | Show-DefensXCurrentInfo 377 | Write-Host "Current cloud installer version: " (Get-DefensXCurrentVersion | Out-String) 378 | exit 0 379 | } 380 | 381 | if ($Uninstall) { 382 | Uninstall-DefensX $UninstallInfo 383 | exit 0 384 | } 385 | 386 | $KEY = Check-DefensXKey -Key $KEY 387 | 388 | if ($Upgrade) { 389 | $AgentInstall=("HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall","HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") | % { Get-ChildItem -Path $_ | % { Get-ItemProperty $_.PSPath } | ? { $_.DisplayName -match "$Application" } } 390 | $AgentVersion = $AgentInstall.DisplayVersion 391 | if (!$AgentVersion) { 392 | Write-Host "DefensX is NOT currently installed. Attempting installation now." 393 | Install-DefensX -Key $KEY 394 | exit 0 395 | } 396 | $CurrentVersion = Get-DefensXCurrentVersion 397 | if (($AgentVersion -ne $CurrentVersion) -or $Force) { 398 | Write-Host "Installed Version of DefensX: " $AgentVersion 399 | Write-Host "Installing current version:" $CurrentVersion 400 | Install-DefensX -Key $KEY 401 | } 402 | else { 403 | Write-Host "DefensX is already up-to-date with version:" $AgentVersion 404 | Write-Host "Verified that current verison on server is:" $CurrentVersion 405 | Write-Host "Installation date: " $AgentInstall.InstallDate 406 | Write-Host "Not installing since already up to date, use -Force to reinstall anyway." 407 | } 408 | exit 0 409 | } 410 | 411 | # Deafult to install if no other option specified and the service is not already installed 412 | If ( Get-Service $ServiceName -ErrorAction SilentlyContinue ) { 413 | Write-Host "The service" $ServiceName "is Already Installed...Bye." 414 | exit 0 415 | } 416 | else { 417 | Install-DefensX -Key $KEY 418 | exit 0 419 | } 420 | -------------------------------------------------------------------------------- /DefensX/README.md: -------------------------------------------------------------------------------- 1 | # Deploy-DefensX.ps1 2 | ## Synopsis 3 | Install, Upgrade, or Uninstall DefenseX provided a valid [DefensX](https://www.defensx.com) KEY from the PowerShell Deployment for a given tenant. 4 | 5 | ## Description 6 | Install, Upgrade, or Uninstall DefenseX provided a valid DefensX KEY from the PowerShell Deployment for a given tenant with KEY provided via parameter, environment variables, or NinjaRMM Custom Field. 7 | 8 | With no parameters, installation will be attempted, but only if the DefenseX services does not exist. Version will be ignored without the -Upgrade parameter. 9 | 10 | This script uses NinjaRMM Custom Fields to get the KEY by default (see CONFIG), but they can be passed in as parameters or Script Variables (environment variables) if desired. 11 | 12 | The Ninja Custom Field should be a single string with either a 16-character (alphanumeric) Short KEY or 224-character Long KEY (alphanumeric plus limited symbols such as -, _, and period), pulled out of the DefensX Deployments RMM field for a given client. The short key is recommended, configurable from the RMM popup. 13 | 14 | As long as you pass in a -KEY parameter, or provide a KEY environment variable, this script will function entirely separate from NinjaRMM. 15 | 16 | ## Parameter List 17 | The additional parameters in the param() array that are not individually documented are all switches that are true if they exist and false if they are not provided. They can be checkboxes in NinjaRMM Script Variables (environment variables) or passed in on the command line, and will be converted to a 1 or 0 and passed to the appropriate argument to the MSI installer, as defined in the DefensX documentation and user interface. The argument names are simplified for ease of typing, and don't correspond precisely to the MSI arguments, but the mapping should be relatively simple to understand. 18 | 19 | ### PARAMETER KEY 20 | If this parameter is passed to the DefensX PowerShell Deployment Script, it will be used instead of NinjaRMM Custom Fields. This key is located in the the DefensX Customer Console under Policies-\>Policy Groups-\>Deployments-\>RMM button, then turn on "Use Short Deployment Key" and then get the 16-character key at the very end of the command after the equals sign. The parameter should also accept the default 224-character key, but the 16-character short key is recommended. Required for install or upgrade unless supplied via $env:KEY (like via Ninja Script Variables) or NinjaRMM Custom Field. Not required for uninstall or Info check. 21 | 22 | ### PARAMETER Upgrade 23 | Install Reinstall/Upgrade DefensX if it's already installed and at an older version than the current version available online, or if it's not installed at all (will also install from scratch, just won't quit if it's already installed and will check if it's outdated and upgrade if it is). 24 | 25 | If the most current version is already installed, the agent will not be reinstalled unless you add the -Force parameter. 26 | 27 | ### PARAMETER Force 28 | Add to the -Upgrade parameter to attempt to reinstall even if the same version is already installed. 29 | 30 | ### PARAMETER Uninstall 31 | If DefensX is already installed, uninstall it, using the uninstall GUID from the Windows Registry. Other parameters will be ignored. 32 | 33 | ### PARAMETER Info 34 | Confirm installation status and print version info, then exit. Also queries the version number of the cloud installer file and reports the current installer version. Other parameters will be ignored. 35 | 36 | ### PARAMETER SpecificVersion 37 | If you provide a specifica installer version that the DefensX cloud has available to download, the installation will use this version of the MSI file to install the agent. This is an untested feature without much error checking or reporting, but uses the installer version URL provided directly in DefensX documentation. 38 | ## EXAMPLES 39 | ``` 40 | Deploy-DefensX.ps1 41 | Deploy-DefensX.ps1 -KEY 'yourKEY' 42 | Deploy-DefensX.ps1 -KEY 'yourKEY' -Upgrade 43 | Deploy-DefensX.ps1 -KEY 'yourKEY' -Upgrade -Force 44 | Deploy-DefensX.ps1 -Info 45 | Deploy-DefensX.ps1 -KEY 'yourKEY' -SpecificVersion 1.9.70 46 | Deploy-DefensX.ps1 -Uninstall 47 | Deploy-DefensX.ps1 -Upgrade 48 | ``` 49 | 50 | ## Version History 51 | Version 0.1.0 - 2024-03-21 - by David Szpunar - Initial released version 52 | 53 | Version 0.0.3 - 2024-03-21 - by David Szpunar - Updated comments and formatting to better describe where to obtain the KEY, refactor some logic (internal) 54 | 55 | Version 0.0.2 - 2024-03-21 - by David Szpunar - Updated comment docs and made slight code adjustments (internal) 56 | 57 | Version 0.0.1 - 2024-03-20 - by David Szpunar - Initial version by David Szpunar (internal) -------------------------------------------------------------------------------- /ImmyBot/Deploy-ImmyBot.ps1: -------------------------------------------------------------------------------- 1 | <# Deploy-ImmyBot.ps1 2 | .SYNOPSIS 3 | Install, Upgrade, or Uninstall ImmyBot Agent provided a valid ImmyBot KEY and ID from the PowerShell Deployment for a given tenant. 4 | .DESCRIPTION 5 | Install, Upgrade, or Uninstall ImmyBot Agent provided a valid ImmyBot KEY and ID from the PowerShell Deployment for a given tenant. 6 | 7 | With no parameters, installation will be attempted, but only if the ImmyBot Agent servies does not exist. Version will be ignored without the -Upgrade parameter. 8 | 9 | The ImmyBot PowerShell Deployment Script uses NinjaRMM Custom Fields to get the ID and KEY by default (see CONFIG), but they can be passed in as parameters if desired. 10 | 11 | The Ninja Custom Field should be a single string with the following format, space-separated, pulled out of a PowerShell deployment for a specific tenant: 12 | 13 | ID=abe6c78d-1de6-91f3-dde1-eafdev729836 KEY=aj78akdjr3ikKEasdfj58vnaew89SDJKVjFei88FVDS= 14 | 15 | You can alternately leave the ADDR field in the middle of the custom field value if you don't want to remove it, like this, and it will still work: 16 | 17 | ID=abe6c78d-1de6-91f3-dde1-eafdev729836 ADDR=https://SUBDOMAIN.immy.bot/plugins/api/v1/1 KEY=aj78akdjr3ikKEasdfj58vnaew89SDJKVjFei88FVDS= 18 | 19 | As long as you pass in -ID and -KEY parameters, or provide ID and KEY environment variables, this script will function entirely separate from NinjaRMM. 20 | .PARAMETER Tenant 21 | The plain subdomain for the ImmyBot tenant (the part after "https://"" and before ".immy.bot"). 22 | 23 | Not required for Info or Uninstall parameters. Always required for Install or Upgrade, though it can be defaulted in the parameters list (hardcoded). 24 | .PARAMETER ID 25 | If this parameter is passed from the ImmyBot PowerShell Deployment Script along with the -KEY parameter, they will be used instead of NinjaRMM Custom Fields. 26 | .PARAMETER KEY 27 | If this parameter is passed from the ImmyBot PowerShell Deployment Script along with the -ID parameter, they will be used instead of NinjaRMM Custom Fields. 28 | .PARAMETER Upgrade 29 | Install Reinstall/Upgrade ImmyBot if it's already installed and at an older version than the current version available online, or if it's not installed at all. 30 | 31 | If the current version is already installed, the agent will not be reinstalled unless you add the -Force parameter. 32 | .PARAMETER Force 33 | Add to the -Upgrade parameter to attempt to reinstall even if the same version is already installed. 34 | .PARAMETER Uninstall 35 | If ImmyBot is already installed, uninstall it, using the uninstall string from the Windows Registry. 36 | .PARAMETER Info 37 | Confirm installation status and print version info, then exit. Other parameters will be ignored. 38 | .EXAMPLE 39 | Deploy-ImmyBot.ps1 -Tenant 'yoursubdomain' 40 | Deploy-ImmyBot.ps1 -Tenant 'yoursubdomain' -ID 'yourID' -KEY 'yourKEY' 41 | Deploy-ImmyBot.ps1 -Tenant 'yoursubdomain' -ID 'yourID' -KEY 'yourKEY' -Upgrade 42 | Deploy-ImmyBot.ps1 -Tenant 'yoursubdomain' -ID 'yourID' -KEY 'yourKEY' -Upgrade -Force 43 | Deploy-ImmyBot.ps1 -Info 44 | Deploy-ImmyBot.ps1 -Uninstall 45 | 46 | 47 | Version 0.5.2 - 2024-03-19 - by David Szpunar - Adjust $ID and $KEY values to fix more similar typos, and add TLS version enforcement 48 | Version 0.5.1 - 2024-03-15 - by David Szpunar - Adjust $ID and $KEY values to fix typos that caused issues for non-custom-field parameters thanks to @HiTechPhilip 49 | Version 0.5.0 - 2024-02-19 - by David Szpunar - Refactor, add -Tenant, -Upgrade, -Force, -Uninstall, -Info switches and cloud version check function, documentation 50 | Version 0.0.5 - 2023-11-28 - by David Szpunar - Review and update for new ImmyBot version, minor adjustments 51 | Version 0.0.1 - 2023-01-26 - by David Szpunar - Initial version by David Szpunar, deployment only if not already installed 52 | #> 53 | [CmdletBinding()] 54 | param( 55 | [string] $Tenant = '', 56 | [string] $ID, 57 | [string] $KEY, 58 | [switch] $Upgrade, 59 | [switch] $Force, 60 | [switch] $Uninstall, 61 | [switch] $Info 62 | ) 63 | 64 | ##### CONFIG ##### 65 | # The page name (such as Deployments) for the custom Ninja Documentation page with the KEY and ID field in it. 66 | # Leave BLANK/empty string if you're using Device/Role Custom Fields and NOT Documentation Custom Fields, or if you aren't using NinjaRMM custom fields at all. 67 | $NinjaCustomDocumentationPage = 'Deployments' 68 | 69 | # The custom field name of the Ninja Custom field that contains both the ID and KEY field/value pairs from the ImmyBot PowerShell deployment script. 70 | # This can be a Device or Role Custom Field, or a Documenation Custom Field (if using the Documentation Page above), as long as Automation Write access is enabled. 71 | # Leave BLANK/empty string if you're passing in the -ID and -KEY parameters to the script directly rather than using NinjaRMM custom fields. 72 | $NinjaCustomFieldName = 'immyBotIDAndKey' 73 | ##### END CONFIG ##### 74 | 75 | #Service Name and Application Name 76 | $ServiceName = "ImmyBot Agent" 77 | $Application = "ImmyBot Agent" 78 | 79 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 80 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 81 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 82 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters 83 | foreach ($param in $switchParameters.keys) { 84 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue 85 | if ($var) { 86 | $envVarName = $var.Name.ToLower() 87 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 88 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { 89 | # Checkbox variables 90 | $PSBoundParameters[$envVarName] = $true 91 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 92 | } 93 | elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { 94 | # non-Checkbox string variables 95 | $PSBoundParameters[$envVarName] = $envVarValue 96 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 97 | } 98 | } 99 | } 100 | ### END PROCESS SCRIPT VARIABLES 101 | 102 | ##### FUNCTIONS ##### 103 | 104 | function Get-ImmyCurrentVersion { 105 | <# 106 | .SYNOPSIS 107 | Given the result of WebResponseObject with the ImmyBot teannt name, determine the current version of ImmyBot from the file name. 108 | .DESCRIPTION 109 | Given the result of WebResponseObject with the ImmyBot teannt name, determine the current version of ImmyBot from the file name. 110 | .EXAMPLE 111 | $version = Get-ImmyCurrentVersion -Tenant 'subdomain' 112 | #> 113 | [CmdletBinding()] 114 | param( 115 | [Parameter(Mandatory = $true)] 116 | [String] $Tenant 117 | ) 118 | 119 | if ([string]::IsNullOrEmpty($Tenant)) { 120 | Write-Host "ERROR: No Tenant specified (required for cloud version check and download URL), quitting." 121 | return $false 122 | } 123 | 124 | $uri = "https://$Tenant.immy.bot/plugins/api/v1/1/installer/latest-download" 125 | Write-Verbose "URI for latest version information: $uri" 126 | 127 | try { 128 | # Manually invoke a web request 129 | $Request = [System.Net.WebRequest]::Create($uri) 130 | $Request.AllowAutoRedirect = $false 131 | $Response = $Request.GetResponse() 132 | Write-Verbose "Response Headers returned from version check:" 133 | Write-Verbose ($Response.Headers | fl * | Out-String) 134 | Write-Verbose "Location header value: $($Response.Headers['Location'])" 135 | } 136 | catch { 137 | Write-Error 'Error: Web request failed.' -ErrorAction Stop 138 | } 139 | if ($Response.StatusCode.value__ -eq '307') { 140 | $redirectUrl = $($Response.Headers['Location']) 141 | Write-Verbose "Redirect URL: $RedirectUrl" 142 | $FileName = [System.IO.Path]::GetFileName($redirectUrl) 143 | Write-Verbose "FileName: $FileName" 144 | if (-not $FileName) { Write-Error 'Error: Failed to resolve file name from URI.' -ErrorAction Stop } 145 | $FileName -match '^(\d+\.\d+\.\d+)\D+(\d+)' | Out-Null 146 | $version = $matches[1] + '.' + $matches[2] 147 | Write-Verbose "Version: $version" 148 | return $version 149 | } 150 | return $null 151 | } 152 | 153 | function Show-ImmyCurrentInfo { 154 | $ImmyAgentInstall = Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, UninstallString, Publisher, InstallDate, InstallLocation | Where-Object { $_.DisplayName -eq "$Application" } 155 | $AgentVersion = $ImmyAgentInstall.DisplayVersion 156 | 157 | if ($ImmyAgentInstall) { 158 | Write-Host "$Application is currently installed." 159 | Write-Host "Installed version: $AgentVersion" 160 | } 161 | else { 162 | Write-Host "$Application is not installed." 163 | } 164 | } 165 | 166 | function Service-Check { 167 | If ( Get-Service $ServiceName -ErrorAction SilentlyContinue ) { 168 | return $true 169 | } 170 | else { 171 | return $false 172 | } 173 | } 174 | 175 | function Check-ImmyBotIDandKey { 176 | param( 177 | [Parameter(Mandatory = $true,Position=0)] 178 | [string] $Tenant, 179 | [string] $ID, 180 | [string] $KEY 181 | ) 182 | if([string]::IsNullOrWhiteSpace($ID) -or [string]::IsNullOrWhiteSpace($KEY)) { 183 | # Make sure this Documentation form, template, and field exist, are script readable, and have the corresponding tenant ID/KEY saved! 184 | if([string]::IsNullOrWhiteSpace($NinjaCustomDocumentationPage)) { 185 | if(![string]::IsNullOrWhiteSpace($NinjaCustomFieldName)) { 186 | Write-Host "Attempting to retreive ID and Key from NinjaRMM Custom Field..." 187 | $IDandKey = Ninja-Property-Get $NinjaCustomFieldName 188 | } else { 189 | Write-Host "Ninja Custom Field Name not provided, not attempting to retrieve ID and Key from NinjaRMM, must used -Id and -Key parameters." 190 | } 191 | } else { 192 | Write-Host "Attempting to retreive ID and Key from NinjaRMM Documentation Custom Field..." 193 | $IDandKey = Ninja-Property-Docs-Get-Single $NinjaCustomDocumentationPage $NinjaCustomFieldName 194 | } 195 | } 196 | if([string]::IsNullOrWhiteSpace($IDandKey)) { 197 | $IDandKey = "ID=$($global:ID) KEY=$($global:KEY)" 198 | } 199 | Write-Verbose "IDandKey Value: $IDandKey" 200 | 201 | #write-host "ImmyBot ID and Key from Documentation Field: $IDandKey" 202 | $regex = "ID=([a-zA-Z00-9]{8}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{12})\s.*\s*KEY=(\S{44})" 203 | $IDandKey -match $regex | Out-Null 204 | if ($Matches) { 205 | ($ID, $KEY) = ($Matches[1], $Matches[2]) 206 | } 207 | else { 208 | Write-Host "Nothing found in ImmyBot ID and Key field, can't deploy for this tenant...Bye." 209 | exit 1 210 | } 211 | 212 | if ($ID -NotMatch "^[a-zA-Z00-9]{8}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{4}-[a-zA-Z00-9]{12}$") { 213 | Write-Host "The ID field is either not defined or is in an invalid format, set valid immyBotIDAndKey in documentation" 214 | Write-Host "Format should be: ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX KEY=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" 215 | Write-Host "The discovered value is" $ID 216 | exit 1 217 | } 218 | elseif ($KEY -NotMatch "^\S{44}$") { 219 | Write-Host "The KEY field is either not defined or is in an invalid format, set valid immyBotIDAndKey in documentation" 220 | Write-Host "Format should be: ID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX KEY=YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY" 221 | Write-Host "The discovered value is" $KEY 222 | exit 1 223 | } 224 | else { 225 | #write-host "Installing with ID=" $ID 226 | $IDmasked = $ID.SubString(0, 10) + "****-****-" + $ID.SubString(24, 12) 227 | Write-Host "Installing with ID: " $IDmasked 228 | 229 | #write-host "Installing with KEY=" $KEY 230 | $KEYmasked = $KEY.SubString(0, 10) + "*************************" + $KEY.SubString(35, 9) 231 | Write-Host "Installing with KEY:" $KEYmasked 232 | 233 | Write-Host "(identifiers partially masked for log)" 234 | } 235 | if ([string]::IsNullOrEmpty($Tenant)) { 236 | Write-Host "ERROR: No Tenant specified (required for cloud version check and download URL), quitting." 237 | exit 1 238 | } 239 | return @($ID, $KEY) 240 | } 241 | 242 | function Install-ImmyBot { 243 | param( 244 | [Parameter(Mandatory = $true,Position=0)] 245 | [string] $Tenant, 246 | [Parameter(Mandatory = $true,Position=1)] 247 | [string] $Id, 248 | [Parameter(Mandatory = $true,Position=2)] 249 | [string] $Key, 250 | [switch] $Reinstall 251 | ) 252 | 253 | $url = "https://$Tenant.immy.bot/plugins/api/v1/1/installer/latest-download" 254 | $ADDR = "https://$Tenant.immy.bot/plugins/api/v1/1" 255 | # Ensure a secure TLS version is used. 256 | $ProtocolsSupported = [enum]::GetValues('Net.SecurityProtocolType') 257 | if ( ($ProtocolsSupported -contains 'Tls13') -and ($ProtocolsSupported -contains 'Tls12') ) { 258 | # Use only TLS 1.3 or 1.2 259 | [Net.ServicePointManager]::SecurityProtocol = ( 260 | [Enum]::ToObject([Net.SecurityProtocolType], 12288) -bOR [Enum]::ToObject([Net.SecurityProtocolType], 3072) 261 | ) 262 | } else { 263 | # Use only 1.2 264 | try { 265 | # In certain .NET 4.0 patch levels, SecurityProtocolType does not have a TLS 1.2 entry. 266 | # Rather than check for 'Tls12', we force-set TLS 1.2 and catch the error if it's truly unsupported. 267 | [Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([Net.SecurityProtocolType], 3072) 268 | } catch { 269 | $msg = $_.Exception.Message 270 | $err = "ERROR: Unable to use a secure version of TLS. Please verify Hotfix KB3140245 is installed." 271 | Write-Host "$err : $msg" 272 | Write-Error "$err : $msg" 273 | exit 1 274 | } 275 | } 276 | # The following code is the ImmyBot PowerShell deployment separated into lines for readability and with the ID and KEY variables swapped into the arguments: 277 | $ErrorActionPreference = "Stop" 278 | $InstallerFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".msi") 279 | (New-Object System.Net.WebClient).DownloadFile($url, $InstallerFile) 280 | $InstallerLogFile = [io.path]::ChangeExtension([io.path]::GetTempFileName(), ".log") 281 | if ($Reinstall) { 282 | $Arguments = " /c msiexec /i `"$InstallerFile`" /qn /norestart /l*v `"$InstallerLogFile`" REBOOT=REALLYSUPPRESS ID=$ID ADDR=$ADDR KEY=$KEY REINSTALL=ALL REINSTALLMODE=vemus" 283 | } 284 | else { 285 | $Arguments = " /c msiexec /i `"$InstallerFile`" /qn /norestart /l*v `"$InstallerLogFile`" REBOOT=REALLYSUPPRESS ID=$ID ADDR=$ADDR KEY=$KEY" 286 | } 287 | Write-Host "InstallerLogFile: $InstallerLogFile" 288 | $Process = Start-Process -Wait cmd -ArgumentList $Arguments -PassThru 289 | if ($Process.ExitCode -ne 0) { 290 | Get-Content $InstallerLogFile -ErrorAction SilentlyContinue | Select-Object -Last 100 291 | Write-Host "Current cloud installer version for reference: " (Get-ImmyCurrentVersion -Tenant $Tenant | Out-String) 292 | } 293 | Write-Host "Exit Code: $($Process.ExitCode)" 294 | Write-Host "ComputerName: $($env:ComputerName)" 295 | 296 | # Return the exit code from the installation as the script exit code: 297 | exit $($Process.ExitCode) 298 | } 299 | 300 | function Uninstall-ImmyBot { 301 | $ImmyAgentInstall = Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, UninstallString, Publisher, InstallDate, InstallLocation | Where-Object { $_.DisplayName -eq "$Application" } 302 | $AgentVersion = $ImmyAgentInstall.DisplayVersion 303 | $UninstallString = "$($ImmyAgentInstall.UninstallString) /quiet /norestart" 304 | Show-ImmyCurrentInfo 305 | 306 | if ($ImmyAgentInstall) { 307 | Write-Host "Uninstalling now." 308 | $Process = Start-Process cmd.exe -ArgumentList "/c $UninstallString" -Wait -PassThru 309 | if ($Process.ExitCode -eq 1603) { 310 | Write-Host "Uninstallation attempt failed with error code: $($Process.ExitCode). Please review manually." 311 | Write-Host "Hint: This exit code likely requires the system to reboot prior to installation." 312 | } 313 | elseif ($Process.ExitCode -ne 0) { 314 | Write-Host "Uninstallation attempt failed with error code: $($Process.ExitCode). Please review manually." 315 | } 316 | else { 317 | Write-Host "Uninstallation attempt completed." 318 | } 319 | exit $($Process.ExitCode) 320 | } 321 | exit 0 322 | } 323 | 324 | ##### BEGIN SCRIPT ##### 325 | 326 | if ($Info) { 327 | Show-ImmyCurrentInfo 328 | Write-Host "Current cloud installer version: " (Get-ImmyCurrentVersion -Tenant $Tenant | Out-String) 329 | exit 0 330 | } 331 | 332 | if ($Uninstall) { 333 | Uninstall-ImmyBot $UninstallInfo 334 | exit 0 335 | } 336 | 337 | ($ID, $KEY) = Check-ImmyBotIDandKey -Tenant $Tenant -Id $ID -Key $KEY 338 | 339 | if (!$Upgrade) { 340 | If ( Service-Check ) { 341 | Write-Host "The service" $ServiceName "is Already Installed...Bye." 342 | exit 0 343 | } 344 | Install-ImmyBot -Tenant $Tenant -ID $Id -Key $KEY 345 | exit 0 346 | } 347 | 348 | if ($Upgrade) { 349 | $ImmyAgentInstall = Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, UninstallString, Publisher, InstallDate, InstallLocation | Where-Object { $_.DisplayName -eq "$Application" } 350 | $AgentVersion = $ImmyAgentInstall.DisplayVersion 351 | if (!$AgentVersion) { 352 | Write-Host "ImmyBot is NOT currently installed. Attempting installation now." 353 | Install-ImmyBot -Tenant $Tenant -ID $Id -Key $KEY 354 | exit 0 355 | } 356 | $CurrentVersion = Get-ImmyCurrentVersion -Tenant $Tenant 357 | if (($AgentVersion -ne $CurrentVersion) -or $Force) { 358 | Write-Host "Installed Version of ImmyBot: " $AgentVersion 359 | Write-Host "Installing current version:" $CurrentVersion 360 | Install-ImmyBot -Tenant $Tenant -Id $ID -Key $KEY -Reinstall 361 | } 362 | else { 363 | Write-Host "ImmyBot is already up-to-date with version:" $AgentVersion 364 | Write-Host "Verified that current verison on server is:" $CurrentVersion 365 | Write-Host "Installation date: " $ImmyAgentInstall.InstallDate 366 | Write-Host "Not installing since already up to date, use -Force to reinstall anyway." 367 | } 368 | } 369 | 370 | -------------------------------------------------------------------------------- /Manage-Visual-C++-Redistributables-in-Windows/README.md: -------------------------------------------------------------------------------- 1 | # Manage Visual C++ Redistributables in Windows 2 | Uninstalls or installs Visual C++ Redistributables in various ways. Uses the [VcRedist](https://vcredist.com/quick/) PowerShell module from the NuGet, and installs the module and an updated Package Manager (to use NuGet at all) if required first. 3 | 4 | - Call with NO arguments/parameters to output a list of the currently installed VC++ Redistributables on the system, but do nothing. 5 | - Call with `-Unsupported` to uninstall all unsupported versions of the Redistributable (2012 and older). 6 | - Call with `-ToUninstall` and one or more years, separated by comma (no spaces), to uninstall specific versions of the Redistributable. 7 | - Call with `-UninstallAll` to uninstall all versions of the Redistributable (all versions installed on the whole system, of any type!). 8 | - Call with `-Install` to install all SUPPORTED versions of the Redistributable (2013 and newer). 9 | 10 | Can mix and match `-Install` and the other options together; supported versions will be installed after the uninstallations are completed. 11 | 12 | ## EXAMPLES 13 | `script.ps1 -ToUninstall "2005,2010”` 14 | Remove all versions of Visual C++ Redistributable 2005 and 2010. 15 | 16 | `script.ps1 -UninstallAll -Install` 17 | Uninstall all versions of Visual C++ Redistributable, then install all supported versions. This can be quite handy to make sure only the latest patch version is installed, as the specific minor version is often outdated or multiple are installed, and this will ensure only the latest version of only the supported VC++ Redistributable releases are installed. 18 | 19 | ### NOTE 20 | Some software may stop working properly if VC++ Redistributables are not installed that match their needed version! If you run into 21 | issues with apps after cleaning up, you may need to reinstall the app (which often reinstalls the VC++ Redistributable it came with), or 22 | install the VC++ Redistributable version manually if it's unsupported. This script can be modified to install other versions, either all 23 | versions including unsupported by changing this line: 24 | 25 | `$Redists = Get-VcList | Save-VcRedist -Path $Path | Install-VcRedist -Silent` 26 | 27 | to be like this with added `-ExportAll` parameter: 28 | 29 | `$Redists = Get-VcList -Export All | Save-VcRedist -Path $Path | Install-VcRedist -Silent` 30 | 31 | Or, you can filter the output using Where-Object per the Examples at [https://vcredist.com/get-vclist/#examples](https://vcredist.com/get-vclist/#examples) in order to install only specific versions or architectures. This may be added to this script in a later release, to control which versions to install besides none or "all supported" as it started just removing old versions and the install-supported was added after the fact. 32 | 33 | ## NinjaOne 34 | This script works with parameters, or via Script Variables with these types and names, none of which are required: 35 | [Checkbox] Unsupported 36 | [String/Text] ToUninstall 37 | [Checkbox] UninstallAll 38 | [Checkbox] Install 39 | 40 | Your configuration may look something like this for the above: 41 | 42 | ![VisualC-Redistributable-Cleanup-ScriptVariables](VisualC-Redistributable-Cleanup-ScriptVariables.png) 43 | 44 | ## VC++ Version Note 45 | The Install command installs VC++ 2012 in addition to the newer ones. However, `-Unsupported` removes up through 2012. My script removes it because it left Microsoft support in January 2023 per [https://learn.microsoft.com/en-us/lifecycle/products/visual-studio-2012](https://learn.microsoft.com/en-us/lifecycle/products/visual-studio-2012) but the PowerShell module [VcRedist](https://vcredist.com/versions/ "VcRedist Included Redistributables Version List") still counts it as a current version as of Nov. 30, 2023. Which is probably fine, but you should be aware of the discrepancy. 46 | -------------------------------------------------------------------------------- /Manage-Visual-C++-Redistributables-in-Windows/VisualC-Redistributable-Cleanup-ScriptVariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/Manage-Visual-C++-Redistributables-in-Windows/VisualC-Redistributable-Cleanup-ScriptVariables.png -------------------------------------------------------------------------------- /Manage-Visual-C++-Redistributables-in-Windows/VisualC-Redistributable-Cleanup.ps1: -------------------------------------------------------------------------------- 1 | <# VisualC-Redistributable-Cleanup.ps1 2 | SOURCE: https://vcredist.com/uninstall-vcredist/ 3 | SOURCE: https://www.powershellgallery.com/packages/VcRedist/ 4 | 5 | Version: 0.0.1 - 2023-11-30 by David Szpunar - Initial Version 6 | 7 | DESCRIPTION: Uninstalls or installs Visual C++ Redistributables in various ways. Uses the VcRedist PowerShell module from the NuGet, and 8 | installs the module and an updated Package Manager (to use NuGet at all) if required first. 9 | 10 | Call with NO arguments/parameters to output a list of the currently installed VC++ Redistributables on the system, but do nothing. 11 | Call with -Unsupported to uninstall all unsupported versions of the Redistributable (2012 and older). 12 | Call with -ToUninstall and one or more years, separated by comma (no spaces), to uninstall specific versions of the Redistributable. 13 | Call with -UninstallAll to uninstall all versions of the Redistributable (all versions installed on the whole system, of any type!). 14 | Call with -Install to install all SUPPORTED versions of the Redistributable (2013 and newer). 15 | 16 | Can mix and match -Install and the other options together; supported versions will be installed after the uninstallations are completed. 17 | 18 | EXAMPLES: 19 | script.ps1 -ToUninstall "2005,2010" 20 | Remove all versions of Visual C++ Redistributable 2005 and 2010. 21 | 22 | script.ps1 -UninstallAll -Install 23 | Uninstall all versions of Visual C++ Redistributable, then install all supported versions. This can be quite handy to make sure only 24 | the latest patch version is installed, as the specific minor version is often outdated or multiple are installed, and this will 25 | ensure only the latest version of only the supported VC++ Redistributable releases are installed. 26 | 27 | NOTE: Some software my stop working properly if VC++ Redistributables are not installed that match their needed version! If you run into 28 | issues with apps after cleaning up, you may need to reinstall the app (which often reinstalls the VC++ Redistributable it came with), or 29 | install the VC++ Redistributable version manually if it's unsupported. This script can be modified to install other versions, either all 30 | versions including unsupported by changing this line: 31 | $Redists = Get-VcList | Save-VcRedist -Path $Path | Install-VcRedist -Silent 32 | 33 | to be like this with added -ExportAll parameter: 34 | $Redists = Get-VcList -Export All | Save-VcRedist -Path $Path | Install-VcRedist -Silent 35 | 36 | Or, you can filter the output using Where-Object per the Examples at https://vcredist.com/get-vclist/#examples in order to 37 | install only specific versions or architectures. This may be added to this script in a later release, to control which versions 38 | to install besides none or "all supported" as it started just removing old versions and the install-supported was added after the fact. 39 | 40 | NINJAONE: This script works with parameters, or via Script Variables with these types and names, none of which are required: 41 | [Checkbox] Unsupported 42 | [String/Text] ToUninstall 43 | [Checkbox] UninstallAll 44 | [Checkbox] Install 45 | 46 | The script does NOT output anything to custom variables in NinjaOne, but it easily could be modified to do so. 47 | #> 48 | param ( 49 | [string]$ToUninstall, 50 | [switch]$Install, 51 | [switch]$UninstallAll, 52 | [switch]$Unsupported 53 | ) 54 | 55 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 56 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 57 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 58 | $switchParameters = if($MyInvocation.InvocationName) { (Get-Command -Name $MyInvocation.InvocationName).Parameters } else { $null } 59 | foreach ($param in $switchParameters.keys) { 60 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue; 61 | if($var) { 62 | $envVarName = $var.Name.ToLower() 63 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 64 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and $envVarValue.ToLower() -eq 'true') { # Checkbox variables 65 | $PSBoundParameters[$envVarName] = $true 66 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 67 | } elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and $envVarValue -ne 'false') { # non-Checkbox string variables 68 | $PSBoundParameters[$envVarName] = $envVarValue 69 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 70 | } 71 | } 72 | } 73 | ### END PROCESS SCRIPT VARIABLES 74 | 75 | # Path to store installation files if installing: 76 | $Path = "$env:Temp\VcRedist" 77 | 78 | function Load-Module ($m) { 79 | 80 | # If module is imported say that and do nothing 81 | if (Get-Module | Where-Object {$_.Name -eq $m}) { 82 | write-host "Module $m is already imported." 83 | return $true 84 | } 85 | else { 86 | 87 | # If module is not imported, but available on disk then import 88 | if (Get-Module -ListAvailable | Where-Object {$_.Name -eq $m}) { 89 | Import-Module $m 90 | return $true 91 | } 92 | else { 93 | 94 | # If module is not imported, not available on disk, but is in online gallery then install and import 95 | # if (Find-Module -Name $m | Where-Object {$_.Name -eq $m}) { 96 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 97 | Get-PackageProvider -Name "NuGet" -Force | Out-Null 98 | Install-Module -Name $m -Force 99 | Import-Module $m 100 | return $true 101 | # } 102 | else { 103 | 104 | # If the module is not imported, not available and not in the online gallery then abort 105 | write-host "Module $m not imported, not available and not in an online gallery, exiting." 106 | return $false 107 | } 108 | } 109 | } 110 | } 111 | 112 | if (Load-Module "VcRedist") { 113 | # Write-Host "VCRedist module is already installed, continuing." 114 | } 115 | else { 116 | Write-Host "Can't find or install VcRedist module, quititng." 117 | exit 1 118 | # Install-Module -Name VcRedist -Force 119 | } 120 | Write-Host "Currently installed VC Redistributables:" 121 | Get-InstalledVcRedist | Select Name | Format-Table 122 | 123 | [array]$ToUninstallArray = @() 124 | 125 | if ($Unsupported -and $false -eq $UninstallAll) { 126 | $ToUninstallArray = @('2005','2008', '2010', '2012') 127 | Write-Host "Uninstalling all unsupported Visual C+ Redistributables, meaning up through 2012." 128 | } elseif ($ToUninstall.Length -gt 0 -and $false -eq $UninstallAll) { 129 | $ToUninstallArray = $ToUninstall.Trim() -split ',' 130 | 131 | if ($ToUninstallArray.Count -eq 0) { 132 | Write-Host "No VC Redistributables provided to uninstall." 133 | exit 0 134 | } else { 135 | Write-Host "Uninstalling the following Visual C+ Redistributables:" 136 | Write-Host "" 137 | } 138 | } elseif ($false -eq $UninstallAll) { 139 | Write-Host "No VC Redistributables provided to uninstall." 140 | exit 0 141 | } 142 | 143 | if(!$UninstallAll) { 144 | Write-Host "Count of versions to try and uninstall:" $ToUninstallArray.Count "(" ($ToUninstallArray -join ", ") "):" 145 | Write-Host "" 146 | 147 | foreach($Version in $ToUninstallArray) { 148 | foreach ($foundVersion in (Get-InstalledVcRedist | where-object Name -like "*$Version*")) { 149 | Write-Host "Triggering uninstall of VC Redistributable $($Version):" $foundVersion.Name 150 | # Get-InstalledVcRedist | where-object Name -like "*$Version*" | Uninstall-VcRedist -Confirm:$false 151 | $foundVersion | Uninstall-VcRedist -Confirm:$false 152 | } 153 | Write-Host "" 154 | } 155 | } elseif ($UninstallAll) { 156 | Write-Host "Uninstalling ALL VC Redistributables." 157 | foreach ($foundVersion in Get-InstalledVcRedist) { 158 | Write-Host "Triggering uninstall of VC Redistributable:" $foundVersion.Name 159 | # Get-InstalledVcRedist | where-object Name -like "*$Version*" | Uninstall-VcRedist -Confirm:$false 160 | $foundVersion | Uninstall-VcRedist -Confirm:$false 161 | } 162 | Write-Host "" 163 | } 164 | 165 | 166 | if($Install) { 167 | Write-Host "Installing VC Redistributables:" 168 | #region tasks/install apps 169 | Write-Host "Saving VcRedists to path: $Path." 170 | New-Item -Path $Path -ItemType "Directory" -Force -ErrorAction "SilentlyContinue" > $null 171 | 172 | Write-Host "Downloading and installing supported Microsoft Visual C++ Redistributables." 173 | $Redists = Get-VcList | Save-VcRedist -Path $Path | Install-VcRedist -Silent 174 | 175 | Write-Host "Installed Visual C++ Redistributables:" 176 | $Redists | Select-Object -Property "Name", "Release", "Architecture", "Version" -Unique 177 | #endregion 178 | } 179 | 180 | Write-Host "Installed VC Redistributables after changes:" 181 | Get-InstalledVcRedist | Select Name | Format-Table 182 | -------------------------------------------------------------------------------- /Microsoft-ASR-Attack-Surface-Reduction-Rules/Microsoft-Defender-ASR-Output-Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/Microsoft-ASR-Attack-Surface-Reduction-Rules/Microsoft-Defender-ASR-Output-Example.png -------------------------------------------------------------------------------- /Microsoft-ASR-Attack-Surface-Reduction-Rules/Microsoft-Defender-ASR-ScriptVariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/Microsoft-ASR-Attack-Surface-Reduction-Rules/Microsoft-Defender-ASR-ScriptVariables.png -------------------------------------------------------------------------------- /Microsoft-ASR-Attack-Surface-Reduction-Rules/README.md: -------------------------------------------------------------------------------- 1 | # Configure Windows Defender ASR Rule and Report Current ASR Rules 2 | Windows Defender Attack Surface Reduction rules, _which only work when Defender is the primary antivirus on a system,_ allow you to enforce or audit additional security rules that may cause problems but may also provide extra protection. These are generally configured via Intune policies, but the "Block abuse of exploited vulnerable signed drivers" rule is, currently, only available via registry or GPO or PowerShell and not in the "Configuration settings" of an Intune profile's GUI like many other ASR rules. 3 | 4 | To that end, I assembled a quick script called [Set-MicrosoftASR-Rule.ps1](Set-MicrosoftASR-Rule.ps1) that (by default, can be overridden) sets the above rule to `Enable` (block) mode via PowerShell. It also reports on the state of any other ASR rules on a system by GUID, either using the `-ReportOnly` flag (makes no changes) or the `-Verbose` flag that reports on all while adjusting one. 5 | 6 | You can set the value of any ASR GUID to `Enable`, `Disabled`, `AuditOnly`, or `Warn` modes by passing that text to the `-Mode` flag (`Enable` is the default), and you can pass in any `-ASRID` value you want, with the default being `56a863a9-875e-4185-98a7-b882c64b5ce5` (the vulnerable signed drivers block). 7 | 8 | The Rule to GUID mappings from Microsoft for the rules are at: [https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#asr-rule-to-guid-matrix](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#asr-rule-to-guid-matrix) 9 | 10 | The details of the _Block abuse of vulnerable signed drivers_ rule are at [https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#block-abuse-of-exploited-vulnerable-signed-drivers](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#block-abuse-of-exploited-vulnerable-signed-drivers) 11 | 12 | Many rules are supported on Server and Workstations; you can determine which ones are supported on which systems here: [https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#asr-rules-supported-operating-systems](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#asr-rules-supported-operating-systems) 13 | 14 | All the above links are to anchors within the same long page. 15 | 16 | The process for enabling ASR rules via PowerShell is documented at: [https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/enable-attack-surface-reduction?view=o365-worldwide#powershell](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/enable-attack-surface-reduction?view=o365-worldwide#powershell) (which is used in this script). 17 | 18 | I'm using this both as a Run once immediately script for all of my endpoints to update them as soon as they come online under management, as well as a run-weekly update to ensure compliance if any get changed, both from a NinjaRMM policy. 19 | 20 | ## Example Output 21 | ### Script Output 22 | Here's what one machine on our system looks like in Report Only mode with the script, with "rule in question" being the driver one that the script is set to add: 23 | 24 | ``` 25 | Rule 0 GUID: 01443614-cd74-433a-b99e-2ecdc07bfc25 is set to value Audit 26 | Rule 1 GUID: 26190899-1602-49e8-8b27-eb1d0a1ce869 is set to value Warn 27 | Rule 2 GUID: 3b576869-a4ec-4529-8536-b80a7769e899 is set to value Audit 28 | Rule 3 GUID: 56a863a9-875e-4185-98a7-b882c64b5ce5 is set to value Block (RULE IN QUESTION) 29 | Rule 4 GUID: 5beb7efe-fd9a-4556-801d-275e5ffc04cc is set to value Block 30 | Rule 5 GUID: 75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84 is set to value Block 31 | Rule 6 GUID: 7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c is set to value Block 32 | Rule 7 GUID: 92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b is set to value Warn 33 | Rule 8 GUID: 9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2 is set to value Block 34 | Rule 9 GUID: b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4 is set to value Block 35 | Rule 10 GUID: be9ba2d9-53ea-4cdc-84e5-9b1eeee46550 is set to value Warn 36 | Rule 11 GUID: c1db55ab-c21a-4637-bb3f-a12568109d35 is set to value Block 37 | Rule 12 GUID: d1e49aac-8f56-4280-b9ba-993a6d77406c is set to value Audit 38 | Rule 13 GUID: d3e037e1-3eb8-44c8-a917-57927947596d is set to value Block 39 | Rule 14 GUID: d4f940ab-401b-4efc-aadc-ad5f3c50688a is set to value Warn 40 | Rule 15 GUID: e6db77e5-3df2-4cf1-b95a-636979351e5b is set to value Audit 41 | ``` 42 | 43 | In a perfect world you set all of them to block, but sometimes you run into user issues that require some tweaks. 44 | 45 | ### Intune GUI Configuration 46 | 47 | In Intune, the settings above (minus the "rule in question" one set via PowerShell) look like this screenshot: ![ASR Script Config in Defender Example](Microsoft-Defender-ASR-Output-Example.png) 48 | 49 | ## From NinjaRMM 50 | This script works with Script Variables as well. See screenshot for the ones I have configured, which are: 51 | 52 | - **ASR ID** [text] (default is `56a863a9-875e-4185-98a7-b882c64b5ce5`) 53 | - **Mode** [Drop-down] with values ‘`Enable`’,’`Disabled`’,’`AuditMode`’,’`Warn`’ (`Enable` is default) 54 | - **Verbose** [CheckBox] (default is `Checked`) 55 | - **Report Only** [CheckBox] (default is `Unchecked`) 56 | 57 | ![NinjaRMM Script Variables Screenshot](Microsoft-Defender-ASR-ScriptVariables.png) 58 | 59 | See script documentation at the top for parameters and usage examples. 60 | 61 | ## Policy Overrides 62 | This should work if you're not in Intune but still Defender for Endpoint licensed, this should work still unless a policy overrides it. This Policy Conflict statement shows you what wins when you have multiple policies: [https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/enable-attack-surface-reduction?view=o365-worldwide#policy-conflict](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/enable-attack-surface-reduction?view=o365-worldwide#policy-conflict) 63 | 64 | Microsoft Defender for Endpoint has to be the PRIMARY antivirus for a device for ASR rules to work, though with EDR Enforce Mode there is a way for Defender to do a bit of enforcement even with another provider's EDR, but that's not entirely standard. 65 | 66 | The desire of Microsoft to have you manually create a custom profile with OMA-URI setting per [https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/enable-attack-surface-reduction?view=o365-worldwide#custom-profile-in-intune](https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/enable-attack-surface-reduction?view=o365-worldwide#custom-profile-in-intune) to enable the "Block abuse of exploited vulnerable signed drivers" rule rather than just letting you select it from an Intune drop-down like the rest of them mostly are, and I didn't want to deal with that so I PowerShelled it. The other settings I actually have Intune-pushed, but you can use GPOs with the Microsoft Defender Antivirus area if you don't have Intune. -------------------------------------------------------------------------------- /Microsoft-ASR-Attack-Surface-Reduction-Rules/Set-MicrosoftASR-Rule.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 5.0 2 | <# 3 | .SYNOPSIS 4 | Set Microsoft Attack Surface Reduction (ASR) Rule to a specified mode and report on existing ASR rule settings. 5 | .DESCRIPTION 6 | Report on currently configured ASR rules in the registry using -ReportOnly; otherwise also request updating of a particular rule by GUID to an available mode. 7 | 8 | .PARAMETER ASRID 9 | The GUID of the Attack Surface Reduction (ASR) rule to be set to a specified mode. 10 | 11 | .PARAMETER ReportOnly 12 | Report on currently configured ASR rules in the registry but don't make any changes. 13 | 14 | .PARAMETER Mode 15 | The mode to set the ASR rule to. Default to 'Enable' (Block). Options are 'Enable', 'Disabled', 'AuditMode', or 'Warn'. 16 | 17 | .PARAMETER Verbose 18 | When updating the configuration, prints all currently configured ASR rule and settings to the console in addition to making the requested update, and not just the status of the rule being adjusted. 19 | 20 | .NOTES 21 | 2023-11-27: Initial release 22 | 23 | .LINK 24 | Not currently linked externally. 25 | 26 | .EXAMPLE 27 | Set-MicrosoftASR-Rule.ps1 28 | 29 | This sets the default rule, which is "Block abuse of exploited vulnerable signed drivers" to "Enable" (Block) because of the script defaults. 30 | 31 | .EXAMPLE 32 | Set-MicrosoftASR-Rule.ps1 -ReportOnly 33 | 34 | This sets the default rule, which is "Block abuse of exploited vulnerable signed drivers" to "Enable" (Block) and reports all currently configured ASR rule and settings. 35 | 36 | .EXAMPLE 37 | Set-MicrosoftASR-Rule.ps1 -ASRID '56a863a9-875e-4185-98a7-b882c64b5ce5' 38 | 39 | This sets the default rule, which is "Block abuse of exploited vulnerable signed drivers" to "Enable" (Block) by passing the ASR rule GUID. 40 | 41 | .EXAMPLE 42 | Set-MicrosoftASR-Rule.ps1 -ASRID '56a863a9-875e-4185-98a7-b882c64b5ce5' -Verbose 43 | 44 | This sets the default rule with the passed GUID to "Enable" (Block) and reports all currently configured ASR rule and settings. 45 | 46 | .EXAMPLE 47 | Set-MicrosoftASR-Rule.ps1 -ASRID '56a863a9-875e-4185-98a7-b882c64b5ce5' -Mode 'Disabled' -Verbose 48 | 49 | This sets the default rule, which is "Block abuse of exploited vulnerable signed drivers" to "Disabled" (Allow) by passing the ASR rule GUID and reports all currently configured ASR rule and settings. 50 | #> 51 | <# 52 | Useful links and references: 53 | All Attack Surface Reduction rules in Defender with IDs: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide 54 | GUID Matrix: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#asr-rule-to-guid-matrix 55 | PowerShell reference: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/enable-attack-surface-reduction?view=o365-worldwide#powershell 56 | (Get-MpPreference).AttackSurfaceReductionRules_Ids 57 | (Get-MpPreference).AttackSurfaceReductionRules_Ids -eq "56a863a9-875e-4185-98a7-b882c64b5ce5" 58 | (Get-MpPreference).AttackSurfaceReductionRules_Actions 59 | 60 | 61 | GUID: 56a863a9-875e-4185-98a7-b882c64b5ce5 62 | Name: Unsigned Drivers: https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/attack-surface-reduction-rules-reference?view=o365-worldwide#block-abuse-of-exploited-vulnerable-signed-drivers 63 | #> 64 | param ( 65 | [string]$ASRID = '56a863a9-875e-4185-98a7-b882c64b5ce5', 66 | [switch]$Verbose, 67 | [Switch]$ReportOnly, 68 | [ValidateSet('Enable','Disabled','AuditMode','Warn')] 69 | [string]$Mode = 'Enable' 70 | ) 71 | 72 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 73 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 74 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 75 | $switchParameters = if($MyInvocation.InvocationName) { (Get-Command -Name $MyInvocation.InvocationName).Parameters } else { $null } 76 | foreach ($param in $switchParameters.keys) { 77 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue; 78 | if($var) { 79 | $envVarName = $var.Name.ToLower() 80 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 81 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and $envVarValue.ToLower() -eq 'true') { # Checkbox variables 82 | $PSBoundParameters[$envVarName] = $true 83 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 84 | } elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and $envVarValue -ne 'false') { # non-Checkbox string variables 85 | $PSBoundParameters[$envVarName] = $envVarValue 86 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 87 | } 88 | } 89 | } 90 | ### END PROCESS SCRIPT VARIABLES 91 | 92 | $MapValueToDesc = @{ 93 | "0" = "Disabled" 94 | "1" = "Block" 95 | "2" = "Audit" 96 | "6" = "Warn" 97 | } 98 | $MapDescToValue = @{ 99 | "Disabled" = "0" 100 | "Block" = "1" 101 | "Audit" = "2" 102 | "Warn" = "6" 103 | } 104 | 105 | $MpStatus = Get-MpComputerStatus 106 | Write-Host "Defender Computer ID: $($MpStatus.ComputerID)" 107 | Write-Host "AntiMalware Service Enabled: $($MpStatus.AMServiceEnabled)" 108 | Write-Host "Antispyware Enabled: $($MpStatus.AntispywareEnabled) (signaures updated $($MpStatus.AntispywareSignatureLastUpdated))" 109 | Write-Host "Antivirus Enabled: $($MpStatus.AntivirusEnabled) (signatures updated $($MpStatus.AntivirusSignatureLastUpdated))" 110 | Write-Host "NOTE: Rules that are configured to 'Enable' will be displayed as being set to 'Block'" 111 | Write-Host "" 112 | 113 | if($MpStatus.AMRunningMode -eq "Passive") { 114 | Write-Host "Defender is in Passive mode and not Normal or EDR Block Mode. Quitting." 115 | exit 1 116 | } 117 | 118 | $MpPrefs = Get-MpPreference 119 | $ASR_Qty = ($MpPrefs.AttackSurfaceReductionRules_Ids.Length) - 1 120 | 121 | if($ASR_Qty -ge 0) { 122 | Write-Host "Existing ASR Rule Value(s):" 123 | for ($i = 0; $i -le $ASR_Qty; $i++) { 124 | $myId = ($MpPrefs.AttackSurfaceReductionRules_Ids)[$i] 125 | $myAction = ($MpPrefs.AttackSurfaceReductionRules_Actions)[$i] 126 | $myActionDesc = $MapValueToDesc["$myAction"] 127 | if($ASRID -eq $MyID) { 128 | Write-Host "Rule $i GUID: $(($MpPrefs.AttackSurfaceReductionRules_Ids)[$i]) is set to value $myActionDesc (RULE IN QUESTION)" 129 | } elseif($Verbose -or $ReportOnly) { 130 | Write-Host "Rule $i GUID: $(($MpPrefs.AttackSurfaceReductionRules_Ids)[$i]) is set to value $myActionDesc" 131 | 132 | } 133 | } 134 | Write-Host "" 135 | } 136 | 137 | if((Get-MpPreference).AttackSurfaceReductionRules_Ids -eq "$ASRID") { 138 | # Write-Host "Rule $ASRID is currently in the list of configured rules on this system." 139 | } else { 140 | Write-Host "Rule $ASRID is not currently in the list of configured rules on this system." 141 | } 142 | 143 | if($ReportOnly) { 144 | Write-Host "Report-only mode requested, quitting with no changes made." 145 | exit 0 146 | } 147 | # Attempt to set the rule provided to the mode specified: 148 | Write-Host "Attempting to set rule $ASRID to $Mode" 149 | Set-MpPreference -AttackSurfaceReductionRules_Ids $ASRID -AttackSurfaceReductionRules_Actions $Mode 150 | 151 | # Report outcome: 152 | if((Get-MpPreference).AttackSurfaceReductionRules_Ids -eq "$ASRID") { 153 | # Write-Host "Rule $ASRID IS in the list of configured rules." 154 | } else { 155 | Write-Host "Rule $ASRID is NOT in the list of configured rules." 156 | } 157 | Write-Host "" 158 | 159 | $MpPrefs = Get-MpPreference 160 | $ASR_Qty = ($MpPrefs.AttackSurfaceReductionRules_Ids.Length) - 1 161 | for ($i = 0; $i -le $ASR_Qty; $i++) { 162 | $myId = ($MpPrefs.AttackSurfaceReductionRules_Ids)[$i] 163 | $myAction = ($MpPrefs.AttackSurfaceReductionRules_Actions)[$i] 164 | $myActionDesc = $MapValueToDesc["$myAction"] 165 | if($ASRID -eq $myId) { 166 | Write-Host "Rule $i GUID: $(($MpPrefs.AttackSurfaceReductionRules_Ids)[$i]) is now set to value $myActionDesc" 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /Microsoft-Updates/Extend WinRE Recovery Partition KB5034441/Extend-WinRE-RecoveryPartition-CustomField-ScriptVariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/Microsoft-Updates/Extend WinRE Recovery Partition KB5034441/Extend-WinRE-RecoveryPartition-CustomField-ScriptVariables.png -------------------------------------------------------------------------------- /Microsoft-Updates/Extend WinRE Recovery Partition KB5034441/README.md: -------------------------------------------------------------------------------- 1 | # Extend-WinRE-RecoveryPartition-CustomField.ps1 2 | Unmodified Source: [https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/add-update-to-winre?view=windows-11#extend-the-windows-re-partition](https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/add-update-to-winre?view=windows-11#extend-the-windows-re-partition) 3 | 4 | To assist with resolution of failed install error status 0x80070643 for Microsoft KB5034441 5 | [https://support.microsoft.com/kb/5034441](https://support.microsoft.com/kb/5034441) 6 | 7 | **NOTE: Version 0.0.2** released on 2024-02-20 on the NinjaOne Discord which adds support for outputting to Custom Fields for later filtering. 8 | 9 | **NOTE: Version 0.0.3** released on 2024-03-21 down below at [https://github.com/dszp/NinjaOne-Scripts/tree/main/Microsoft-Updates/Extend%20WinRE%20Recovery%20Partition%20KB5034441](https://github.com/dszp/NinjaOne-Scripts/tree/main/Microsoft-Updates/Extend%20WinRE%20Recovery%20Partition%20KB5034441) which also adds some nice logging changes contributed by Olrik Lenstra (Discord user artellos)! 10 | 11 | ### MICROSOFT DESCRIPTION: 12 | The sample script below can be used to increase the size of recovery partition to enable successfully update Windows Recovery Environment (WinRE). Updates to WinRE require at minimum 250MB of free space in the 13 | recovery partition to install successfully. On devices that may not have adequate free space in the recovery partition, the sample script below can be used to extend the recovery partition. 14 | 15 | Reboot your machine before you run the script. This is critical as there may be pending partition operations staged on your machine that will need to be finalized before the script can safely increase the WinRE partition size. After your machine reboots open PowerShell as admin and run 16 | 17 | `mkdir ~~ ` 18 | 19 | to create a backup directory that the script may use in case of failure to restore the original partition. Note the location of this backup directory as the script will ask for your backup path. 20 | 21 | ## Version 0.0.1 on 2024-02-10 22 | MODIFIED/ADDED by David Szpunar on 2024-02-10 v0.0.1: 23 | 24 | Default the Backup folder to `$env:temp\ExtendWinREBackup` and create the folder if it doesn't exist. 25 | 26 | Run with the `-SkipConfirmation` switch to perform the recommended changes, otherwise READ ONLY proposed output only by default! 27 | 28 | You can create Ninja Script Variables for `SkipConfirmation` (checkbox) and `BackupFolder` (text box) and they will work (see example image below). 29 | 30 | Example output in read-only mode from a system that needs its partition expanded: 31 | 32 | ``` 33 | In write-mode, would create backup folder C:\Windows\TEMP\ExtendWinREBackup as it does not exist. 34 | Start time: 02/10/2024 21:12:57 35 | Examining the system... 36 | Windows RE status: Enabled 37 | Windows RE location: \\?\GLOBALROOT\device\harddisk1\partition4\Recovery\WindowsRE 38 | System directory: C:\Windows\system32 39 | ReAgent xml: C:\Windows\system32\Recovery\ReAgent.xml 40 | 41 | 42 | Collecting OS and WinRE partition info... 43 | OS Disk: 1 44 | OS Partition: 3 45 | WinRE Partition: 4 46 | Disk PartitionStyle: GPT 47 | WinRE partition size info 48 | Partition capacity: 532672512 49 | Partition free space: 60162048 50 | WinRE Partition Offset: 127502647296 51 | WinRE Partition Type: Recovery 52 | OS partition size: 127379963904 53 | OS partition Offset: 122683392 54 | OS partition ends at: 127502647296 55 | WinRE partition starts at: 127502647296 56 | 57 | 58 | Verifying if the WinRE partition needs to be extended or not... 59 | 60 | 61 | Summary of proposed changes 62 | Will shrink OS partition by 262144000 63 | Current OS partition size: 127379963904 64 | Target OS partition size after shrinking: 127117819904 65 | Unallocated space between OS and WinRE partition that will be used towards the new WinRE partition: 0 66 | Will extend WinRE partition size by 250MB 67 | WinRE partition: Disk [1] Partition [4] 68 | Current WinRE partition size: 532672512 69 | New WinRE partition size: 794816512 70 | WinRE will be temporarily disabled before extending the WinRE partition and enabled automatically in the end 71 | 72 | Please reboot the device before running this script to ensure any pending partition actions are finalized 73 | 74 | REPORT-ONLY mode, must rerun with -SkipConfirmation flag to proceed with changes. Quitting. 75 | ``` 76 | 77 | ## Version 0.0.2 on 2024-02-20 78 | ADDED by David Szpunar on 2024-02-20 v0.0.2: 79 | - Added custom field support to output last run full results, last-line results, or both, if custom fields are defined in CONFIG section below. 80 | - Custom Field Recommended Device Filters: 81 | - "Devices Failing MS KB5034441 WinRE Expansion - After Script" - Devices with failed OS patch where the Last Result custom field contains "REPORT-ONLY" 82 | - "Devices Failing MS KB5034441 WinRE Expansion - NEEDS SCRIPT RUN" - Devices with failed OS patch where the Last Result custom field equals "Doesn't Exist" 83 | - "Devices Failing MS KB5034441 WinRE Expansion - SUCCESS, NEEDS PATCH" - Devices with failed OS patch where the Last Result custom field contains any 84 | - of "Successfully completed the operation" or "More than 250 MB of free space was detected" and Status is "Up" (these devices you should be able to 85 | - attempt to re-apply the patch for KB5034441 and it should now succeed). 86 | - There may be other useful filters, but these are the ones I've created and used in my testing. Note that currently these filters match on any failed OS 87 | - patch, NOT just KB5034441, but you may be able to customize the filters further. 88 | 89 | ## Version 0.0.3 on 2024-03-21 90 | ADDED by David Szpunar on 2024-03-21 v0.0.3: 91 | 92 | Updated with recommended minor changes from Discord user artellos (Olrik Lenstra) from the NinjaOne Discord at: 93 | [https://discord.com/channels/676451788395642880/1206063878564352081/1217215431102697593](https://discord.com/channels/676451788395642880/1206063878564352081/1217215431102697593) 94 | 95 | and 96 | 97 | [https://discord.com/channels/676451788395642880/1206063878564352081/1217222521779261572](https://discord.com/channels/676451788395642880/1206063878564352081/1217222521779261572) 98 | 99 | These changes avoid backups if just checking and not also attempting to make changes, and adjust some logging messages to be more clear in filtering results. 100 | 101 | Search the output field for the word "RERUN" to determine which devices may need to have the script re-run to confirm they are fixed. 102 | 103 | Thanks for these changes! The full notes provided with the changes are here, describing the updates to logging outputs, primarily: 104 | 105 | ## Detailed rundown of updates made in 0.0.3 from Olrik Lenstra (artellos on Discord): 106 | 107 | Just a few pointers from me as improvements to the script that I found while testing this out myself and preparing it for "prime time" for our environment. 🙂 108 | I hope you don't mind 🫣 109 | 110 | ### Creating the Backup Folder 111 | I did a dry-run (without SkipConfirmation checked) and it still created the backup folder. On line 239/240 it starts looking at the backup folder parameter but it never checks the SkipConfirmation if it really should create that folder or not 😉 112 | 113 | My suggestion would be to change line 240 to: 114 | `if ($PSBoundParameters.ContainsKey('BackupFolder') -And $SkipConfirmation)` 115 | 116 | and change line 285 to: 117 | ``` 118 | } else { 119 | LogMessage("READ ONLY: Backup Folder will not be checked or created in read only mode.") 120 | } 121 | ``` 122 | 123 | That way it will log to the script output that it won't create the folder when the SkipConfirmation is false. 💪 124 | 125 | ### Adding some formatting 126 | Personally, I like some extra formatting in the script so that when you do have to read it back it's easier to spot the important bits. 127 | 128 | I would add another log message underneath line 384: 129 | `LogMessage("===========================")` 130 | 131 | ### No changes needed? 132 | I also ran this script on my device that has more than enough room available in the WinRE partition (500MB or something). 133 | 134 | Aside from that, my OS (Win11 23H2) doesn't need a fix because MS actually fixed it properly from Win11 22H2 and onwards 🙂 135 | 136 | My advice would be to add at least the following code after line 164: 137 | ``` 138 | # Check if the script is required to make any adjustments. 139 | 140 | if (get-hotfix -Id KB5034441 -ErrorAction Ignore ) { 141 | # Hotfix already installed and should be skipped. 142 | LogMessage("PATCHED: This system is already patched and doesn't need any action. Quitting.") 143 | exit 0 144 | } elseif ([System.Environment]::OSVersion.Version.Build -gt 22621) { 145 | # Win11 22H2 and above - These are fully automated and fixed and should not be fixed in this script. 146 | LogMessage("PATCHED: This system runs Win11 22H2 or higher and doesn't require this fix. Quitting.") 147 | exit 0 148 | } else { 149 | # No conditions detected that make this script unnecessary. 150 | LogMessage("No conditions detected that make this script unnecessary. Continuing with other checks.") 151 | } 152 | ``` 153 | 154 | ### Change Logging 155 | Perhaps it would be more "in line" with the rest of the logging if line 440 would be changed to this: 156 | `LogMessage("WRITE MODE: SkipConfirmation Parameter set to TRUE")` 157 | 158 | ## Script Variables 159 | NinjaRMM Script Variables example: 160 | ![Extend-WinRE-RecoveryPartition-CustomField-ScriptVariables](Extend-WinRE-RecoveryPartition-CustomField-ScriptVariables.png) 161 | 162 | # Extend-WinRE-RecoveryPartition-NoCustomFields.ps1 163 | This script removes the pause and instead adds the `-SkipConfirmation` flag to fully process (or read-only output if not provided) to the _original_ script from Microsoft. There are no fancy changes here per the above, just the automation, provided for reference. You an also just use the above script and comment out the Ninja custom field definitions to use the updated script without NinjaRMM! 164 | -------------------------------------------------------------------------------- /Microsoft-Updates/Patch WinRE for KB5034232 With Download/PatchWinREScript_2004plus_withdownload_KB5034232.ps1: -------------------------------------------------------------------------------- 1 | <# PatchWinREScript_2004plus_withdownload.ps1 2 | SOURCE: https://support.microsoft.com/en-us/topic/kb5034957-updating-the-winre-partition-on-deployed-devices-to-address-security-vulnerabilities-in-cve-2024-20666-0190331b-1ca3-42d8-8a55-7fc406910c10 3 | Discord Original Link: https://discord.com/channels/676451788395642880/1072879047329202258/1197244573605445642 4 | Discord link for start of download addition: https://discord.com/channels/676451788395642880/1195093276118765650/1195118075062788248 5 | 6 | About this script: This script is for Windows 10, version 2004 and later versions, including Windows 11. We recommend that you use this version of the script, because it is more robust but uses features available only on Windows 10, version 2004 and later versions. 7 | 8 | ADDITIONS v0.0.1 by David Szpunar on 2024-02-10: This is the Microsoft script, with the following changes: 9 | - Added download of the Safe OS Dynamic Update package from Microsoft to $env:temp, for Windows 10 22H2 only. Detects x86 or x64. 10 | - Added flag -Only1022H2 to exit immediately if the OS doesn't match Windows 10 22H2 exactly. 11 | - If it's not Windows 10 22H2 and you don't use -Only1022H2, you still MUST use the -packagePath parameter and provide a path to the patch. 12 | - Made the packagePath parameter optional in order to allow for the download of the package for Windows 10 22H2. 13 | - Outputs all dism logs to dism_script_kb5034232_YYYY-MM-DD-HH-MM.log in the $env:temp folder. 14 | - If it's been run before, exit code is 0 instead of 1 to indicate "succcss" even though nothing was changed. 15 | 16 | I created Script Variables for Only1022H2 (checkbox) and packagePath (text) to make running the script easier. 17 | Each of them will be used in place of script parameters if they exist and are not empty. 18 | 19 | USAGE: 20 | Parameter: workDir 21 | Specifies the scratch space used to patch WinRE. If not specified, the script will use the default temp folder for the device. 22 | Parameter: packagePath 23 | Specifies the path and name of the OS-version-specific and processor architecture-specific Safe OS Dynamic update package to be used to update the WinRE image. 24 | Note This can be a local path or a remote UNC path but the Safe OS Dynamic Update must be downloaded and available for the script to use. 25 | (Download location for Safe OS Update: https://www.catalog.update.microsoft.com/Search.aspx?q=Safe%20OS ) 26 | Example: 27 | .\PatchWinREScript_2004plus.ps1 -packagePath "\\server\share\windows10.0-kb5021043-x64_efa19d2d431c5e782a59daaf2d.cab 28 | #> 29 | ################################################################################################ 30 | # 31 | # Copyright (c) Microsoft Corporation. 32 | # Licensed under the MIT License. 33 | # 34 | # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 35 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 36 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 37 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 38 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 39 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 40 | # SOFTWARE. 41 | # 42 | ################################################################################################ 43 | Param ( 44 | [Parameter(HelpMessage = "Work Directory for patch WinRE")][string]$workDir = "", 45 | [Parameter(Mandatory = $false, HelpMessage = "Path of target package")][string]$packagePath, 46 | [Parameter(Mandatory = $false, HelpMessage = "Only try to apply KB5034232 for Windows 10 22H2, otherwise quit.")][switch]$Only1022H2 47 | ) 48 | 49 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 50 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 51 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 52 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters 53 | foreach ($param in $switchParameters.keys) { 54 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue 55 | if ($var) { 56 | $envVarName = $var.Name.ToLower() 57 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 58 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { 59 | # Checkbox variables 60 | $PSBoundParameters[$envVarName] = $true 61 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 62 | } 63 | elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { 64 | # non-Checkbox string variables 65 | $PSBoundParameters[$envVarName] = $envVarValue 66 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 67 | } 68 | } 69 | } 70 | ### END PROCESS SCRIPT VARIABLES 71 | 72 | $DISMLog = Join-Path $env:TEMP ("dism_script_kb5034232_" + (Get-Date -Format "yyyy-MM-dd-HH-mm") + ".log") 73 | 74 | $CurrentWindows = ((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion") | Select-Object -Property ProductName,DisplayVersion) 75 | # ------------------------------------ 76 | # Help functions 77 | # ------------------------------------ 78 | # Log message 79 | function LogMessage([string]$message) { 80 | $message = "$([DateTime]::Now) - $message" 81 | Write-Host $message 82 | } 83 | function IsTPMBasedProtector { 84 | $DriveLetter = $env:SystemDrive 85 | LogMessage("Checking BitLocker status") 86 | $BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$DriveLetter'" 87 | if (-not $BitLocker) { 88 | LogMessage("No BitLocker object") 89 | return $False 90 | } 91 | $protectionEnabled = $False 92 | switch ($BitLocker.GetProtectionStatus().protectionStatus) { 93 | ("0") { 94 | LogMessage("Unprotected") 95 | break 96 | } 97 | ("1") { 98 | LogMessage("Protected") 99 | $protectionEnabled = $True 100 | break 101 | } 102 | ("2") { 103 | LogMessage("Uknown") 104 | break 105 | } 106 | default { 107 | LogMessage("NoReturn") 108 | break 109 | } 110 | } 111 | if (!$protectionEnabled) { 112 | LogMessage("Bitlocker isn’t enabled on the OS") 113 | return $False 114 | } 115 | $ProtectorIds = $BitLocker.GetKeyProtectors("0").volumekeyprotectorID 116 | $return = $False 117 | foreach ($ProtectorID in $ProtectorIds) { 118 | $KeyProtectorType = $BitLocker.GetKeyProtectorType($ProtectorID).KeyProtectorType 119 | switch ($KeyProtectorType) { 120 | "1" { 121 | LogMessage("Trusted Platform Module (TPM)") 122 | $return = $True 123 | break 124 | } 125 | "4" { 126 | LogMessage("TPM And PIN") 127 | $return = $True 128 | break 129 | } 130 | "5" { 131 | LogMessage("TPM And Startup Key") 132 | $return = $True 133 | break 134 | } 135 | "6" { 136 | LogMessage("TPM And PIN And Startup Key") 137 | $return = $True 138 | break 139 | } 140 | default { break } 141 | }#endSwitch 142 | }#EndForeach 143 | if ($return) { 144 | LogMessage("Has TPM-based protector") 145 | } 146 | else { 147 | LogMessage("Doesn't have TPM-based protector") 148 | } 149 | return $return 150 | } 151 | function SetRegistrykeyForSuccess { 152 | reg add HKLM\SOFTWARE\Microsoft\PushButtonReset /v WinREPathScriptSucceed /d 1 /f 153 | } 154 | function TargetfileVersionExam([string]$mountDir) { 155 | # Exam target binary 156 | $targetBinary = $mountDir + "\Windows\System32\bootmenuux.dll" 157 | LogMessage("TargetFile: " + $targetBinary) 158 | $realNTVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($targetBinary).ProductVersion 159 | $versionString = "$($realNTVersion.Split('.')[0]).$($realNTVersion.Split('.')[1])" 160 | $fileVersion = $($realNTVersion.Split('.')[2]) 161 | $fileRevision = $($realNTVersion.Split('.')[3]) 162 | LogMessage("Target file version: " + $realNTVersion) 163 | if (!($versionString -eq "10.0")) { 164 | LogMessage("Not Windows 10 or later") 165 | return $False 166 | } 167 | $hasUpdated = $False 168 | #Windows 10, version 1507 10240.19567 169 | #Windows 10, version 1607 14393.5499 170 | #Windows 10, version 1809 17763.3646 171 | #Windows 10, version 2004 1904X.2247 172 | #Windows 11, version 21H2 22000.1215 173 | #Windows 11, version 22H2 22621.815 174 | switch ($fileVersion) { 175 | "10240" { 176 | LogMessage("Windows 10, version 1507") 177 | if ($fileRevision -ge 19567) { 178 | LogMessage("Windows 10, version 1507 with revision " + $fileRevision + " >= 19567, updates have been applied") 179 | $hasUpdated = $True 180 | } 181 | break 182 | } 183 | "14393" { 184 | LogMessage("Windows 10, version 1607") 185 | if ($fileRevision -ge 5499) { 186 | LogMessage("Windows 10, version 1607 with revision " + $fileRevision + " >= 5499, updates have been applied") 187 | $hasUpdated = $True 188 | } 189 | break 190 | } 191 | "17763" { 192 | LogMessage("Windows 10, version 1809") 193 | if ($fileRevision -ge 3646) { 194 | LogMessage("Windows 10, version 1809 with revision " + $fileRevision + " >= 3646, updates have been applied") 195 | $hasUpdated = $True 196 | } 197 | break 198 | } 199 | "19041" { 200 | LogMessage("Windows 10, version 2004") 201 | if ($fileRevision -ge 2247) { 202 | LogMessage("Windows 10, version 2004 with revision " + $fileRevision + " >= 2247, updates have been applied") 203 | $hasUpdated = $True 204 | } 205 | break 206 | } 207 | "22000" { 208 | LogMessage("Windows 11, version 21H2") 209 | if ($fileRevision -ge 1215) { 210 | LogMessage("Windows 11, version 21H2 with revision " + $fileRevision + " >= 1215, updates have been applied") 211 | $hasUpdated = $True 212 | } 213 | break 214 | } 215 | "22621" { 216 | LogMessage("Windows 11, version 22H2") 217 | if ($fileRevision -ge 815) { 218 | LogMessage("Windows 11, version 22H2 with revision " + $fileRevision + " >= 815, updates have been applied") 219 | $hasUpdated = $True 220 | } 221 | break 222 | } 223 | default { 224 | LogMessage("Warning: unsupported OS version") 225 | } 226 | } 227 | return $hasUpdated 228 | } 229 | function DownloadPatch([string]$packagePath) { 230 | $CurrentWindows = ((Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion") | Select-Object -Property ProductName,DisplayVersion) 231 | LogMessage("Windows Product Name: `t" + $CurrentWindows.ProductName) 232 | LogMessage("Windows Product Version: `t" + $CurrentWindows.DisplayVersion) 233 | LogMessage("packagePath: `t`t" + $packagePath) 234 | if([string]::IsNullOrEmpty($packagePath) -and $CurrentWindows.ProductName -like "*10*" -and $CurrentWindows.DisplayVersion -eq "22H2") { 235 | LogMessage("packagePath not specified but this is Windows 10 22H2, trying to download patch.") 236 | 237 | if([Environment]::Is64BitOperatingSystem) { 238 | LogMessage("64-bit OS detected, trying to download 64-bit patch") 239 | $packageUrl = "https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/crup/2024/01/windows10.0-kb5034232-x64_ff4651e9e031bad04f7fa645dc3dee1fe1435f38.cab" 240 | } else { 241 | LogMessage("32-bit OS detected, trying to download 32-bit patch") 242 | $packageUrl = "https://catalog.s.download.windowsupdate.com/d/msdownload/update/software/crup/2024/01/windows10.0-kb5034232-x86_3f9ddcafa903e4dd499193a851ecacbe79d842b3.cab" 243 | } 244 | 245 | $packagePath = "$env:temp\windows10.0-kb5034232-x64ff4651e9e031bad04f7fa645dc3dee1fe1435f38.cab" 246 | write-host "Start Safe OS Dynamic Update download ...." 247 | try { 248 | $wc = New-Object System.Net.WebClient 249 | $wc.DownloadFile($packageUrl, $packagePath) 250 | LogMessage("Safe OS Dynamic Update download complete, saved to $packagePath") 251 | } 252 | catch { 253 | LogMessage("Failed to download patch from $($URL)") 254 | exit 1 255 | } finally 256 | { 257 | $wc.Dispose(); 258 | } 259 | } else { 260 | LogMessage("Either packagePath not specified or this is not Windows 10 22H2 and automatic download is not supported. Quitting.") 261 | exit 1 262 | } 263 | } 264 | function PatchPackage([string]$mountDir, [string]$packagePath) { 265 | # Exam target binary 266 | $hasUpdated = TargetfileVersionExam($mountDir) 267 | if ($hasUpdated) { 268 | LogMessage("The update has already been added to WinRE") 269 | SetRegistrykeyForSuccess 270 | return $False 271 | } 272 | # Add package 273 | DownloadPatch $packagePath 274 | LogMessage("Apply package:" + $packagePath) 275 | Dism /Add-Package /Image:$mountDir /PackagePath:$packagePath /LogLevel:4 >>"$DISMLog" 276 | if ($LASTEXITCODE -eq 0) { 277 | LogMessage("Successfully applied the package") 278 | } 279 | else { 280 | LogMessage("Applying the package failed with exit code: " + $LASTEXITCODE) 281 | return $False 282 | } 283 | # Cleanup recovery image 284 | LogMessage("Cleanup image") 285 | Dism /image:$mountDir /cleanup-image /StartComponentCleanup /ResetBase /LogLevel:4 >>"$DISMLog" 286 | if ($LASTEXITCODE -eq 0) { 287 | LogMessage("Cleanup image succeed") 288 | } 289 | else { 290 | LogMessage("Cleanup image failed: " + $LASTEXITCODE) 291 | return $False 292 | } 293 | return $True 294 | } 295 | # ------------------------------------ 296 | # Execution starts 297 | # ------------------------------------ 298 | LogMessage("DISM Log File: $DISMLog") 299 | LogMessage("Windows Product Name: `t`t" + $CurrentWindows.ProductName) 300 | LogMessage("Windows Product Version: `t" + $CurrentWindows.DisplayVersion) 301 | LogMessage("packagePath: `t`t`t`t" + $packagePath) 302 | if($Only1022H2 -and !($CurrentWindows.ProductName -like "*10*" -and $CurrentWindows.DisplayVersion -eq "22H2")) { 303 | LogMessage("The 'only try to apply KB5034232 for Windows 10 22H2' flag was set, and this system DOES NOT match, quitting!") 304 | exit 0 305 | } elseif($Only1022H2) { 306 | LogMessage("The 'only try to apply KB5034232 for Windows 10 22H2' flag was set, and this system DOES match, continuing!") 307 | } 308 | # Check breadcrumb 309 | if (Test-Path HKLM:\Software\Microsoft\PushButtonReset) { 310 | $values = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset 311 | if (!(-not $values)) { 312 | if (Get-Member -InputObject $values -Name WinREPathScriptSucceed) { 313 | $value = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset -Name WinREPathScriptSucceed 314 | if ($value.WinREPathScriptSucceed -eq 1) { 315 | LogMessage("This script was previously run successfully") 316 | # exit 1 317 | exit 0 318 | } 319 | } 320 | } 321 | } 322 | if ([string]::IsNullorEmpty($workDir)) { 323 | LogMessage("No input for mount directory") 324 | LogMessage("Use default path from temporary directory") 325 | $workDir = [System.IO.Path]::GetTempPath() 326 | } 327 | LogMessage("Working Dir: " + $workDir) 328 | $name = "CA551926-299B-27A55276EC22_Mount" 329 | $mountDir = Join-Path $workDir $name 330 | LogMessage("MountDir: " + $mountdir) 331 | # Delete existing mount directory 332 | if (Test-Path $mountDir) { 333 | LogMessage("Mount directory: " + $mountDir + " already exists") 334 | LogMessage("Try to unmount it") 335 | Dism /unmount-image /mountDir:$mountDir /discard /LogLevel:4 >>"$DISMLog" 336 | if (!($LASTEXITCODE -eq 0)) { 337 | LogMessage("Warning: unmount failed: " + $LASTEXITCODE) 338 | } 339 | LogMessage("Delete existing mount directory " + $mountDir) 340 | Remove-Item $mountDir -Recurse 341 | } 342 | # Create mount directory 343 | LogMessage("Create mount directory " + $mountDir) 344 | New-Item -Path $mountDir -ItemType Directory 345 | # Set ACL for mount directory 346 | LogMessage("Set ACL for mount directory") 347 | icacls $mountDir /inheritance:r 348 | icacls $mountDir /grant:r SYSTEM:"(OI)(CI)(F)" 349 | icacls $mountDir /grant:r *S-1-5-32-544:"(OI)(CI)(F)" 350 | # Mount WinRE 351 | LogMessage("Mount WinRE:") 352 | reagentc /mountre /path $mountdir 353 | if ($LASTEXITCODE -eq 0) { 354 | # Patch WinRE 355 | if (PatchPackage -mountDir $mountDir -packagePath $packagePath) { 356 | $hasUpdated = TargetfileVersionExam($mountDir) 357 | if ($hasUpdated) { 358 | LogMessage("After patch, find expected version for target file") 359 | } 360 | else { 361 | LogMessage("Warning: After applying the patch, unexpected version found for the target file") 362 | } 363 | LogMessage("Patch succeed, unmount to commit change") 364 | Dism /unmount-image /mountDir:$mountDir /commit /LogLevel:4 >>"$DISMLog" 365 | if (!($LASTEXITCODE -eq 0)) { 366 | LogMessage("Unmount failed: " + $LASTEXITCODE) 367 | exit 1 368 | } 369 | else { 370 | if ($hasUpdated) { 371 | if (IsTPMBasedProtector) { 372 | # Disable WinRE and re-enable it to let new WinRE be trusted by BitLocker 373 | LogMessage("Disable WinRE") 374 | reagentc /disable 375 | LogMessage("Re-enable WinRE") 376 | reagentc /enable 377 | reagentc /info 378 | } 379 | # Leave a breadcrumb indicates the script has succeed 380 | SetRegistrykeyForSuccess 381 | } 382 | } 383 | } 384 | else { 385 | LogMessage("Patch failed or is not applicable, discard unmount") 386 | Dism /unmount-image /mountDir:$mountDir /discard /LogLevel:4 >>"$DISMLog" 387 | if (!($LASTEXITCODE -eq 0)) { 388 | LogMessage("Unmount failed: " + $LASTEXITCODE) 389 | exit 1 390 | } 391 | } 392 | } 393 | else { 394 | LogMessage("Mount failed: " + $LASTEXITCODE) 395 | } 396 | # Cleanup Mount directory in the end 397 | LogMessage("Delete mount direcotry") 398 | Remove-Item $mountDir -Recurse 399 | -------------------------------------------------------------------------------- /Microsoft-Updates/Patch WinRE for KB5034232 With Download/PatchWinREScript_2004plus_withdownload_KB5034232_ScriptVariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/Microsoft-Updates/Patch WinRE for KB5034232 With Download/PatchWinREScript_2004plus_withdownload_KB5034232_ScriptVariables.png -------------------------------------------------------------------------------- /Monitor-Office-365-Version/Get-OfficeUpdateInfo-Registry.ps1: -------------------------------------------------------------------------------- 1 | <# Get-OfficeUpdateInfo-Registry.ps1 2 | 2023-08-22 - 1.0.0 - Gather registry information for internal data collection in reference to ticket 303725 re: Office Updates 3 | 2023-08-23 - 1.0.1 - Update to output to a custom multi-line field only if the field name is defined; default to script output only. 4 | 5 | How Office update branches are selected: https://techcommunity.microsoft.com/t5/microsoft-365-blog/how-to-manage-office-365-proplus-channels-for-it-pros/ba-p/795813 6 | Issues setting Office update channel: https://learn.microsoft.com/en-us/answers/questions/938793/can-not-set-o365-update-channel 7 | Office update channel change details: https://learn.microsoft.com/en-us/deployoffice/updates/change-update-channels 8 | Config that forces reset on clients: https://config.office.com/officeSettings/serviceprofile 9 | #> 10 | 11 | ##### CONFIG 12 | # Custom mulit-line field name to write output to. Must have script write access. 13 | # Comment out to NOT write to custom field at all! 14 | 15 | # $customScratch = 'scratchSpaceDs' 16 | ##### END CONFIG 17 | if($null -ne $customScratch) { 18 | $customCurrentValue = Ninja-Property-Get $customScratch -ErrorAction SilentlyContinue 19 | } 20 | $customOutput = '' 21 | 22 | # Scratch info: 23 | # HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\16.0\Common 24 | # HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun 25 | # \LastScenario = UPDATE 26 | # \LastScenarioResult - Failure 27 | # \WorkstationLockState = Locked 28 | # 29 | #HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Policies 30 | 31 | function OutputCustomFields { 32 | $now = Get-Date -Format "yyyy-MM-dd HH:mm:ss" 33 | if($null -ne $customScratch -and $customScratch -ne '') { # only write to custom field if it's defined 34 | if($null -ne $customOutput) { 35 | Ninja-Property-Set $customScratch "OUTPUT $($now):`n$customOutput" 36 | } else { 37 | Ninja-Property-Set $customScratch "NOTHING NEW $($now):`n$customCurrentValue" 38 | } 39 | } 40 | 41 | Write-Host "Output at $($now):`n$customOutput" 42 | } 43 | 44 | function Get-RegistryAndProperty ([string]$key, [string]$property) { 45 | try { 46 | $value = Get-ItemPropertyValue -Path $key -Name $property -ErrorAction Stop 47 | } catch [System.Management.Automation.ItemNotFoundException] { 48 | $customOutput += "Key $key not found.`n" 49 | } catch [System.Management.Automation.PSArgumentException] { 50 | $customOutput += "Found key $key but property $property not found.`n" 51 | } 52 | return $value 53 | } 54 | 55 | $RegConfig = "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" 56 | 57 | $Channel = Get-RegistryAndProperty $RegConfig "CDNBaseUrl" 58 | $UpdateChannel = Get-RegistryAndProperty $RegConfig "UpdateChannel" 59 | $UpdateChannelChanged = Get-RegistryAndProperty $RegConfig "UpdateChannelChanged" 60 | $VersionToReport = Get-RegistryAndProperty $RegConfig "VersionToReport" 61 | $ReportedVersion = Get-RegistryAndProperty $RegConfig "ClientXnoneVersion" 62 | $UpdatesEnabled = Get-RegistryAndProperty $RegConfig "UpdatesEnabled" 63 | 64 | $RegPoliciesOfficeUpdate = "HKLM:\SOFTWARE\Policies\Microsoft\office\16.0\common\officeupdate" 65 | $SoftwarePolicies_OfficeUpdate_UpdateBranch = Get-RegistryAndProperty $RegPoliciesOfficeUpdate "UpdateBranch" 66 | 67 | try { 68 | $CloudVersionInfo = Invoke-RestMethod 'https://clients.config.office.net/releases/v1.0/OfficeReleases' -ErrorAction Stop 69 | } catch [System.Net.WebException] { 70 | $customOutput += "Unable to make web request to clients.config.office.net.`n" 71 | OutputCustomFields 72 | exit 1 73 | } 74 | 75 | $UsedChannel = $cloudVersioninfo | Where-Object { $_.OfficeVersions.cdnBaseURL -eq $channel } 76 | $UsedChannelUpdateKey = $cloudVersioninfo | Where-Object { $_.OfficeVersions.cdnBaseURL -eq $UpdateChannel } 77 | 78 | # Write the selected channel to the provided custom field: 79 | $customOutput += "Keys from $($RegConfig):`n" 80 | $customOutput += " CDNBaseUrl URL: $Channel`n" 81 | $customOutput += "UpdateChannel URL: $UpdateChannel`n" 82 | $customOutput += " Channel from CDNBaseUrl: $($UsedChannel.Channel)`n" 83 | $customOutput += "Channel from UpdateChannel: $($UsedChannelUpdateKey.Channel)`n" 84 | $customOutput += "Version from Install: $($ReportedVersion)`n" 85 | $customOutput += " Version from CDNBaseUrl Key: $($UsedChannel.latestVersion)`n" 86 | $customOutput += "Version from UpdateChannel Key: $($UsedChannelUpdateKey.latestVersion)`n" 87 | $customOutput += " End of Support Date from CDNBaseUrl Key: $($UsedChannel.endOfSupportDate)`n" 88 | $customOutput += "End of Support Date from UpdateChannel Key: $($UsedChannelUpdateKey.endOfSupportDate)`n" 89 | $customOutput += "Update Channel Changed: $($UpdateChannelChanged)`n" 90 | $customOutput += "Version to Report: $($VersionToReport)`n" 91 | $customOutput += "Updates Enabled: $($UpdatesEnabled)`n" 92 | 93 | $customOutput += "`n`nKeys from $($RegPoliciesOfficeUpdate):`n" 94 | $customOutput += "UpdateBranch: $($SoftwarePolicies_OfficeUpdate_UpdateBranch)`n" 95 | 96 | OutputCustomFields 97 | -------------------------------------------------------------------------------- /Monitor-Office-365-Version/Monitor-Office-365-Version-ScriptVariables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dszp/NinjaOne-Scripts/0f3cd17f35ae91311c6e0c14a4ced86674a61d28/Monitor-Office-365-Version/Monitor-Office-365-Version-ScriptVariables.png -------------------------------------------------------------------------------- /Monitor-Office-365-Version/Monitor-Office-365-Version.ps1: -------------------------------------------------------------------------------- 1 | <# Monitor-Office-365-Version.ps1 2 | 2023-08-14 - Updated by David Szpunar to use variable for custom field name and catch registry key not found where Office not installed. 3 | Also adds output of current channel to a second custom field. 4 | 2023-11-15 - Updated by David Szpunar to add the Reported Platform (x86 or x64) for the ClickToRun installation to the end of the version output. 5 | Also updated to check the ClientVersionToReport key instead of the ClientXnoneVersion if the latter doesn't exist. 6 | 2023-11-16 - Updated by David Szpunar to fix a logic bug in previous release. 7 | 8 | Output: The defined $customField_office365Version custom field (can be global or role-specific, if configured for the role in use) 9 | is set to the string "latest" if the latest version of Office is currently installed, or the version number otherwise. It's left 10 | blank if Office Click2Run is not installed based on detected registry key. 11 | 12 | The custom field in $customField_office365Channel is also updated with the current Office update channel configured on the system. 13 | 14 | Description of Microsoft Office update channels: https://learn.microsoft.com/en-us/deployoffice/updates/overview-update-channels 15 | List of CDNBaseUrls for Office 365 used for mapping to channel names: https://clients.config.office.net/releases/v1.0/OfficeReleases 16 | Webpage list of latest Office versions: https://learn.microsoft.com/en-us/officeupdates/update-history-microsoft365-apps-by-date 17 | (This URL must be accessible from this script for this script to work properly) 18 | 19 | SOURCE: https://discord.com/channels/676451788395642880/1072879047329202258/1140650471695061042 20 | ORIGINAL SOURCE: https://www.cyberdrain.com/automating-with-powershell-monitoring-office-releases/ 21 | Saved search in Ninja example: https://discord.com/channels/676451788395642880/1072879047329202258/1140651444714868798 22 | #> 23 | 24 | ##### CONFIG 25 | # Custom field name to write output to. Must have script write access. 26 | $customField_office365Version = 'office365Version' 27 | 28 | # Custom field name to write currently selected channel to. Must have script write access. 29 | $customField_office365Channel = 'office365Channel' 30 | ##### END CONFIG 31 | 32 | try { 33 | if(Test-Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration\ClientXnoneVersion") { 34 | $ReportedVersion = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name "ClientXnoneVersion" -ErrorAction Stop 35 | } else { 36 | $ReportedVersion = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name "ClientVersionToReport" -ErrorAction Stop 37 | } 38 | } catch [System.Management.Automation.ItemNotFoundException] { 39 | Write-Host "No registry key found, Office Click2Run likely not installed." 40 | Ninja-Property-Set $customField_office365Version $null 41 | exit 0 42 | } catch [System.Management.Automation.PSArgumentException] { 43 | Write-Host "Registry path found but property key ClientXnoneVersion was not found, Office may be installed but not the Click2Run version." 44 | Ninja-Property-Set $customField_office365Version $null 45 | } 46 | try { 47 | $Channel = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name "CDNBaseUrl" -ErrorAction Stop | Select-Object -Last 1 48 | } catch [System.Management.Automation.ItemNotFoundException] { 49 | Write-Host "ClientXnoneVersion key found but CDNBaseUrl key not found. Something is wrong or misconfigured." 50 | Ninja-Property-Set $customField_office365Version 'error' 51 | exit 1 52 | } 53 | # Gather Office Bitness 54 | $ReportedPlatform = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" -Name "Platform" -ErrorAction Continue 55 | 56 | try { 57 | $CloudVersionInfo = Invoke-RestMethod 'https://clients.config.office.net/releases/v1.0/OfficeReleases' -ErrorAction Stop 58 | } catch [System.Net.WebException] { 59 | Write-Host "Unable to make web request to clients.config.office.net. Setting custom fields blank (not saving) and quitting. Installed version of Office is reported as: $($ReportedVersion)" 60 | Ninja-Property-Set $customField_office365Version $null 61 | Ninja-Property-Set $customField_office365Channel $null 62 | exit 1 63 | } 64 | 65 | $UsedChannel = $cloudVersioninfo | Where-Object { $_.OfficeVersions.cdnBaseURL -eq $channel } 66 | 67 | # Write the selected channel to the provided custom field: 68 | Ninja-Property-Set $customField_office365Channel "$($UsedChannel.Channel)" 69 | 70 | if ($UsedChannel.latestversion -eq $ReportedVersion) { 71 | Write-Host "Currently using the latest version of Office in the $($UsedChannel.Channel) Channel: $($ReportedVersion) on the $ReportedPlatform platform." 72 | Ninja-Property-Set $customField_office365Version latest 73 | exit 0 74 | } 75 | else { 76 | Write-Host "Not using the latest version in the $($UsedChannel.Channel) Channel. Check if version is still supported" 77 | $OurVersion = $CloudVersionInfo.OfficeVersions | Where-Object -Property legacyVersion -EQ $ReportedVersion 78 | if ($OurVersion.endOfSupportDate -eq "0001-01-01T00:00:00Z") { 79 | Write-Host "This version does not yet have an end-of-support date. You are running a current version on the $ReportedPlatform platform, but not the latest. Your version is $($ReportedVersion) and the latest version is $($UsedChannel.latestVersion)" 80 | Ninja-Property-Set $customField_office365Version "$ReportedVersion $ReportedPlatform" 81 | exit 0 82 | } 83 | if ($OurVersion.endOfSupportDate) { 84 | Write-Host "This version will not be supported at $($OurVersion.endOfSupportDate). Your version is $($ReportedVersion) on the $ReportedPlatform platform and the latest version is $($UsedChannel.latestVersion)" 85 | Ninja-Property-Set $customField_office365Version "$ReportedVersion $ReportedPlatform" 86 | exit 1 87 | } 88 | else { 89 | Write-Host "Could not find version in the supported versions list. This version is most likely no longer supported. Your version is $($ReportedVersion) on the $ReportedPlatform platform and the latest version is $($UsedChannel.latestVersion)." 90 | Ninja-Property-Set $customField_office365Version "$ReportedVersion $ReportedPlatform" 91 | exit 1 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Monitor-Office-365-Version/Update-Microsoft-Office-Click2Run-Monthly-Channel.ps1: -------------------------------------------------------------------------------- 1 | <# Update-Microsoft-Office-Click2Run-Current-Channel.ps1 2 | 3 | Update the Microsoft Office Click2Run release version to remove any channel version targets and customized update settings, 4 | clearing out related registry keys so it returns to defaults, forces the CDNBaseUrl value to the default Microsoft Office 5 | update value, and starts a gentle background update (waits for user to close Office to complete update). 6 | 7 | Version 0.2.0 - 2023-08-14 - Updated by David Szpunar to clean up error reporting and loop through property removals. 8 | Version 0.1.1 - 2023-07-31 - Updated by David Szpunar to soft update call at the end, $CDNBaseUrl config section, and add this comment to original source. 9 | Version 0.1.0 - 2023-03-30 - Initial version from Discord user Anthony P (@anthonyp0129). 10 | 11 | The $CDNBaseUrl can be adjusted to the URL for whichever release cycle you want. List of CDNBaseUrl options to channels mapping is at: 12 | https://techcommunity.microsoft.com/t5/microsoft-365-blog/how-to-manage-office-365-proplus-channels-for-it-pros/ba-p/795813 13 | 14 | SOURCE: https://discord.com/channels/676451788395642880/1072879047329202258/1135121736480854066 15 | #> 16 | 17 | [CmdletBinding()] 18 | param () 19 | 20 | ##### CONFIG 21 | # Set this URL to the one for the Office release channel you wish to configure. Defaults to Current Channel. 22 | # Map of channel versions to URLs: 23 | # https://techcommunity.microsoft.com/t5/microsoft-365-blog/how-to-manage-office-365-proplus-channels-for-it-pros/ba-p/795813 24 | $CDNBaseUrl = "http://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6ddf67d60" 25 | ##### END CONFIG 26 | 27 | Function Test-RegistryValue { 28 | param( 29 | [Alias("PSPath")] 30 | [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 31 | [String]$Path 32 | , 33 | [Parameter(Position = 1, Mandatory = $true)] 34 | [String]$Name 35 | , 36 | [Switch]$PassThru 37 | ) 38 | 39 | process { 40 | if (Test-Path $Path) { 41 | $Key = Get-Item -LiteralPath $Path 42 | if ($Key.GetValue($Name, $null) -ne $null) { 43 | if ($PassThru) { 44 | Get-ItemProperty $Path $Name 45 | } else { 46 | $true 47 | } 48 | } else { 49 | $false 50 | } 51 | } else { 52 | $false 53 | } 54 | } 55 | } 56 | 57 | if ([System.Environment]::Is64BitOperatingSystem) { 58 | $C2RPaths = @( 59 | (Join-Path -Path $ENV:SystemDrive -ChildPath 'Program Files (x86)\Common Files\Microsoft Shared\ClickToRun\OfficeC2RClient.exe'), 60 | (Join-Path -Path $ENV:SystemDrive -ChildPath 'Program Files\Common Files\Microsoft Shared\ClickToRun\OfficeC2RClient.exe') 61 | ) 62 | } else { 63 | $C2RPaths = (Join-Path -Path $ENV:SystemDrive -ChildPath 'Program Files\Common Files\Microsoft Shared\ClickToRun\OfficeC2RClient.exe') 64 | } 65 | 66 | Set-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration -Name CDNBaseUrl -Value "$CDNBaseUrl" 67 | Write-Host "Updated HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\Configuration property CDNBaseUrl to $CDNBaseUrl" 68 | 69 | Write-Host "" 70 | Write-Host "Attempting to remove Click2Run related update registry keys. Failures indicate keys that already didn't exist:" 71 | 72 | $UpdateConfigPath = 'HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration' 73 | $UpdatePropertyNames = @( 74 | 'UpdateUrl', 75 | 'UpdateToVersion', 76 | 'UnmanagedUpdateUrl', 77 | 'UpdateChannel', 78 | 'UpdateToVersion' 79 | ) 80 | 81 | # Loop trough each property and attempt to remove properties if they exist, showing where they already do not: 82 | foreach ($PropertyName in $UpdatePropertyNames) { 83 | Write-Host "" # Add newline space before output for this item. 84 | $PropertyValue = Test-RegistryValue -Path "$UpdateConfigPath" -Name $PropertyName -PassThru 85 | if($PropertyValue) { 86 | Write-Host "Attempting to remove existing value '$($PropertyValue.$PropertyName)' for property $($PropertyName):" 87 | } 88 | try { 89 | Remove-ItemProperty -Path $UpdateConfigPath -Name $PropertyName -Force -Verbose -ErrorAction Stop 90 | } catch [System.Management.Automation.PSArgumentException] { 91 | Write-Host 'Nothing to remove since this path already doesn''t exist for "Item:' 92 | Write-Host "$($UpdateConfigPath): $($PropertyName)""." 93 | } catch { 94 | Write-Host "Unexpected error encountered: " $PSItem.Exception.Message 95 | } 96 | finally { 97 | $Error.Clear() 98 | } 99 | } 100 | 101 | try { 102 | Write-Host "" 103 | Remove-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Office\16.0\Common\OfficeUpdate' -Force -Verbose -ErrorAction Stop 104 | } catch [System.Management.Automation.ItemNotFoundException] { 105 | Write-Host 'Nothing to remove since this path already doesn''t exist for "Item:' 106 | Write-Host "Path HKLM:\SOFTWARE\Policies\Microsoft\Office\16.0\Common\OfficeUpdate""." 107 | } catch { 108 | Write-Host "Unexpected error encountered: " $PSItem.Exception.Message 109 | } 110 | finally { 111 | $Error.Clear() 112 | } 113 | 114 | 115 | 116 | $C2RPaths | ForEach-Object { 117 | if (Test-Path -Path $_) { 118 | $C2RPath = $_ 119 | } 120 | } 121 | if ($C2RPath) { 122 | Write-Verbose "C2RPath: $C2RPath" 123 | # Force an update immediately, closing Office if it's open: 124 | # Start-Process -FilePath $C2RPath -ArgumentList '/update user displaylevel=false forceappshutdown=true updatepromptuser=false' -Wait 125 | # Kicks off Office scheduled update task to update itself, if auto-updates are enabled, but don't prompt user, wait for natural restart to update: 126 | Start-Process -FilePath $C2RPath -ArgumentList '/frequentupdate SCHEDULEDTASK displaylevel=false updatepromptuser=false forceappshutdown=false' -Wait 127 | Write-Host "" 128 | Write-Host "Background update to Office initiated." 129 | } else { 130 | Write-Error 'No Click-to-Run Office installation detected. This script only works with Click-to-Run Office installations.' 131 | Exit 1 132 | } 133 | -------------------------------------------------------------------------------- /Monitor-Office-365-Version/Update-Office-CDNBaseUrl-From-UpdateChannel.ps1: -------------------------------------------------------------------------------- 1 | <# Update-Office-CDNBaseUrl-From-UpdateChannel.ps1 2 | 2023-08-23 - 1.0.0 - Initial script by David Szpunar to force the CDNBaseUrl registry property to match the UpdateChannel 3 | 4 | Per https://techcommunity.microsoft.com/t5/microsoft-365-blog/how-to-manage-office-365-proplus-channels-for-it-pros/ba-p/795813 5 | And in reference to ticket 303725 from Ninja, this script detects if the registry property 6 | HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration\UpdateChannel exists and is not empty, and if so, takes that value and 7 | copies it to the property HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration\CDNBaseUrl so they match. 8 | #> 9 | 10 | # Define Configuration registry key location 11 | $RegConfig = "HKLM:\SOFTWARE\Microsoft\Office\ClickToRun\Configuration" 12 | 13 | function Get-RegistryAndProperty ([string]$key, [string]$property) { 14 | try { 15 | $value = Get-ItemPropertyValue -Path $key -Name $property -ErrorAction Stop 16 | } catch [System.Management.Automation.ItemNotFoundException] { 17 | $customOutput += "Key $key not found.`n" 18 | } catch [System.Management.Automation.PSArgumentException] { 19 | $customOutput += "Found key $key but property $property not found.`n" 20 | } 21 | return $value 22 | } 23 | 24 | function Set-RegistryPropertyValue { 25 | param( 26 | [Parameter(Mandatory=$true)] 27 | [string]$key, 28 | 29 | [Parameter(Mandatory=$true)] 30 | [string]$property, 31 | 32 | [Parameter(Mandatory=$true)] 33 | $Value 34 | ) 35 | 36 | # Check if the key exists 37 | if (!(Test-Path $key)) { 38 | Write-Host "Registry key does not exist: $key (quitting, no changes made)." 39 | exit 1 40 | } 41 | 42 | # Check if the property already exists 43 | $propertyExists = $null -ne (Get-ItemProperty -Path $key -Name $property -ErrorAction SilentlyContinue) 44 | 45 | # Create the property if it doesn't exist 46 | if (!$propertyExists) { 47 | New-ItemProperty -Path $key -Name $property -Value $Value 48 | Write-Host "Created property $property in registry key $key" 49 | } else { 50 | # Set the value of the property 51 | Set-ItemProperty -Path $key -Name $property -Value $Value 52 | Write-Host "Set value of property $property in registry key $key to $Value" 53 | } 54 | } 55 | 56 | function Show-Registry-Key-Values { 57 | $global:UpdateChannel = Get-RegistryAndProperty $RegConfig "UpdateChannel" 58 | $global:CDNBaseUrl = Get-RegistryAndProperty $RegConfig "CDNBaseUrl" 59 | 60 | Write-Host "Keys from $($RegConfig):" 61 | Write-Host " CDNBaseUrl URL: $global:CDNBaseUrl" 62 | Write-Host "UpdateChannel URL: $global:UpdateChannel" 63 | } 64 | 65 | # Output current registry parameter values 66 | Show-Registry-Key-Values 67 | 68 | if($null -ne $UpdateChannel -and $UpdateChannel -ne "") { 69 | if($UpdateChannel -ne $CDNBaseUrl) { 70 | Write-Host "`nUpdating CDNBaseUrl value to match UpdateChannel value:" 71 | Set-RegistryPropertyValue $RegConfig "CDNBaseUrl" $UpdateChannel 72 | Write-Host "" 73 | # Output current registry parameter values again after changes 74 | Show-Registry-Key-Values 75 | } else { 76 | Write-Host "`nNo changes made, keys already match." 77 | } 78 | } else { 79 | Write-Host "`nNo changes made, UpdateChannel value is empty or doesn't exist." 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NinjaOne Scripts - Various useful [PowerShell](https://microsoft.com/powershell) scripts for [NinjaOne](https://ninjaone.com/) software 2 | 3 | ## Who am I? 4 | I am David Szpunar ([@dszp](https://github.com/dszp)) the owner and CEO of a managed IT services provider in the US Midwest with a past history of dabbling with scripts and programming as a support technician for over 20 years. 5 | 6 | ## What is this? 7 | Each folder is a separate [PowerShell](https://microsoft.com/powershell) script that is in some way applicable to running on devices or via the API using the [NinjaOne]() platform. Most scripts are documented through comments at the top and often require some level of configuration or parameters to use fully. Please understand the code you run, I make no claim or warranty or fitness for any particular purpose! 8 | 9 | Scripts that are intended to be run on Windows endpoints are generally written for PowerShell 5.1, while scripts that access the API are written primarily for [PowerShell 7](https://docs.microsoft.com/en-us/powershell/scripting/whats-new/what-s-new-in-powershell-71?view=powershell-7.1) and are mostly untested on earlier versions. API scripts will make use of the [NinjaOne PowerShell module](https://github.com/homotechsual/NinjaOne). 10 | 11 | ## Generic MSP Scripts 12 | Many of the scripts above are perfectly usable, or usable with very minimal modification, on nearly any Remote Monitoring & Management (RMM) tool, not just NinjaRMM from NinjaOne, but generally speaking, they are all designed to run from one. Scripts that are more generic, which may be run from an RMM but don’t have any specific RMM details, or are useful outside of an RMM context, are instead in my [MSP-Scripts](https://github.com/dszp/MSP-Scripts) repository. 13 | 14 | ## Licensing and Attribution 15 | Note that some of these scripts I’ve written, and some I’ve modified based on others either initial scripts or contributions; some may even be vendor-provided scripts with minor adjustments to make them more useful. I don’t claim any copyright over scripts or sections of scripts not written by me, and although I haven’t chosen a specific license for my stuff (I may do so), it’s freely usable and modifiable by others (attribution preferred). I’ve attempted to attribute code from others where it’s included. Most scripts are compilations of improvements to small useful projects over time and I’m not aware of any sources who wish to restrict the publication of code that they wrote or contributed to, but if there are objections to the public posting of any of these scripts from other sources, please let me know and I’ll be happy to adjust. You can reach me on Discord at DavidSzp or I’m sure you can probably find other ways to get in touch, I’m not terribly hard to find. -------------------------------------------------------------------------------- /Stairwell-Forwarder/Deploy-Stairwell-Forwarder-Windows.ps1: -------------------------------------------------------------------------------- 1 | <# Deploy-Stairwell-Forwarder-Windows-Shareable.ps1 2 | 3 | FOR NINJAONE DISCORD SHARING RELEASE - removed direct URL references to Stairwell.com site/downloads, though none of 4 | them are behind authentication I don't have direct permission to share so you'll need to obtain yourself. 5 | by David Szpunar, Servant 42, Inc. 6 | No warranty or suitability for purpose implied, use at your own risk. 7 | Various parts of this script have been collected from elsewhere and are not entirely original. 8 | 9 | Version 1.0.3 - 2024-03-18 - Update Script Variables handling code to latest version with small adjustments 10 | Version 1.0.2 - 2023-11-13 - Update Script Variables handling to new generalized parsing method 11 | Version 1.0.1 - 2023-11-10 - Add Script Variables support and -Force switch option 12 | Version 1.0.0 - 2023-08-24 - Fixes token validation from custom fields (didn't work previously). All current features 13 | working and tested across multiple Windows systems. 14 | Version 0.2.0 - 2023-08-23 - Adds working -Uninstall flag as long as downloaded version matches installed version 15 | (the removal is done by downloading the installer and passing uninstallation flags) 16 | Version 0.1.0 - 2023-08-23 - Initial deployment script for Stairwell Windows Inception Forwarder 17 | Agent and, by default, performs an initial breach assessment scan. 18 | 19 | Pass the argument -NoScan or -NoInitialScan to skip the initial full breach assessment scan and only add files as 20 | they are written, modified, or executed. 21 | #> 22 | param( 23 | [switch] $NoInitialScan, 24 | [switch] $NoScan, 25 | [switch] $Force, 26 | [switch] $Uninstall 27 | ) 28 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 29 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 30 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 31 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters; 32 | foreach ($param in $switchParameters.keys) { 33 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue; 34 | if($var) { 35 | $envVarName = $var.Name.ToLower() 36 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 37 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { # Checkbox variables 38 | $PSBoundParameters[$envVarName] = $true 39 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 40 | } elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { # non-Checkbox string variables 41 | $PSBoundParameters[$envVarName] = $envVarValue 42 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 43 | } 44 | } 45 | } 46 | ### END PROCESS SCRIPT VARIABLES 47 | 48 | ########### 49 | # EDIT ME 50 | ########### 51 | <# 52 | Create two custom fields with the below two field names, and for each Organization, add values to the two fields from the 53 | Stairwell portal before running this script. Script will fail if the custom fields don't have validly-formatted values. 54 | #> 55 | 56 | # The custom field containing the Auth Token of the Stairwell tenant 57 | # (Stairwell knowledgebase contains directions for locating this information) 58 | $customAuthToken = 'stairwellAuthToken' 59 | 60 | # The custom field containing the Environment ID of the Stairwell tenant 61 | # (Stairwell knowledgebase contains directions for locating this information) 62 | $customEnvironmentId = 'stairwellEnvironmentId' 63 | 64 | # Stairwell Download Path for Bundled Installer: 65 | # See Stairwell knoweldgebase for Download link to copy (prefer Bundle with .NET) 66 | # (URL may change for new version releases, requriing update or hosting installer elsewhere.) 67 | $DownloadURL = "" 68 | 69 | #Service Name (service to check to verify if the agent is already installed) 70 | $ServiceName = "Inception Forwarder" 71 | 72 | #Installer Name (locally saved filename for installer) 73 | $InstallerName = "StairwellInceptionForwarderBundle.exe" 74 | 75 | # Also go down and edit the $ArgumentList below, if necessary for this agent, which is set after the tokens are validated below. 76 | ########### 77 | # EDIT EDIT 78 | ########### 79 | 80 | ########### 81 | # CUSTOM FIELD CHECKS 82 | ########### 83 | 84 | if(!$Uninstall) { 85 | $AuthToken = Ninja-Property-Get $customAuthToken 86 | # Write-Host "Auth Token from $customAuthToken Custom Field: $AuthToken" 87 | if ($AuthToken -Match "^[A-Z0-9]{52}$") { 88 | write-host "AuthToken from custom field $customAuthToken passed basic format validation, continuing to install using this value." 89 | } else { 90 | write-host "No Auth Token field defined or invalid format, set valid $customAuthToken value in custom fields." 91 | write-host "Format should be: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (52 capital alphanumeric characters long)" 92 | exit 1 93 | } 94 | 95 | $EnvironmentId = Ninja-Property-Get $customEnvironmentId 96 | write-host "Environment ID from $customEnvironmentId Custom Field: $EnvironmentId" 97 | if ($EnvironmentId -Match "^[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{6}-[A-Z0-9]{8}$") { 98 | write-host "Environment ID passed basic format validation, continuing to install using this value." 99 | } else { 100 | write-host "No Environment ID field defined or invalid format, set valid $customEnvironmentId value in custom fields." 101 | write-host "Format should be: XXXXXX-XXXXXX-XXXXXX-XXXXXXXX (6-6-6-6 capital alphanumeric characters long)" 102 | exit 1 103 | } 104 | 105 | # If this script is used for other installers, this likely won't be required. It's referenced in 106 | # the Installer Argument List below; other items may be appropriate for other installers. 107 | if($NoInitialScan -or $NoScan) { 108 | $DoScan = 'DOSCAN=0' 109 | } else { 110 | $DoScan = '' 111 | } 112 | 113 | $LogFileName = (Join-Path $env:TEMP "$InstallerName").Replace(".exe", ".log") 114 | 115 | # Installer Agrument List 116 | $ArgumentList = @" 117 | TOKEN="$AuthToken" ENVIRONMENT_ID="$EnvironmentId" $DoScan /quiet /norestart /log $LogFileName 118 | "@ 119 | } 120 | 121 | ############################## 122 | # DO NOT EDIT PAST THIS POINT 123 | ############################## 124 | 125 | # Installer Location 126 | $InstallerPath = Join-Path $env:TEMP $InstallerName 127 | 128 | # Enable Debug with 1 129 | $DebugMode = 1 130 | 131 | # Failure message 132 | $Failure = "$ServiceName was not installed Successfully." 133 | 134 | function Get-TimeStamp { 135 | return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date) 136 | } 137 | 138 | #Checking if the Service is Running 139 | function Agent-Check($service) 140 | { 141 | if (Get-Service $service -ErrorAction SilentlyContinue) 142 | { 143 | return $true 144 | } 145 | return $false 146 | } 147 | 148 | # Debug Output (if enabled) 149 | function Debug-Print ($message) 150 | { 151 | if ($DebugMode -eq 1) 152 | { 153 | Write-Host "$(Get-TimeStamp) [DEBUG] $message" 154 | } 155 | } 156 | 157 | # Download installation file 158 | function Download-Installer { 159 | Debug-Print("Downloading from provided $DownloadURL...") 160 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 161 | $Client = New-Object System.Net.Webclient 162 | try 163 | { 164 | $Client.DownloadFile($DownloadURL, $InstallerPath) 165 | } 166 | catch 167 | { 168 | $ErrorMsg = $_.Exception.Message 169 | Write-Host "$(Get-TimeStamp) $ErrorMsg" 170 | } 171 | If ( ! (Test-Path $InstallerPath) ) { 172 | $DownloadError = "Failed to download the $ServiceName Installation file from $DownloadURL" 173 | Write-Host "$(Get-TimeStamp) $DownloadError" 174 | throw $Failure 175 | } 176 | Debug-Print ("Installer Downloaded to $InstallerPath...") 177 | 178 | 179 | } 180 | 181 | # Installation Routine 182 | function Install-Agent { 183 | Debug-Print ("Verifying AV did not steal exe...") 184 | If (! (Test-Path $InstallerPath)) { 185 | { 186 | $AVError = "Something, or someone, deleted the file." 187 | Write-Host "$(Get-TimeStamp) $AVError" 188 | throw $Failure 189 | } 190 | } 191 | if($NoInitialScan -or $NoScan) { 192 | Debug-Print("Skipping initial scan due to -NoScan or -NoInitialScan flag.") 193 | } else { 194 | Debug-Print("Performing initial backscan assessment after installation.") 195 | } 196 | Debug-Print ("Unpacking and Installing agent...") 197 | Start-Process -NoNewWindow -FilePath $InstallerPath -ArgumentList $ArgumentList -Wait 198 | } 199 | 200 | # Uninstallation Routine 201 | function Uninstall-Agent { 202 | Debug-Print ("Verifying AV did not steal exe...") 203 | If (! (Test-Path $InstallerPath)) { 204 | { 205 | $AVError = "Something, or someone, deleted the file." 206 | Write-Host "$(Get-TimeStamp) $AVError" 207 | throw $Failure 208 | } 209 | } 210 | 211 | # Uninstaller Agrument List 212 | $ArgumentList = @" 213 | /uninstall /quiet /norestart 214 | "@ 215 | Debug-Print ("Unpacking and Uninstalling agent from Installation File...") 216 | Start-Process -NoNewWindow -FilePath $InstallerPath -ArgumentList $ArgumentList -Wait 217 | } 218 | 219 | # Installation Routine 220 | function Install-Agent { 221 | Debug-Print ("Verifying AV did not steal exe...") 222 | If (! (Test-Path $InstallerPath)) { 223 | { 224 | $AVError = "Something, or someone, deleted the file." 225 | Write-Host "$(Get-TimeStamp) $AVError" 226 | throw $Failure 227 | } 228 | } 229 | if($NoInitialScan -or $NoScan) { 230 | Debug-Print("Skipping initial scan due to -NoScan or -NoInitialScan flag.") 231 | } else { 232 | Debug-Print("Performing initial backscan assessment after installation.") 233 | } 234 | Debug-Print ("Unpacking and Installing agent...") 235 | Start-Process -NoNewWindow -FilePath $InstallerPath -ArgumentList $ArgumentList -Wait 236 | } 237 | 238 | # Uninstallation Process Start 239 | function removeAgent { 240 | Debug-Print("Starting...") 241 | Debug-Print("Checking if $ServiceName is already installed...") 242 | If ( !$Force -and !(Agent-Check($ServiceName)) ) 243 | { 244 | $ServiceError = "$ServiceName is Not Installed, won't try to uninstall (retry with -Force flag)...Bye." 245 | Write-Host "$(Get-TimeStamp) $ServiceError" 246 | exit 0 247 | } elseif ($Force) { 248 | $ServiceError = "$ServiceName is Not Installed, but trying to remove anyway because -Force flag was used." 249 | Write-Host "$(Get-TimeStamp) $ServiceError" 250 | } 251 | Download-Installer 252 | Uninstall-Agent 253 | Debug-Print("$ServiceName Uninstall was run...cleaning up installer...") 254 | Remove-Item $InstallerPath -Force -ErrorAction SilentlyContinue 255 | Debug-Print("Attempted to delete installer.") 256 | } 257 | 258 | try 259 | { 260 | if($Uninstall) { 261 | removeAgent 262 | } else { 263 | installAgent 264 | } 265 | } 266 | catch 267 | { 268 | $ErrorMsg = $_.Exception.Message 269 | Write-Host "$(Get-TimeStamp) $ErrorMsg" 270 | exit 1 271 | } 272 | -------------------------------------------------------------------------------- /Stairwell-Forwarder/README.md: -------------------------------------------------------------------------------- 1 | # Deploy Stairwell Agent to Windows, or Uninstall 2 | This script Installs the Stairwell.com file-forwarding Inception Forwarder agent to Windows machines, including file download (the installer works from their site directly if you locate it and add to the script, from their knowledge base, though I've left direct links out of this script for now), using the Auth Token and Environment ID from a Stairwell tenant (their knowledge base also contains documentation on where to find these) stored in two Custom Fields (recommend Organization level but up to you). If the values don't match the right pattern for these values, the script will error and exit. 3 | 4 | I stored the Auth Token in a NinjaRMM **Secure Custom Field** with Script Read access and the Environment ID in a **Text Custom Field** with Script Read access. The field names are defined at the top of the script. These custom fields must have Automation Read access allowed. The format of the two tokens are validated by the script to be in the correct format. 5 | 6 | This script has been kept somewhat generic for easy modification/reuse. It defines a service name variable and the script will quit and not install if the service exists on the system already. It also confirms the service exists after the installation completes. The script attempts to delete the installer after it's finished. You will likely need to modify any token validation regular expressions if you need to supply one or more values from custom fields as well. 7 | 8 | The installer is a WiX-generated .exe file and the silent installation flags are straightforward and could be adapted in $ArgumentList to many other installers. 9 | 10 | ## Usage 11 | Update the EDIT ME section of the script to adjust any variables that are custom to you, and define the $DownloadURL variable to have the correct URL. 12 | 13 | Run without arguments to install using values provided by Custom Fields. Add the `-Force` parameter or Script Variable Checkbox to attempt the installation even if the agent service already exists on the target machine. 14 | 15 | Pass the `-Uninstall` switch to the script to trigger removal instead of installation. 16 | 17 | The Uninstall command requires the original installer from the same download, but runs it with different flags to trigger silent removal; these arguments are farther down the script and not near the top. I'm told that the installer used for removal should be the same version that's installed, but haven't had multiple versions to test. The .NET 5+ libraries are installed by the bundle (currently a .NET 6 library version is installed), required for the app, and are NOT removed by the uninstallation routine. 18 | 19 | You can pass either `-NoScan` or `-NoInitialScan` flags (they do the same thing) to the script to pass an argument that overrides the default state and prevents an initial full system scan and cataloging of all files at installation, instead just uploading executables and scripts as they are written or run going forward. -------------------------------------------------------------------------------- /UTILITIES/Script-Variables-Parameters/Convert-Script-Variables-To-Parameters.ps1: -------------------------------------------------------------------------------- 1 | <# A SNIPPET to Convert Script Variables To Parameters 2 | 3 | NOTE: This script is a snippet designed to be inserted into your own projects, and is NOT designed to be run on its own! See directions below. 4 | 5 | I prefer to write scripts that are usable from the command line and from NinjaRMM Automations, but support both parameters as well as Script Variables. The [switch] parameter type allows for a checkbox-like "true if exists" function when used with manually-entered parameters (via NinjaRMM or CLI), but Ninja's new Script Variables feature instead turns checkboxes into environment variables where the string value "true" is set if the checkbox is checked at runtime. 6 | 7 | While a simple if statement takes these into account, it requires a second definition of each switch parameter, adding room for typos and extra manual effort. This snippet (after the example parameter binding; use your own) automatically overwrites any named parameters to $true if there's a correspondingly-named lowercase (NinjaRMM converts all Script Variables to lowercase environment variables) environment variable with a string value of true, for easy re-use 8 | 9 | After the snippet runs, access the contents of your paramters as usual via variable name; if Script Variables with the same name were set, they will have overwritten the parameter variables with the Script Variable values from NinjaRMM. 10 | 11 | If a Script Variable is set to the strings 'true' or 'false', the corresponding parameter will be a boolean $true or $false value. 12 | 13 | If you do not define a parameter in the param() block with the same name as a Script Variable, it will be ignored by this snippet (you can still access it via the usual $env: method). 14 | 15 | It also resolves these issues from the original version I wrote: 16 | - The oddities of environment variable names and cases (being case-sensitive unlike most variables) 17 | - Ninja lower-casing all Script Variables names (good but need to know) 18 | - Needing to loop through the list of all possible arguments defined by the param group and (especially!) not just the ones passed in 19 | - Knowing that Script Variables use a lowercase string "true" for checkboxes rather than a $true value, to convert the value to boolean correctly 20 | - The fact that updating the parameter list value does NOT change the assigned variable name ($PSBoundParameters['SwitchName'] = $true does NOT also set $SwitchName to $true if it was false at script inception) which does make sense but is another bit of minutiae to consider 21 | 22 | This version I've used in multiple scripts since it's initial release and I haven't had to change any of it in quite some time. I haven't tested it with every type of Script Variable beyond checkboxes and text fields. 23 | 24 | Note: Script Variables are documented by NinjaOne in the following locations (must be logged in to access Dojo articles): 25 | https://ninjarmm.zendesk.com/hc/en-us/articles/17783013460621-Automation-Library-Using-Variables-in-Scripts 26 | https://ninjarmm.zendesk.com/hc/en-us/articles/17765447097357-Script-Variable-Types 27 | 28 | Version 1.0.0 - 2023-11-22 - Initially published version supporting at least Checkboxes and String/Text fields. 29 | #> 30 | 31 | # Example parameters; use your own: 32 | param ( 33 | [switch]$SwitchParam1, 34 | [switch]$SwitchParam2, 35 | [string]$NonSwitchParam 36 | ) 37 | 38 | # TO DEPLOY, ADD THE FOLLOWING TO YOUR SCRIPT, just below your param() block and before the rest of your script: 39 | 40 | 41 | 42 | 43 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED BOOLEAN SWITCH or STRING PARAMETERS 44 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 45 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 46 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters; 47 | foreach ($param in $switchParameters.keys) { 48 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue; 49 | if($var) { 50 | $envVarName = $var.Name.ToLower() 51 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 52 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { # Checkbox variables 53 | $PSBoundParameters[$envVarName] = $true 54 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 55 | } elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { # non-Checkbox string variables 56 | $PSBoundParameters[$envVarName] = $envVarValue 57 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 58 | } 59 | } 60 | } 61 | ### END PROCESS SCRIPT VARIABLES 62 | -------------------------------------------------------------------------------- /Windows-Registry-Hardening/Harden-Security-Windows-Registry.ps1: -------------------------------------------------------------------------------- 1 | <# Harden-Security-Windows-Registry.ps1 2 | This script has been moved to https://github.com/dszp/MSP-Scripts/tree/main/Windows-Registry-Hardening because it's not NinjaOne-specific and makes more sense in that general location. 3 | This location may be deleted at some point but temporarily has been updated with the pointer to redirect anyone. 4 | #> 5 | -------------------------------------------------------------------------------- /Zorus/Deploy-Zorus-Agent.ps1: -------------------------------------------------------------------------------- 1 | <# Deploy-Zorus-Agent.ps1 2 | 3 | Deploy the Zorus Archon Agent, or use -Uninstall parameter to uninstall it. Use -Force to install even if already installed. 4 | 5 | Optionally pass the install Token via -Token (parameter or Script Variable), or use custom field as defined under EDIT ME section below. 6 | 7 | The custom field can be from a Documentation template or a NinjaRMM Custom Global or Role Field, but regardless of type it requires Automation Read access. 8 | 9 | Version 0.1.0 - 2023-03-22 - by David Szpunar - Initial version 10 | Version 0.2.0 - 2024-03-07 - by David Szpunar - Update formatting, restructure to handle non-Documentation custom fields. 11 | Version 0.2.1 - 2024-03-11 - by David Szpunar - Tweak documentation field logic from last update to choose doc/custom field selection properly 12 | #> 13 | [CmdletBinding()] 14 | param( 15 | [Parameter(Mandatory = $false)][switch] $Uninstall, 16 | [Parameter(Mandatory = $false)][switch] $Force, 17 | [Parameter(Mandatory = $false)][string] $Token 18 | ) 19 | 20 | ### PROCESS NINJRAMM SCRIPT VARIABLES AND ASSIGN TO NAMED SWITCH PARAMETERS 21 | # Get all named parameters and overwrite with any matching Script Variables with value of 'true' from environment variables 22 | # Otherwise, if not a checkbox ('true' string), assign any other Script Variables provided to matching named parameters 23 | $switchParameters = (Get-Command -Name $MyInvocation.InvocationName).Parameters 24 | foreach ($param in $switchParameters.keys) { 25 | $var = Get-Variable -Name $param -ErrorAction SilentlyContinue 26 | if ($var) { 27 | $envVarName = $var.Name.ToLower() 28 | $envVarValue = [System.Environment]::GetEnvironmentVariable("$envVarName") 29 | if (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue.ToLower() -eq 'true') { 30 | # Checkbox variables 31 | $PSBoundParameters[$envVarName] = $true 32 | Set-Variable -Name "$envVarName" -Value $true -Scope Script 33 | } 34 | elseif (![string]::IsNullOrWhiteSpace($envVarValue) -and ![string]::IsNullOrEmpty($envVarValue) -and $envVarValue -ne 'false') { 35 | # non-Checkbox string variables 36 | $PSBoundParameters[$envVarName] = $envVarValue 37 | Set-Variable -Name "$envVarName" -Value $envVarValue -Scope Script 38 | } 39 | } 40 | } 41 | ### END PROCESS SCRIPT VARIABLES 42 | 43 | ########### 44 | # EDIT ME 45 | ########### 46 | 47 | #Service Name (validate if already installed) 48 | $ServiceName = "Zorus Archon" 49 | 50 | # Ninja Documentation Template Name (BLANK this to use Custom Field/Role Field rather than Documetation Custom Field!) 51 | $doc_template = 'Deployments' 52 | 53 | # Deployment Token Custom Field Name (required Automation Read access, is Custom Global or Role Field unless Documentation Template provided above) 54 | $token_field = "zorusDeploymentToken" 55 | 56 | ############################## 57 | # DO NOT EDIT PAST THIS POINT 58 | ############################## 59 | 60 | # Configure preferred TLS versions in order and disable progress bar to speed downloads. 61 | [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls" 62 | $ProgressPreference = 'SilentlyContinue' 63 | 64 | If (!$Uninstall) { 65 | if ([string]::IsNullOrWhiteSpace($Token)) { 66 | if (![string]::IsNullOrWhiteSpace($doc_template)) { 67 | $Token = Ninja-Property-Docs-Get-Single "$doc_template" "$token_field" 68 | } 69 | else { 70 | $Token = Ninja-Property-Get "$token_field" 71 | } 72 | } 73 | 74 | if ($Token.Length -lt 51 -and !$Uninstall) { 75 | Write-Host "Deployment code is too short or invalid, set valid $token_field field. " 76 | Write-Host "Format should be alphanumeric and 52 characters long" 77 | if ($doc_template) { 78 | Write-Host "(from documentation template $doc_template)" 79 | } 80 | exit 1 81 | } 82 | elseif ($Token -NotMatch "^[a-zA-Z00-9]{52}$" -and !$Uninstall) { 83 | Write-Host "No Deployment Code field defined or invalid format, set valid $token_field field. " 84 | Write-Host "Format should be alphanumeric and 52 characters long" 85 | if ($doc_template) { 86 | Write-Host "(from documentation template $doc_template)" 87 | } 88 | exit 1 89 | } 90 | else { 91 | Write-Host "Continuing to install with the provided $token_field. " 92 | Write-Host "First 10 characters of Deployment Token from Custom Field:" $Token.Substring(0, 10) 93 | if ($doc_template) { 94 | Write-Host "(from documentation template $doc_template)" 95 | } 96 | } 97 | 98 | # Orig Script: 99 | #$Token = "token here"; 100 | 101 | # Determine wether or not Archon is currently installed 102 | $IsInstalled = $false 103 | $InstalledSoftware = Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall" 104 | foreach ($obj in $InstalledSoftware) { 105 | if ($obj.GetValue('DisplayName') -match "Archon") { 106 | $IsInstalled = $true 107 | } 108 | } 109 | 110 | 111 | # If it is installed 112 | if ($IsInstalled -and !$Force) { 113 | # We skip the install routine 114 | Write-Host "Archon already installed. Skipping" 115 | 116 | } 117 | else { 118 | # If it is not installed, we do the routine we had previously in place 119 | if ($Force) { 120 | Write-Host "Archon force install requested, attempting install regardless of current state." 121 | } 122 | else { 123 | Write-Host "Archon not installed. Installing now" 124 | } 125 | 126 | $source = "http://static.zorustech.com.s3.amazonaws.com/downloads/ZorusInstaller.exe" 127 | $destination = "$env:TEMP\ZorusInstaller.exe" 128 | 129 | Write-Host "Downloading Zorus Archon Agent..." 130 | $WebClient = New-Object System.Net.WebClient 131 | $WebClient.DownloadFile($source, $destination) 132 | 133 | if (!(Test-Path -Path $destination)) { 134 | Write-Host "For some reason the download or save to $destination failed, quitting!" 135 | exit 10 136 | } 137 | 138 | If ([string]::IsNullOrEmpty($Password)) { 139 | Write-Host "Installing Zorus Archon Agent..." 140 | 141 | Start-Process -FilePath $destination -ArgumentList "/qn", "ARCHON_TOKEN=$Token" -Wait 142 | } 143 | Else { 144 | Write-Host "Installing Zorus Archon Agent with password..." 145 | Start-Process -FilePath $destination -ArgumentList "/qn", "ARCHON_TOKEN=$Token", "UNINSTALL_PASSWORD=$Password" -Wait 146 | } 147 | 148 | Write-Host "Removing Installer..." 149 | Remove-Item -Recurse $destination 150 | Write-Host "Job Complete!" 151 | } 152 | 153 | } 154 | Else { 155 | # Uninstall 156 | Write-Host "Continuing to uninstall the Zorus Archon agent. " 157 | $source = "http://static.zorustech.com.s3.amazonaws.com/downloads/ZorusAgentRemovalTool.exe" 158 | $destination = "$env:TEMP\ZorusAgentRemovalTool.exe" 159 | 160 | $WebClient = New-Object System.Net.WebClient 161 | $WebClient.DownloadFile($source, $destination) 162 | 163 | Write-Host "Downloading Zorus Agent Removal Tool..." 164 | 165 | If ([string]::IsNullOrEmpty($Password)) { 166 | Write-Host "Uninstalling Zorus Archon Agent..." 167 | Start-Process -FilePath $destination -ArgumentList "-s" -Wait 168 | } 169 | Else { 170 | Write-Host "Uninstalling Zorus Archon Agent with password..." 171 | Start-Process -FilePath $destination -ArgumentList "-s", "-p $Password" -Wait 172 | } 173 | 174 | Write-Host "Removing Uninstaller..." 175 | Remove-Item -Recurse $destination 176 | Write-Host "Job Complete!" 177 | } 178 | 179 | exit 180 | # SOURCE: https://discord.com/channels/839605716163887146/839605717580513345/1127949602310598666 181 | $tls = "Tls" 182 | [System.Net.ServicePointManager]::SecurityProtocol = $tls 183 | 184 | If ($env:Install -eq $TRUE) { 185 | $source = "http://static.zorustech.com.s3.amazonaws.com/downloads/ZorusInstaller.exe" 186 | $destination = "$env:TEMP\ZorusInstaller.exe" 187 | 188 | UNINSTALL = "YES" 189 | 190 | $WebClient = New-Object System.Net.WebClient 191 | $WebClient.DownloadFile($source, $destination) 192 | 193 | Write-Host "Downloading Zorus Archon Agent..." 194 | 195 | If ([string]::IsNullOrEmpty($env:Password)) { 196 | Write-Host "Installing Zorus Archon Agent..." 197 | Start-Process -FilePath $destination -ArgumentList "/qn", "ARCHON_TOKEN=$env:Token", "HIDE_TRAY_ICON=$trayIcon", "HIDE_ADD_REMOVE=$addRemove" -Wait 198 | } 199 | Else { 200 | Write-Host "Installing Zorus Archon Agent with password..." 201 | Start-Process -FilePath $destination -ArgumentList "/qn", "ARCHON_TOKEN=$env:Token", "UNINSTALL_PASSWORD=$env:Password", "HIDE_TRAY_ICON=$trayIcon", "HIDE_ADD_REMOVE=$addRemove" -Wait 202 | } 203 | 204 | Write-Host "Removing Installer..." 205 | Remove-Item -Recurse $destination 206 | Write-Host "Job Complete!" 207 | } 208 | Else { 209 | $source = "http://static.zorustech.com.s3.amazonaws.com/downloads/ZorusAgentRemovalTool.exe" 210 | $destination = "$env:TEMP\ZorusAgentRemovalTool.exe" 211 | 212 | $WebClient = New-Object System.Net.WebClient 213 | $WebClient.DownloadFile($source, $destination) 214 | 215 | Write-Host "Downloading Zorus Agent Removal Tool..." 216 | 217 | If ([string]::IsNullOrEmpty($env:Password)) { 218 | Write-Host "Uninstalling Zorus Archon Agent..." 219 | Start-Process -FilePath $destination -ArgumentList "-s" -Wait 220 | } 221 | Else { 222 | Write-Host "Uninstalling Zorus Archon Agent with password..." 223 | Start-Process -FilePath $destination -ArgumentList "-s", "-p $env:Password" -Wait 224 | } 225 | 226 | Write-Host "Removing Uninstaller..." 227 | Remove-Item -Recurse $destination 228 | Write-Host "Job Complete!" 229 | } 230 | --------------------------------------------------------------------------------