├── .prettierignore ├── .vscode └── launch.json ├── Bulk Change PrinterDriverAttributes for non Package-Aware printer drivers ├── ChangePrinterDriverAttributes.ps1 ├── do_you_trust_this_printer.png └── readme.md ├── Change Lock Screen and Desktop Background in Windows 10 Pro ├── Set-LockScreen.ps1 └── readme.md ├── Defrag Windows Search Database ├── Defrag-WinSearchDB.ps1 ├── readme.md └── screenshot.png ├── Delete Exchange Online Emails Older Than x Months └── Remove-ExchangeOnlineEmails.ps1 ├── Delete Windows 10 Preinstalled Apps └── readme.md ├── Delete reappearing printers that keeps comming back ├── DeletePrintersRDS.cmd ├── printers.png └── readme.md ├── Email Report of File Permissions on HTML and CSV ├── Get-FolderPermissions.ps1 ├── readme.md └── screenshot.png ├── File Server Access Audit Report with PowerShell ├── 1.PNG ├── 2.PNG ├── 5.PNG ├── 6.PNG ├── 7.png ├── Get-AuditReport.ps1 └── readme.md ├── GUI Password Reset Tool for Active Directory ├── GUI.PNG ├── Set-ADPassword.ps1 └── readme.md ├── Install JRE ├── Download-JRE.ps1 ├── Install-JRE.ps1 └── readme.md ├── Install LibreOffice ├── Install-LibreOffice.ps1 └── readme.md ├── Install MSI ├── Install-MSI.ps1 └── readme.md ├── Install Print Drivers Remotely ├── 1.PNG ├── 2.PNG ├── 3.png ├── Install-PrinterDriversRemotely.ps1 └── readme.md ├── Install Software Remotely ├── Install-SoftwareRemotely.ps1 ├── Screenshot.png ├── Screenshot2.png └── readme.md ├── Install Software ├── Install-Software.ps1 ├── locally.png ├── psexec.png └── readme.md ├── LICENSE ├── Optimize and cleanup of WSUS on Windows Server 2012 R2 and 2016 ├── Optimize-WSUS.ps1 ├── WsusDBMaintenance.sql └── readme.md ├── Optimize drives ├── Invoke-DiskDefrag.ps1 ├── Invoke-DiskOptimize.ps1 └── readme.md ├── Password Encryption for PowerShell scripts ├── New-StringDecryption.ps1 ├── New-StringEncription.ps1 └── readme.md ├── RZGet launcher for Intune └── Install-Software.ps1 ├── Redirect Folder └── Redirect-Folder.ps1 ├── Remote Computer Update ├── Launch-Remote.ps1 ├── LaunchRemote.cmd ├── Update-Computer.ps1 ├── readme.md └── screenshot.png ├── SQL Server Backup ├── BackupSQL.ps1 └── readme.md ├── System Center DPM 2012 (R2) HTML Report ├── DPMReport.ps1 ├── readme.md └── report_screenshot.PNG ├── Take ownership of folder └── Take-Own.ps1 ├── Windows Mainteinance ├── Win-Mnt.ps1 ├── clamav-0.104.0.win.x64.zip └── readme.md ├── Windows Server Backup Email Report of Several Servers ├── Get-WSBReport.ps1 ├── Servers.txt ├── readme.md └── wsb.PNG └── readme.md /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore all md files: 2 | *.md -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Remove-ExchangeOnlineEmails Script", 6 | "type": "PowerShell", 7 | "request": "launch", 8 | "script": "${workspaceFolder}\\Delete Exchange Online Emails Older Than x Months\\Remove-ExchangeOnlineEmails.ps1", 9 | "cwd": "${workspaceFolder}", 10 | "args": [ 11 | "-target \"soporte@3digits.es\" -months 13 -deleteComplianceSearch" 12 | ] 13 | }, 14 | { 15 | "name": "Launch Install-Software Script", 16 | "type": "PowerShell", 17 | "request": "launch", 18 | "script": "${workspaceFolder}\\Install Software\\Install-Software.ps1", 19 | "cwd": "${workspaceFolder}", 20 | "args": ["-software '7-Zip','Notepad++','Edge','Teams','Postman'"] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /Bulk Change PrinterDriverAttributes for non Package-Aware printer drivers/ChangePrinterDriverAttributes.ps1: -------------------------------------------------------------------------------- 1 | Function Set-PrinterDriverAttributes([string]$RegistryPath){ 2 | $Changes = 0 3 | Write-Host "Checking $($RegistryPath)" 4 | $Printers = Get-ChildItem -Path "$RegistryPath" -Recurse 5 | ForEach ($Printer in $Printers){ 6 | $PrinterDriverAttributes = $Printer.GetValue("PrinterDriverAttributes") 7 | If($PrinterDriverAttributes % 2 -eq 0){ 8 | Write-Host "Printer driver $($Printer) has PrinterDriverAttributes value of $($PrinterDriverAttributes)" -ForegroundColor Yellow 9 | Write-Host "Changing PrinterDriverAttributes to $($PrinterDriverAttributes + 1)" -ForegroundColor Yellow 10 | try{ 11 | New-ItemProperty -Path $Printer.PSPath -Name PrinterDriverAttributes -PropertyType DWord -Value $($PrinterDriverAttributes + 1) -Force -ErrorAction Continue | Out-Null 12 | $Changes++ 13 | } catch { 14 | Write-Host "Error changing registry key" -ForegroundColor Red 15 | Write-Host "Excepcion: $($_.Exception.GetType().FullName)" -ForegroundColor Red 16 | Write-Host "Error: $($_.Exception.Message)" -ForegroundColor Red 17 | } 18 | } 19 | Else{ 20 | Write-Host "Printer driver $($Printer) has PrinterDriverAttributes value of $($PrinterDriverAttributes)" -ForegroundColor DarkCyan 21 | } 22 | } 23 | Return $Changes 24 | } 25 | 26 | $ErrorActionPreference = "Stop" 27 | 28 | If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(` 29 |     [Security.Principal.WindowsBuiltInRole] "Administrator")) 30 | { 31 |     Write-Error "You need to run script as administrador" 32 |     Exit(1) 33 | } 34 | $DriversChanged = Set-PrinterDriverAttributes "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows NT x86\Drivers\Version-3" 35 | $DriversChanged += Set-PrinterDriverAttributes "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows x64\Drivers\Version-3" 36 | If ($DriversChanged){ 37 | Write-Host "$($DriversChanged) registry keys changed." -ForegroundColor Green 38 | Write-Host "Restarting Spooler." -ForegroundColor Yellow 39 | Restart-Service Spooler 40 | } 41 | Else{ 42 | Write-Host "All PrinterDriverAttributes registry keys OK." -ForegroundColor Green 43 | } -------------------------------------------------------------------------------- /Bulk Change PrinterDriverAttributes for non Package-Aware printer drivers/do_you_trust_this_printer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Bulk Change PrinterDriverAttributes for non Package-Aware printer drivers/do_you_trust_this_printer.png -------------------------------------------------------------------------------- /Bulk Change PrinterDriverAttributes for non Package-Aware printer drivers/readme.md: -------------------------------------------------------------------------------- 1 | # Bulk Change PrinterDriverAttributes for non Package-Aware printer drivers 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Bulk%20Change%20PrinterDriverAttributes%20for%20non%20Package-Aware%20printer%20drivers/ChangePrinterDriverAttributes.ps1) 4 | 5 |

6 | Warning message Do you trust this printer 7 |

8 | 9 | Since [KB3170455](https://support.microsoft.com/en-us/topic/ms16-087-security-update-for-windows-print-spooler-components-july-12-2016-afceb380-914b-500f-5aa2-904fe6d13817), deployed Printers via Group Policy does not install non Package-Aware printer drivers automatically. Users are prompted with a message saying "Do yo trust this printer?" 10 | 11 | More info and manual fix: [Group Policy Printer Issue – Print and Point Restrictions – KB3170455](https://www.richardwalz.com/group-policy-printer-issue-print-and-point-restrictions-kb3170455/) 12 | 13 | You have to install printer driver manually in each computer. But, for many old drivers, you can force GPO driver installation changing PrinterDriverAttributes registry value to a odd number in print server. 14 | 15 | This script, scan registry and changes all even PrinterDriverAttributes to allow driver installation by GPO. You have to restart print server spooler after running it. 16 | 17 | -------------------------------------------------------------------------------- /Change Lock Screen and Desktop Background in Windows 10 Pro/Set-LockScreen.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.0 4 | 5 | .GUID 9320af4f-61f1-4e65-9579-62e6e14e3d4f 6 | 7 | .AUTHOR Juan Granados 8 | 9 | .COPYRIGHT 2021 Juan Granados 10 | 11 | .TAGS Lock Screen Windows 10 Pro GPO 12 | 13 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 14 | 15 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Change%20Lock%20Screen%20and%20Desktop%20Background%20in%20Windows%2010%20Pro 16 | 17 | .RELEASENOTES 18 | Change Lock Screen and Desktop Background in Windows 10 Pro. 19 | #> 20 | 21 | <# 22 | .SYNOPSIS 23 | Change Lock Screen and Desktop Background in Windows 10 Pro. 24 | .DESCRIPTION 25 | This script allows you to change logon screen and desktop background in Windows 10 Professional using GPO startup script. 26 | .PARAMETER LockScreenSource (Optional) 27 | Path to the Lock Screen image to copy locally in computer. 28 | Example: "\\SERVER-FS01\LockScreen.jpg" 29 | .PARAMETER BackgroundSource (Optional) 30 | Path to the Desktop Background image to copy locally in computer. 31 | Example: "\\SERVER-FS01\BackgroundScreen.jpg" 32 | .PARAMETER LogPath (Optional) 33 | Path where save log file. If it's not specified no log is recorded. 34 | .EXAMPLE 35 | Set Lock Screen and Desktop Wallpaper with logs: 36 | Set-LockScreen -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 37 | .EXAMPLE 38 | Set Lock Screen and Desktop Wallpaper without logs: 39 | Set-LockScreen -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" 40 | .EXAMPLE 41 | Set Lock Screen only: 42 | Set-LockScreen -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 43 | .EXAMPLE 44 | Set Desktop Wallpaper only: 45 | Set-LockScreen -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 46 | .NOTES 47 | Author: Juan Granados 48 | #> 49 | Param( 50 | [Parameter(Mandatory=$false,Position=0)] 51 | [ValidateNotNullOrEmpty()] 52 | [string]$LockScreenSource, 53 | [Parameter(Mandatory=$false,Position=1)] 54 | [ValidateNotNullOrEmpty()] 55 | [string]$BackgroundSource, 56 | [Parameter(Mandatory=$false,Position=2)] 57 | [ValidateNotNullOrEmpty()] 58 | [string]$LogPath 59 | ) 60 | 61 | #Requires -RunAsAdministrator 62 | 63 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { 64 | Start-Transcript -Path "$($LogPath)\$($env:COMPUTERNAME).log" | Out-Null 65 | } 66 | 67 | $ErrorActionPreference = "Stop" 68 | 69 | $RegKeyPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP" 70 | 71 | $DesktopPath = "DesktopImagePath" 72 | $DesktopStatus = "DesktopImageStatus" 73 | $DesktopUrl = "DesktopImageUrl" 74 | $LockScreenPath = "LockScreenImagePath" 75 | $LockScreenStatus = "LockScreenImageStatus" 76 | $LockScreenUrl = "LockScreenImageUrl" 77 | 78 | $StatusValue = "1" 79 | $DesktopImageValue = "C:\Windows\System32\Desktop.jpg" 80 | $LockScreenImageValue = "C:\Windows\System32\LockScreen.jpg" 81 | 82 | if (!$LockScreenSource -and !$BackgroundSource) 83 | { 84 | Write-Host "Either LockScreenSource or BackgroundSource must has a value." 85 | } 86 | else 87 | { 88 | if(!(Test-Path $RegKeyPath)) { 89 | Write-Host "Creating registry path $($RegKeyPath)." 90 | New-Item -Path $RegKeyPath -Force | Out-Null 91 | } 92 | if ($LockScreenSource) { 93 | Write-Host "Copy Lock Screen image from $($LockScreenSource) to $($LockScreenImageValue)." 94 | Copy-Item $LockScreenSource $LockScreenImageValue -Force 95 | Write-Host "Creating registry entries for Lock Screen" 96 | New-ItemProperty -Path $RegKeyPath -Name $LockScreenStatus -Value $StatusValue -PropertyType DWORD -Force | Out-Null 97 | New-ItemProperty -Path $RegKeyPath -Name $LockScreenPath -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null 98 | New-ItemProperty -Path $RegKeyPath -Name $LockScreenUrl -Value $LockScreenImageValue -PropertyType STRING -Force | Out-Null 99 | } 100 | if ($BackgroundSource) { 101 | Write-Host "Copy Desktop Background image from $($BackgroundSource) to $($DesktopImageValue)." 102 | Copy-Item $BackgroundSource $DesktopImageValue -Force 103 | Write-Host "Creating registry entries for Desktop Background" 104 | New-ItemProperty -Path $RegKeyPath -Name $DesktopStatus -Value $StatusValue -PropertyType DWORD -Force | Out-Null 105 | New-ItemProperty -Path $RegKeyPath -Name $DesktopPath -Value $DesktopImageValue -PropertyType STRING -Force | Out-Null 106 | New-ItemProperty -Path $RegKeyPath -Name $DesktopUrl -Value $DesktopImageValue -PropertyType STRING -Force | Out-Null 107 | } 108 | } 109 | 110 | if (-not [string]::IsNullOrWhiteSpace($LogPath)){Stop-Transcript} 111 | -------------------------------------------------------------------------------- /Change Lock Screen and Desktop Background in Windows 10 Pro/readme.md: -------------------------------------------------------------------------------- 1 | # **Change Lock Screen and Desktop Background in Windows 10 Pro** 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Change%20Lock%20Screen%20and%20Desktop%20Background%20in%20Windows%2010%20Pro/Set-LockScreen.ps1) 4 | 5 | This script allows you to change login screen and desktop background in Windows 10 Professional using GPO startup script. 6 | 7 | By default, lock screen can not be changed by GPO in Windows 10 Professional, with this script you can change it to comply with corporate image. 8 | 9 | Create a GPO to run this PowerShell script. 10 | 11 | Examples: 12 | 13 | Set Lock Screen and Desktop Wallpaper with logs: 14 | 15 | ```powershell 16 | .\Set-LockScreen.ps1 -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 17 | ``` 18 | 19 | Set Lock Screen and Desktop Wallpaper without logs: 20 | 21 | ```powershell 22 | .\Set-LockScreen.ps1 -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" 23 | ``` 24 | 25 | Set Lock Screen only: 26 | 27 | ```powershell 28 | .\Set-LockScreen.ps1 -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 29 | ``` 30 | 31 | Set Desktop Wallpaper only: 32 | 33 | ```powershell 34 | .\Set-LockScreen.ps1 -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 35 | ``` 36 | 37 | ```powershell 38 | <# 39 | .SYNOPSIS 40 | Change Lock Screen and Desktop Background in Windows 10 Pro. 41 | .DESCRIPTION 42 | This script allows you to change logon screen and desktop background in Windows 10 Professional using GPO startup script. 43 | .PARAMETER LockScreenSource (Optional) 44 | Path to the Lock Screen image to copy locally in computer. 45 | Example: "\\SERVER-FS01\LockScreen.jpg" 46 | .PARAMETER BackgroundSource (Optional) 47 | Path to the Desktop Background image to copy locally in computer. 48 | Example: "\\SERVER-FS01\BackgroundScreen.jpg" 49 | .PARAMETER LogPath (Optional) 50 | Path where save log file. If it's not specified no log is recorded. 51 | .EXAMPLE 52 | Set Lock Screen and Desktop Wallpaper with logs: 53 | Set-Loca Screen -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 54 | .EXAMPLE 55 | Set Lock Screen and Desktop Wallpaper without logs: 56 | Set-LockScreen -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" 57 | .EXAMPLE 58 | Set Lock Screen only: 59 | .\Set-Screen.ps1 -LockScreenSource "\\SERVER-FS01\LockScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 60 | .EXAMPLE 61 | Set Desktop Wallpaper only: 62 | .\Set-LockScreen.ps1 -BackgroundSource "\\SERVER-FS01\BackgroundScreen.jpg" -LogPath "\\SERVER-FS01\Logs" 63 | .NOTES 64 | Author: Juan Granados 65 | #> 66 | ``` 67 | -------------------------------------------------------------------------------- /Defrag Windows Search Database/Defrag-WinSearchDB.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.0 4 | 5 | .GUID c478611d-d1c2-4063-8f40-fa67874a3711 6 | 7 | .AUTHOR Juan Granados 8 | 9 | .COPYRIGHT 2021 Juan Granados 10 | 11 | .TAGS WSUS Windows Update Remote Software 12 | 13 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 14 | 15 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Defrag%20Windows%20Search%20Database 16 | 17 | .RELEASENOTES Initial release 18 | 19 | #> 20 | 21 | <# 22 | .SYNOPSIS 23 | Defrag Windows Search Database. 24 | .DESCRIPTION 25 | Defrag Windows Search Database and optionally deletes it after error. 26 | .PARAMETER DataBase 27 | Windows Search Database path. 28 | Default C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb 29 | .PARAMETER TempPath 30 | Temporary folder to perform defrag. Using a different physical drive is recommended. 31 | Default: C:\ProgramData\Microsoft\Search\Data\Applications\Windows 32 | .PARAMETER LogPath 33 | Log file path. 34 | Default "Documents" 35 | .PARAMETER DeleteOnError 36 | Deletes Windows Search Database and modify registry to rebuild it at next Search Service startup. 37 | Default false. 38 | .EXAMPLE 39 | Defrag-WinSearchDB -LogPath "\\ES-CPD-BCK02\Log" -TempPath \\ES-CPD-BCK02\Temp 40 | .EXAMPLE 41 | Defrag-WinSearchDB -TempPath "D:\Temp" 42 | .LINK 43 | https://github.com/juangranados/powershell-scripts/tree/main/Defrag%20Windows%20Search%20Database 44 | .NOTES 45 | Author: Juan Granados 46 | #> 47 | Param( 48 | [Parameter()] 49 | [ValidateNotNullOrEmpty()] 50 | [string]$DataBase = "$($env:ProgramData)\Microsoft\Search\Data\Applications\Windows\Windows.edb", 51 | [Parameter()] 52 | [ValidateNotNullOrEmpty()] 53 | [string]$LogPath = [Environment]::GetFolderPath("MyDocuments"), 54 | [Parameter()] 55 | [ValidateNotNullOrEmpty()] 56 | [string]$TempPath = "$($env:ProgramData)\Microsoft\Search\Data\Applications\Windows", 57 | [Parameter()] 58 | [switch]$DeleteOnError 59 | ) 60 | ## ------------------------------------------------------------------ 61 | # function Invoke-Process 62 | # https://stackoverflow.com/a/66700583 63 | ## ------------------------------------------------------------------ 64 | function Invoke-Process { 65 | param 66 | ( 67 | [Parameter(Mandatory)] 68 | [ValidateNotNullOrEmpty()] 69 | [string]$FilePath, 70 | 71 | [Parameter()] 72 | [ValidateNotNullOrEmpty()] 73 | [string]$ArgumentList, 74 | 75 | [ValidateSet("Full", "StdOut", "StdErr", "ExitCode", "None")] 76 | [string]$DisplayLevel 77 | ) 78 | 79 | $ErrorActionPreference = 'Stop' 80 | 81 | try { 82 | $pinfo = New-Object System.Diagnostics.ProcessStartInfo 83 | $pinfo.FileName = $FilePath 84 | $pinfo.RedirectStandardError = $true 85 | $pinfo.RedirectStandardOutput = $true 86 | $pinfo.UseShellExecute = $false 87 | $pinfo.WindowStyle = 'Hidden' 88 | $pinfo.CreateNoWindow = $true 89 | $pinfo.Arguments = $ArgumentList 90 | $p = New-Object System.Diagnostics.Process 91 | $p.StartInfo = $pinfo 92 | $p.Start() | Out-Null 93 | $result = [pscustomobject]@{ 94 | Title = ($MyInvocation.MyCommand).Name 95 | Command = $FilePath 96 | Arguments = $ArgumentList 97 | StdOut = $p.StandardOutput.ReadToEnd() 98 | StdErr = $p.StandardError.ReadToEnd() 99 | ExitCode = $p.ExitCode 100 | } 101 | $p.WaitForExit() 102 | 103 | if (-not([string]::IsNullOrEmpty($DisplayLevel))) { 104 | switch ($DisplayLevel) { 105 | "Full" { return $result; break } 106 | "StdOut" { return $result.StdOut; break } 107 | "StdErr" { return $result.StdErr; break } 108 | "ExitCode" { return $result.ExitCode; break } 109 | } 110 | } 111 | } 112 | catch { 113 | Write-Host "An error has ocurred" 114 | } 115 | } 116 | $ErrorActionPreference = "SilentlyContinue" 117 | Stop-Transcript | out-null 118 | $ErrorActionPreference = "Continue" 119 | 120 | $LogPath = $LogPath.TrimEnd('\') 121 | if (-not (Test-Path $LogPath)) { 122 | Write-Host "Log path $($LogPath) not found" 123 | Exit (1) 124 | } 125 | 126 | Start-Transcript -path "$($LogPath)\$(get-date -Format yyyy_MM_dd)_$($env:COMPUTERNAME).txt" 127 | 128 | if (-not (Test-Path $DataBase) -or (($($DataBase.Substring($DataBase.Length - 4)) -ne ".edb"))) { 129 | Write-Host "Windows Search Database not found on $($DataBase)" 130 | Stop-Transcript 131 | Exit (1) 132 | } 133 | 134 | $TempPath = $TempPath.TrimEnd('\') 135 | if (-not (Test-Path $TempPath)) { 136 | Write-Host "Temp path $($TempPath) not found" 137 | Exit (1) 138 | } 139 | 140 | $TempDataBase = $TempPath + "\tempdfrg_$(Get-Random).edb" 141 | 142 | Write-Host "Disabling Windows Search" 143 | Set-Service -Name 'wsearch' -StartupType 'Disabled' 144 | Stop-Service wsearch -Force 145 | Get-Service wsearch 146 | Write-Host "Perform defrag on $($DataBase)" 147 | $defragResult = Invoke-Process -FilePath "$([System.Environment]::SystemDirectory)\esentutl.exe" -ArgumentList "/d $($DataBase) /t $($TempDataBase)" -DisplayLevel Full 148 | if ($defragResult.ExitCode -ne 0) { 149 | Write-Host "An error has ocurred: $($defragResult.ExitCode)" 150 | $defragResult.StdOut 151 | if ($DeleteOnError) { 152 | Write-Host "Deleting database $($DataBase)" 153 | Remove-Item $DataBase -Force 154 | New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows Search" -Name 'SetupCompletedSuccessfully' -Value 0 -PropertyType "DWord" -Force 155 | } 156 | } 157 | else { 158 | $defragResult.StdOut 159 | } 160 | Write-Host "Enabling Windows Search" 161 | Invoke-Process -FilePath "$([System.Environment]::SystemDirectory)\sc.exe" -ArgumentLis "config wsearch start=delayed-auto" -DisplayLevel "StdOut" 162 | Start-Service wsearch 163 | Get-Service wsearch 164 | Stop-Transcript -------------------------------------------------------------------------------- /Defrag Windows Search Database/readme.md: -------------------------------------------------------------------------------- 1 | # Defrag Windows Search Database 2 | 3 | [Right click here and select "Save link as" to download](https://github.com/juangranados/powershell-scripts/tree/main/Defrag%20Windows%20Search%20Database/DefragWinSearchDB.ps1) 4 | 5 | Script to Defrag Windows Search Database and optionally deletes it after error. 6 | 7 | ![Screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Defrag%20Windows%20Search%20Database/screenshot.png) 8 | 9 | ## Parameters 10 | 11 | ### DataBase 12 | 13 | Windows Search Database path. 14 | 15 | Default C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb 16 | 17 | ### TempPath 18 | 19 | Temporary folder to perform defrag. Using a different physical drive is recommended. 20 | 21 | Default: C:\ProgramData\Microsoft\Search\Data\Applications\Windows 22 | 23 | ### LogPath 24 | 25 | Log file path. 26 | 27 | Default: Documents. 28 | 29 | Example: "\\ES-CPD-BCK02\SearchDefrag\Log" 30 | 31 | ### DeleteOnError 32 | 33 | Deletes Windows Search Database and modify registry to rebuild it at next Search Service startup. 34 | 35 | Default: false 36 | 37 | ## Example 38 | ```powershell 39 | Defrag-WinSearchDB -LogPath "\\ES-CPD-BCK02\DefragWindDB\Log" -TempPath "\\ES-CPD-BCK02\DefragWindDB\Temp" 40 | ``` 41 | 42 | ```powershell 43 | Defrag-WinSearchDB -TempPath "D:\Temp" 44 | ``` -------------------------------------------------------------------------------- /Defrag Windows Search Database/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Defrag Windows Search Database/screenshot.png -------------------------------------------------------------------------------- /Delete Exchange Online Emails Older Than x Months/Remove-ExchangeOnlineEmails.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.0 4 | 5 | .GUID 5d639597-a35a-4a95-8010-042fb21c343f 6 | 7 | .AUTHOR Juan Granados 8 | 9 | .COPYRIGHT 2022 Juan Granados 10 | 11 | .TAGS Exchange Online Remove Messages 12 | 13 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 14 | 15 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Delete%20Exchange%20Online%20Emails%20Older%20Than%20x%20Months 16 | 17 | .RELEASENOTES Initial release 18 | 19 | #> 20 | 21 | <# 22 | .SYNOPSIS 23 | Delete Exchange Online emails older than x months from a mailbox. 24 | .DESCRIPTION 25 | Delete Exchange Online emails older than x months from a mailbox. 26 | Running user has to be member of eDiscovery Manager group: https://docs.microsoft.com/en-us/microsoft-365/compliance/assign-ediscovery-permissions?view=o365-worldwide 27 | in order to run New-ComplianceSearchAction. 28 | Thanks to: https://answers.microsoft.com/en-us/msoffice/forum/all/delete-more-than-10-items-from-a-mailbox-using/f28efa60-3766-4f50-af2d-e1f9be588931 29 | .PARAMETER target 30 | Mailboxto delete emails from. 31 | .PARAMETER months 32 | Emails older than this number of months will be deleted 33 | .PARAMETER logPath 34 | Path where save log file. 35 | Default: Temp folder 36 | .PARAMETER preview 37 | Only shows matching emails. It will not delete anything. 38 | .PARAMETER deleteComplianceSearch 39 | Deletes Compliance Search object after using it. 40 | .EXAMPLE 41 | Remove-ExchangeOnlineEmails.ps1 -LogPath "C:\temp\Log" -target esmith@contoso.com -months 12 -preview 42 | .EXAMPLE 43 | Remove-ExchangeOnlineEmails.ps1 -target finance@contoso.com -months 24 -deleteComplianceSearch 44 | .LINK 45 | https://github.com/juangranados/powershell-scripts/tree/main/Delete%20Exchange%20Online%20Emails%20Older%20Than%20x%20Months 46 | .NOTES 47 | Author: Juan Granados 48 | #> 49 | Param( 50 | [Parameter(Mandatory = $true)] 51 | [ValidateNotNullOrEmpty()] 52 | [string]$target, 53 | [Parameter(Mandatory = $true)] 54 | [ValidateNotNullOrEmpty()] 55 | [ValidateRange(0, 100)] 56 | [int]$months, 57 | [Parameter(Mandatory = $false)] 58 | [ValidateNotNullOrEmpty()] 59 | [string]$logPath = $env:temp, 60 | [Parameter()] 61 | [switch]$preview, 62 | [Parameter()] 63 | [switch]$deleteComplianceSearch 64 | ) 65 | 66 | $ErrorActionPreference = "SilentlyContinue" 67 | Stop-Transcript | out-null 68 | $ErrorActionPreference = "Stop" 69 | 70 | function Get-StringBetweenTwoStrings([string]$firstString, [string]$secondString, [string]$string) { 71 | $pattern = "$firstString(.*?)$secondString" 72 | $result = [regex]::Match($string, $pattern).Groups[1].Value 73 | return $result 74 | } 75 | function Get-ParsedLog([string]$log) { 76 | $log = $log -replace '{' -replace '}', ',' 77 | $table = New-Object system.Data.DataTable "DetailedMessageStats" 78 | $table.columns.add($(New-Object system.Data.DataColumn Location, ([string]))) 79 | $table.columns.add($(New-Object system.Data.DataColumn Sender, ([string]))) 80 | $table.columns.add($(New-Object system.Data.DataColumn Subject, ([string]))) 81 | $table.columns.add($(New-Object system.Data.DataColumn Type, ([string]))) 82 | $table.columns.add($(New-Object system.Data.DataColumn Size, ([int]))) 83 | $table.columns.add($(New-Object system.Data.DataColumn ReceivedTime, ([Datetime]))) 84 | $table.columns.add($(New-Object system.Data.DataColumn DataLink, ([string]))) 85 | ForEach ($line in $($log -split "`r`n")) { 86 | $row = $table.NewRow() 87 | $row.Location = Get-StringBetweenTwoStrings "Location: " "; Sender:" $line 88 | $row.Sender = Get-StringBetweenTwoStrings "Sender: " "; Subject:" $line 89 | $row.Subject = Get-StringBetweenTwoStrings "Subject: " "; Type:" $line 90 | $row.Type = Get-StringBetweenTwoStrings "Type: " "; Size:" $line 91 | $row.Size = Get-StringBetweenTwoStrings "Size: " "; Received Time:" $line 92 | $row.ReceivedTime = Get-StringBetweenTwoStrings "Received Time: " "; Data Link:" $line 93 | $row.DataLink = Get-StringBetweenTwoStrings "Data Link: " "," $line 94 | $table.Rows.Add($row) 95 | } 96 | return $table 97 | } 98 | 99 | $logPath = $logPath.TrimEnd('\') 100 | if (-not (Test-Path $logPath)) { 101 | Write-Host "Log path $($logPath) not found" 102 | Exit (1) 103 | } 104 | 105 | Start-Transcript -path "$($logPath)\$(get-date -Format yyyy_MM_dd)_Remove-ExchangeOnlineEmails.txt" 106 | if (-not (Get-InstalledModule -Name ExchangeOnlineManagement)) { 107 | Write-Host "ExchangeOnlineManagement module not found! Run: Install-Module -Name ExchangeOnlineManagement" 108 | Exit 109 | } 110 | Write-Host "Checking Exchange Online and Compliance connection" 111 | $sessions = Get-PSSession | Select-Object -Property State, Name, ComputerName 112 | $exchangeOnlineConnection = (@($sessions) -like '@{State=Opened; Name=ExchangeOnlineInternalSession*; ComputerName=outlook.office365.com*').Count -gt 0 113 | $complianceConnection = (@($sessions) -like '@{State=Opened; Name=ExchangeOnlineInternalSession*; ComputerName=*compliance.protection.outlook.com*').Count -gt 0 114 | 115 | if (!$exchangeOnlineConnection) { 116 | Write-Host "Connecting Exchange Online" 117 | Connect-ExchangeOnline -ErrorAction Stop 118 | } 119 | 120 | if (-not $complianceConnection) { 121 | Write-Host "Connecting Compliance" 122 | Connect-IPPSSession -ErrorAction Stop 123 | } 124 | 125 | Write-Host "Checking target" 126 | $folderQueries = @() 127 | $folderStatistics = Get-MailboxFolderStatistics $target | where-object { ($_.FolderPath -eq "/Recoverable Items") -or ($_.FolderPath -eq "/Purges") -or ($_.FolderPath -eq "/Versions") -or ($_.FolderPath -eq "/DiscoveryHolds") } 128 | if ($folderStatistics) { 129 | foreach ($folderStatistic in $folderStatistics) { 130 | $folderId = $folderStatistic.FolderId; 131 | $folderPath = $folderStatistic.FolderPath; 132 | $encoding = [System.Text.Encoding]::GetEncoding("us-ascii") 133 | $nibbler = $encoding.GetBytes("0123456789ABCDEF"); 134 | $folderIdBytes = [Convert]::FromBase64String($folderId); 135 | $indexIdBytes = New-Object byte[] 48; 136 | $indexIdIdx = 0; 137 | $folderIdBytes | Select-Object -skip 23 -First 24 | ForEach-Object { $indexIdBytes[$indexIdIdx++] = $nibbler[$_ -shr 4]; $indexIdBytes[$indexIdIdx++] = $nibbler[$_ -band 0xF] } 138 | $folderQuery = "folderid:$($encoding.GetString($indexIdBytes))"; 139 | $folderStat = New-Object PSObject 140 | Add-Member -InputObject $folderStat -MemberType NoteProperty -Name FolderPath -Value $folderPath 141 | Add-Member -InputObject $folderStat -MemberType NoteProperty -Name FolderQuery -Value $folderQuery 142 | $folderQueries += $folderStat 143 | } 144 | } 145 | else { 146 | Write-Host "$target statistics not found. Exiting" -ForegroundColor Red 147 | Exit 148 | } 149 | 150 | $RecoverableItemsFolder = $folderQueries.folderquery[0] 151 | $PurgesFolder = $folderQueries.folderquery[1] 152 | $VersionsFolder = $folderQueries.folderquery[2] 153 | $DiscoveryHoldsFolder = $folderQueries.folderquery[3] 154 | $searchName = "$($target)_emails_older_than_$($months)_months" 155 | try { 156 | if (Get-ComplianceSearch -Identity $searchName -ErrorAction SilentlyContinue) { 157 | Write-Host "Compliance Search $searchName exists. Changing properties" 158 | Set-ComplianceSearch -Identity $searchName -ExchangeLocation $target -ContentMatchQuery "(Received <= $((get-date).AddMonths(-$months).ToString("MM/dd/yyy"))) AND (kind:email) AND (NOT (($RecoverableItemsFolder) OR ($PurgesFolder) OR ($VersionsFolder) OR ($DiscoveryHoldsFolder)))" -ErrorAction "Stop" 159 | } 160 | else { 161 | Write-Host "Creating Compliance Search $searchName" 162 | New-ComplianceSearch -Name $searchName -ExchangeLocation $target -ContentMatchQuery "(Received <= $((get-date).AddMonths(-$months).ToString("MM/dd/yyy"))) AND (kind:email) AND (NOT (($RecoverableItemsFolder) OR ($PurgesFolder) OR ($VersionsFolder) OR ($DiscoveryHoldsFolder)))" -ErrorAction "Stop" 163 | } 164 | Write-Host "Running Compliance Search $searchName" 165 | Start-ComplianceSearch -Identity $searchName -ErrorAction "Stop" 166 | 167 | While ((Get-ComplianceSearch -Identity $searchName -ErrorAction "Stop").status -ne "Completed") { 168 | Write-Host "." -NoNewLine 169 | Start-Sleep 5 170 | } 171 | Write-Host "." 172 | $complianceSearchResults = Get-ComplianceSearch -Identity $searchName -ErrorAction "Stop" 173 | if ($complianceSearchResults.Items -le 0) { 174 | Write-Host "Compliance Search returned 0 items" 175 | } 176 | else { 177 | if ($complianceSearchResults.ExchangeLocation.count -ne 1) { 178 | Write-Host "You have selected a Compliance Search scoped for more than 1 mailbox, please restart and select a search scoped for a single mailbox." 179 | if ($deleteComplianceSearch) { 180 | Write-Host "Deleting object $searchName" 181 | Remove-ComplianceSearch -Identity $searchName -Confirm:$false -ErrorAction "Stop" 182 | } 183 | Exit 184 | } 185 | Write-Host "Compliance Search returned $($complianceSearchResults.Items) items" 186 | if ($preview) { 187 | $searchActionName = "$($searchName)_preview" 188 | if (Get-ComplianceSearchAction -Identity $searchActionName -ErrorAction SilentlyContinue) { 189 | Write-Host "Compliance Search Action $searchActionName exists. Deleting" 190 | Remove-ComplianceSearchAction -Identity $searchActionName -Confirm:$false -ErrorAction "Stop" 191 | } 192 | Write-Host "Creating Compliance Search Action for Preview $searchActionName" 193 | New-ComplianceSearchAction -SearchName $searchName -Preview -ErrorAction "Stop" | Out-Null 194 | 195 | Write-Host "Waiting for Compliance Search Action to finish" 196 | While ((Get-ComplianceSearchAction -Identity $searchActionName -ErrorAction "Stop").status -ne "Completed") { 197 | Write-Host "." -NoNewline 198 | Start-Sleep 5 199 | } 200 | Write-Host "." 201 | $complianceSearchActionResult = Get-ParsedLog (Get-ComplianceSearchAction $searchActionName -Details).Results 202 | $complianceSearchActionResult | Out-GridView -Title "Compliance Search Preview" 203 | } 204 | else { 205 | $searchActionName = "$($searchName)_purge" 206 | [int]$batches = [math]::floor($complianceSearchResults.Items / 10) 207 | if (Get-ComplianceSearchAction -Identity $searchActionName -ErrorAction SilentlyContinue) { 208 | Write-Host "Compliance Search Action $searchActionName exists. Deleting" 209 | Remove-ComplianceSearchAction -Identity $searchActionName -Confirm:$false -ErrorAction "Stop" | Out-Null 210 | } 211 | for ($batch = 1; $batch -le $batches; $batch++) { 212 | Write-Host "Batch $batch of $batches" -ForegroundColor Cyan 213 | Write-Host "Creating Compliance Search Action for Deletion $searchActionName" 214 | $repeat = $true 215 | $i = 1 216 | while ($repeat) { 217 | try { 218 | New-ComplianceSearchAction -SearchName $searchName -Purge -PurgeType HardDelete -Confirm:$false -ErrorAction "Stop" | Out-Null 219 | $repeat = $false 220 | } 221 | catch { 222 | Write-Host "Error trying to create Compliance Search Action. Waiting 5 seconds until next try. Try $i of 5" 223 | Start-Sleep -Seconds 5 224 | Remove-ComplianceSearchAction -Identity $searchActionName -Confirm:$false -ErrorAction "SilentlyContinue" | Out-Null 225 | if ($i -lt 6) { 226 | $i++ 227 | } 228 | else { 229 | Write-Host "Cannot create new Compliance Search Action" -ForegroundColor Red 230 | if ($deleteComplianceSearch) { 231 | Write-Host "Deleting object $searchName" 232 | Remove-ComplianceSearch -Identity $searchName -Confirm:$false -ErrorAction "Stop" 233 | } 234 | Exit 235 | } 236 | } 237 | } 238 | $complianceSearchActionStatus = (Get-ComplianceSearchAction -Identity $searchActionName).status 239 | Write-Host "Waiting for Compliance Search Action to finish" 240 | do { 241 | Write-Host "." -NoNewline 242 | Start-Sleep 5 243 | $complianceSearchActionStatus = (Get-ComplianceSearchAction -Identity $searchActionName).status 244 | } while ($complianceSearchActionStatus -ne "Completed") 245 | Write-Host "." 246 | Write-Host "10 items deleted. $($complianceSearchResults.Items - (10 * $batch)) remaining" -ForegroundColor Green 247 | Write-Host "Deleting Compliance Search Action $searchActionName" 248 | Remove-ComplianceSearchAction -Identity $searchActionName -Confirm:$false | Out-Null 249 | } 250 | } 251 | } 252 | 253 | if ($deleteComplianceSearch) { 254 | Write-Host "Deleting object $searchName" 255 | Remove-ComplianceSearch -Identity $searchName -Confirm:$false -ErrorAction "Stop" 256 | } 257 | } 258 | catch { 259 | Write-Error "An error occurred: $_" 260 | } 261 | Stop-Transcript 262 | -------------------------------------------------------------------------------- /Delete Windows 10 Preinstalled Apps/readme.md: -------------------------------------------------------------------------------- 1 | # Delete Windows 10 Preinstalled Apps 2 | 3 | Windows 10 includes a variety of universal apps like Candy Crush, Facebook, ect. All of these can be uninstalled with a single PowerShell Command. 4 | 5 | This command uninstalls all built-in apps except Windows Store, Calculator and Photos. 6 | 7 | ## Deletes current user apps 8 | 9 | ```powershell 10 | Get-AppxPackage | where-object {$_.name -notlike "*Microsoft.WindowsStore*"} | where-object {$_.name -notlike "*Microsoft.WindowsCalculator*"} | where-object {$_.name -notlike "*Microsoft.Windows.Photos*"} | Remove-AppxPackage 11 | ``` 12 | ## Deletes all users apps (current and new). 13 | 14 | Run PowerShell console as administrator. 15 | 16 | ```powershell 17 | Get-AppxPackage -AllUsers | where-object {$_.name -notlike "*Microsoft.WindowsStore*"} | where-object {$_.name -notlike "*Microsoft.WindowsCalculator*"} | where-object {$_.name -notlike "*Microsoft.Windows.Photos*"} | Remove-AppxPackage 18 | 19 | Get-AppxProvisionedPackage -online | where-object {$_.name -notlike "*Microsoft.WindowsStore*"} | where-object {$_.name -notlike "*Microsoft.WindowsCalculator*"} | where-object {$_.name -notlike "*Microsoft.Windows.Photos*"} | Remove-AppxProvisionedPackage -online 20 | ``` -------------------------------------------------------------------------------- /Delete reappearing printers that keeps comming back/DeletePrintersRDS.cmd: -------------------------------------------------------------------------------- 1 | Psexec.exe -s -i -accepteula powershell.exe -Command (Remove-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Enum\SWD\PRINTENUM\*' -Recurse -Force) 2 | Psexec.exe -s -i -accepteula powershell.exe -Command (Remove-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\DeviceClasses\{0ecef634-6ef0-472a-8085-5ad023ecbccd}\*' -Recurse -Force) 3 | Psexec.exe -s -i -accepteula powershell.exe -Command (Remove-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Providers\Client Side Rendering Print Provider\*' -Recurse -Force) 4 | Psexec.exe -s -i -accepteula powershell.exe -Command (Remove-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Printers\*' -Recurse -Force) 5 | Psexec.exe -s -i -accepteula powershell.exe -Command (Remove-Item -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\V4 Connections\*' -Recurse -Force) 6 | @echo off 7 | set /p r= Reboot computer? [y/n] 8 | if %r% == y goto reboot 9 | exit 10 | :reboot 11 | shutdown -r -f -t 0 -------------------------------------------------------------------------------- /Delete reappearing printers that keeps comming back/printers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Delete reappearing printers that keeps comming back/printers.png -------------------------------------------------------------------------------- /Delete reappearing printers that keeps comming back/readme.md: -------------------------------------------------------------------------------- 1 | # Windows Server Backup Email Report of Several Servers 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Delete%20reappearing%20printers%20that%20keeps%20comming%20back/DeletePrintersRDS.cmd) 4 | 5 | Script to delete ghost printers that keeps coming back after deletion. 6 | 7 | **Warning: this script will delete all printers.** 8 | 9 | You need to add psexec to system path. [Download PsTools](https://docs.microsoft.com/en-us/sysinternals/downloads/pstools) and extract it to C:\Windows\System32. Please, take a registry backup first! 10 | 11 | *Example of undeletable printers on a RDS server* 12 | 13 | ![Example of ghost printers](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Delete%20reappearing%20printers%20that%20keeps%20comming%20back/printers.png) 14 | 15 | -------------------------------------------------------------------------------- /Email Report of File Permissions on HTML and CSV/Get-FolderPermissions.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.0 4 | 5 | .GUID 1069276e-50b4-414a-ae8c-b8801445ae7e 6 | 7 | .AUTHOR Juan Granados 8 | 9 | .COPYRIGHT 2021 Juan Granados 10 | 11 | .TAGS Folder Permission Report HTML CSV email mail 12 | 13 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 14 | 15 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Email%20Report%20of%20File%20Permissions%20on%20HTML%20and%20CSV 16 | 17 | .RELEASENOTES 18 | Initial release 19 | #> 20 | 21 | <# 22 | .SYNOPSIS 23 | Generate a folders permissions report. 24 | .DESCRIPTION 25 | Starting with a root folder, it generates a folders permissions report. Number of subfolders examined depends on FolderDeep parameter. 26 | Report is generated in CSV format and can be send attached via mail with a html report in the body. 27 | .PARAMETER OutFile 28 | Path to store CSV file. 29 | Default .\Permissions.csv 30 | .PARAMETER RootPath 31 | Folder to start checking permissions. 32 | .PARAMETER FolderDeep 33 | Number of subfolders levels to check. 34 | Default 99. 35 | .PARAMETER ObjectsIgnored 36 | Users or groups to ignore in report. 37 | Default NT AUTHORITY\SYSTEM,BUILTIN\Administrator 38 | .PARAMETER InspectGroups 39 | List only users in report. 40 | Default $False 41 | .PARAMETER SMTPServer 42 | Sets smtp server in order to sent an email with backup result. If leave blank, no email will be send. 43 | .PARAMETER SMTPRecipient 44 | List of emails addresses which will receive the backup result separated by commas. 45 | .PARAMETER SMTPSender 46 | Email address which will send the backup result. 47 | .PARAMETER SMTPUser 48 | Username in case of smtp server requires authentication. 49 | .PARAMETER SMTPPassword 50 | Password in case of smtp server requires authentication. 51 | .PARAMETER SMTPSSL 52 | Use of SSL in case of smtp server requires SSL. 53 | Default: $False 54 | .PARAMETER SMTPPort 55 | Port to connect to smtp server. 56 | Default: 25 57 | .EXAMPLE 58 | Get-FoldersPermissions -RootPath "D:\Data\Departments" -FolderDeep 2 -SMTPServer "mail.server.com" -SMTPRecipient "megaboss@server.com","support@server.com" -SMTPSender "reports@server.com" 59 | .LINK 60 | https://github.com/juangranados/powershell-scripts/tree/main/Email%20Report%20of%20File%20Permissions%20on%20HTML%20and%20CSV 61 | .NOTES 62 | Author: Juan Granados 63 | #> 64 | [cmdletbinding()] 65 | 66 | param( 67 | [parameter(Position=0,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Path to store CSV file')][string]$OutFile = ".\$(Get-Date -format "yyyyMMdd_hhmmss")-Permissions.csv", 68 | [parameter(Position=1,Mandatory=$true,ValueFromPipeline=$false,HelpMessage='Folder to start checking permissions')][string]$RootPath, 69 | [parameter(Position=2,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Number of subfolders levels to check')][string]$FolderDeep = 99, 70 | [parameter(Position=3,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Users or groups to ignore in report')][string[]]$ObjectsIgnored = @("NT AUTHORITY\SYSTEM","BUILTIN\Administrator"), 71 | [parameter(Position=4,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Inspect users in groups ($True/$False)')][bool]$InspectGroups=$false, 72 | [parameter(Position=5,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail From')][string]$SMTPSender, 73 | [parameter(Position=6,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail To')]$SMTPRecipient, 74 | [parameter(Position=7,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail Server')][string]$SMTPServer, 75 | [parameter(Position=8,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail User')][string]$SMTPUser, 76 | [parameter(Position=9,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail Password')][string]$SMTPPassword, 77 | [parameter(Position=10,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail Port')][string]$SMTPPort=25, 78 | [parameter(Position=11,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Use SSL in mail sending ($True/$False)')][bool]$SMTPSSL=$False, 79 | [parameter(Position=12,Mandatory=$false,ValueFromPipeline=$false,HelpMessage='Mail Subject')][string]$SMTPSubject="Permission report on server $($env:computername) on directory $($RootPath) with $($FolderDeep) level deep" 80 | ) 81 | Function Get-FolderPermissions($Folder,[int]$Deep = 0){ 82 | # Write current folder name 83 | Write-Host "Examining folder $($Folder.FullName)" 84 | 85 | # Get folder ACLs 86 | $ACLs = get-acl $Folder.fullname | ForEach-Object {$_.Access} 87 | # Examining folder ACLs 88 | Foreach ($ACL in $ACLs){ 89 | # If current ACL contains one of object ignored list, skip ACL 90 | if (-not ($ObjectsIgnored.Contains($ACL.IdentityReference.value.toString()))){ 91 | # Delete commas in folder name and ACL InheritanceFlags, FileSystemRights 92 | $FolderName = $($Folder.Fullname -replace ',','.') 93 | $ACLInheritanceFlags = $($ACL.InheritanceFlags -replace ',',' &') 94 | $ACLFileSystemRights = $($ACL.FileSystemRights -replace ',',' &') 95 | # If inspect groups is true, list group users. 96 | if ($InspectGroups){ 97 | # Check if ACL identity reference is a group 98 | if (-not ($ACL.IdentityReference.value -like "BUILTIN\*") -and ($ACL.IdentityReference.Value.LastIndexOf('\') -ne -1)){ 99 | # Get group users 100 | $ADGroup = Get-ADGroup -LDAPFilter "(SAMAccountName=$($ACL.IdentityReference.Value.Substring($ACL.IdentityReference.Value.LastIndexOf('\')+1)))" -ErrorAction SilentlyContinue 101 | # If group has users 102 | if ($ADGroup){ 103 | # Get group users 104 | $users = Get-ADGroupMember -identity $ADGroup -Recursive | Get-ADUser -Property DisplayName 105 | # Store users ACL information 106 | ForEach($User in $users){ 107 | # Store user info in csv file 108 | $OutInfo = $FolderName + "," + $User.UserPrincipalName + "," + $ACL.AccessControlType + "," + $ACLFileSystemRights + "," + $ACL.IsInherited + "," + $ACLInheritanceFlags + "," + $ACL.PropagationFlags 109 | Add-Content -Value $OutInfo -Path $OutFile 110 | # Store user info in table 111 | $Rows+="" + $Folder.Fullname + "" + "" + $User.UserPrincipalName + "" + "" + $ACL.AccessControlType + "" + "" + $ACL.FileSystemRights + "" + "" + $ACL.IsInherited + "" + "" + $ACL.InheritanceFlags + "" + "" + $ACL.PropagationFlags + "`r" 112 | } 113 | # Next loop 114 | continue 115 | } 116 | } 117 | } 118 | # Inspect groups is false or ACL identity reference is a group but users could not be retrieved 119 | # Store object info in csv file 120 | $OutInfo = $FolderName + "," + $ACL.IdentityReference + "," + $ACL.AccessControlType + "," + $ACLFileSystemRights + "," + $ACL.IsInherited + "," + $ACLInheritanceFlags + "," + $ACL.PropagationFlags 121 | Add-Content -Value $OutInfo -Path $OutFile 122 | # Store object info in table 123 | $Rows+="" + $Folder.Fullname + "" + "" + $ACL.IdentityReference + "" + "" + $ACL.AccessControlType + "" + "" + $ACL.FileSystemRights + "" + "" + $ACL.IsInherited + "" + "" + $ACL.InheritanceFlags + "" + "" + $ACL.PropagationFlags + "`r" 124 | 125 | } 126 | } 127 | # Get subfolders 128 | $Folders = Get-ChildItem $Folder.FullName -dir -ErrorAction SilentlyContinue 129 | # If there any folders and we want to inspect more folders 130 | If ($Folders -and $Deep){ 131 | # Examining new folders 132 | ForEach ($F in $Folders){ 133 | # Add results to table 134 | $Rows += $(Get-FolderPermissions $F $($Deep - 1) ) 135 | } 136 | } 137 | # Return table 138 | return $Rows 139 | } 140 | 141 | # Variable initialization 142 | # Set csv header 143 | $Header = "Folder Path,Identity Reference,Access Control Type,File System Rights,Is Inherited,Inheritance Flags,Propagation Flags" 144 | # Delete csv file 145 | Del $OutFile -ErrorAction "SilentlyContinue" 146 | # Add header to csv 147 | Add-Content -Value $Header -Path $OutFile 148 | # Clear html table variable 149 | if ([boolean](get-variable "Rows" -ErrorAction SilentlyContinue)) 150 | {Clear-Variable -Name "Rows" -Scope Global} 151 | 152 | #Generate table and csv 153 | $Table = Get-FolderPermissions $(Get-Item $RootPath) $FolderDeep 154 | 155 | #Create HTML body 156 | $HTMLFile = @" 157 | 158 | 159 | 160 | 161 | 165 | 166 | 167 |

$SMTPSubject

168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | $Table 179 |
Folder PathIdentity ReferenceAccess Control TypeFile System RightsIs InheritedInheritance FlagsPropagation Flags
180 | 181 | 182 | "@ 183 | #Send mail 184 | If ($SMTPServer) 185 | { 186 | # Set smpt password 187 | $SecureSMTPPassword = ConvertTo-SecureString $SMTPPassword -AsPlainText -Force 188 | $SMTPCredential = New-Object System.Management.Automation.PSCredential($SMTPUser,$SecureSMTPPassword) 189 | Write-Host "Sending Email" 190 | if ($SMTPSSL){ 191 | Send-MailMessage -To $SMTPRecipient -From $SMTPSender -Attachments $OutFile -SmtpServer $SMTPServer -Subject $SMTPSubject -UseSsl -Port $SMTPPort -Credential $SMTPCredential -BodyAsHtml -Body $HTMLFile -Encoding ([System.Text.Encoding]::utf8) 192 | } 193 | else{ 194 | Send-MailMessage -To $SMTPRecipient -From $SMTPSender -Attachments $OutFile -SmtpServer $SMTPServer -Subject $SMTPSubject -Port $SMTPPort -Credential $SMTPCredential -BodyAsHtml -Body $HTMLFile -Encoding ([System.Text.Encoding]::utf8) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Email Report of File Permissions on HTML and CSV/readme.md: -------------------------------------------------------------------------------- 1 | # Email Report of File Permissions on HTML and CSV 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Email%20Report%20of%20File%20Permissions%20on%20HTML%20and%20CSV/Get-FolderPermissions.ps1) 4 | 5 | Starting with a root folder, it generates a folders permissions report. Number of sub folders examined depends on FolderDeep parameter. 6 | 7 | Report is generated in CSV format and can be send attached via mail with a HTML report in the body. 8 | 9 | ![Screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Email%20Report%20of%20File%20Permissions%20on%20HTML%20and%20CSV/screenshot.png) 10 | 11 | ```powershell 12 | <# 13 | .SYNOPSIS 14 | Generate a folders permissions report. 15 | .DESCRIPTION 16 | Starting with a root folder, it generates a folders permissions report. Number of subfolders examined depends on FolderDeep parameter. 17 | Report is generated in CSV format and can be send attached via mail with a html report in the body. 18 | .PARAMETER OutFile 19 | Path to store CSV file. 20 | Default .\Permissions.csv 21 | .PARAMETER RootPath 22 | Folder to start checking permissions. 23 | .PARAMETER FolderDeep 24 | Number of subfolders levels to check. 25 | Default 99. 26 | .PARAMETER ObjectsIgnored 27 | Users or groups to ignore in report. 28 | Default NT AUTHORITY\SYSTEM,BUILTIN\Administrator 29 | .PARAMETER InspectGroups 30 | List only users in report. 31 | Default $False 32 | .PARAMETER SMTPServer 33 | Sets smtp server in order to sent an email with backup result. If leave blank, no email will be send. 34 | .PARAMETER SMTPRecipient 35 | List of emails addresses which will receive the backup result separated by commas. 36 | .PARAMETER SMTPSender 37 | Email address which will send the backup result. 38 | .PARAMETER SMTPUser 39 | Username in case of smtp server requires authentication. 40 | .PARAMETER SMTPPassword 41 | Password in case of smtp server requires authentication. 42 | .PARAMETER SMTPSSL 43 | Use of SSL in case of smtp server requires SSL. 44 | Default: $False 45 | .PARAMETER SMTPPort 46 | Port to connect to smtp server. 47 | Default: 25 48 | .EXAMPLE 49 | Get-FoldersPermissions -RootPath "D:\Data\Departments" -FolderDeep 2 -SMTPServer "mail.server.com" -SMTPRecipient "megaboss@server.com","support@server.com" -SMTPSender "reports@server.com" 50 | .NOTES 51 | Author: Juan Granados 52 | #> 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /Email Report of File Permissions on HTML and CSV/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Email Report of File Permissions on HTML and CSV/screenshot.png -------------------------------------------------------------------------------- /File Server Access Audit Report with PowerShell/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/File Server Access Audit Report with PowerShell/1.PNG -------------------------------------------------------------------------------- /File Server Access Audit Report with PowerShell/2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/File Server Access Audit Report with PowerShell/2.PNG -------------------------------------------------------------------------------- /File Server Access Audit Report with PowerShell/5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/File Server Access Audit Report with PowerShell/5.PNG -------------------------------------------------------------------------------- /File Server Access Audit Report with PowerShell/6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/File Server Access Audit Report with PowerShell/6.PNG -------------------------------------------------------------------------------- /File Server Access Audit Report with PowerShell/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/File Server Access Audit Report with PowerShell/7.png -------------------------------------------------------------------------------- /File Server Access Audit Report with PowerShell/readme.md: -------------------------------------------------------------------------------- 1 | # File Server Access Audit Report with PowerShell 2 | 3 | Right click here and select "Save link as" to download 4 | 5 | This PowerShell script allows to audit several file servers and send a report in CSV and HTML by mail. 6 | 7 | CSV file can be import on Excel to generate a File Audit Report. 8 | 9 | HTML report can filter and sorting rows by server, time, user, file or operation (read, delete or write). 10 | 11 | ![HTML Report example](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell/7.png) 12 | 13 | ## How to configure auditing in Windows Server 14 | 15 | ### Enable audit in Windows Server 16 | 17 | From local policy or group policy, navigate to Computer Configuration → Policies → Windows Settings → Security Settings → Local Policies → Audit Policy → Open Audit object access and select Success and Failure 18 | 19 | ![Enable Audit](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell/1.PNG) 20 | 21 | ### Enable audit in folder and subfolders 22 | 23 | Right click on folder →Properties → Security → Advanced → Auditing → Add 24 | 25 | ![Enable auditing](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell/2.PNG) 26 | 27 | Select domain users or user group and mark: 28 | 29 | - Type: All. 30 | 31 | - Basic Permissions: Read & execute, List folder contents, Read. 32 | 33 | ![Configure auditing](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell/5.PNG) 34 | 35 | ### Increase Security Log size 36 | 37 | From local policy or group policy, browse to Computer Configuration → Policies → Administrative Templates → Windows Components → Event Log Service → Security → Specify the maximum log file size 38 | Set the maximum log file size setting, for example 4194240 KB (4 GB). 39 | 40 | ![Increase security log screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell/6.PNG) 41 | -------------------------------------------------------------------------------- /GUI Password Reset Tool for Active Directory/GUI.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/GUI Password Reset Tool for Active Directory/GUI.PNG -------------------------------------------------------------------------------- /GUI Password Reset Tool for Active Directory/Set-ADPassword.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.0 4 | 5 | .GUID 4111a7cc-46b4-4648-8b8b-e7743bd61b86 6 | 7 | .AUTHOR Juan Granados 8 | 9 | .COPYRIGHT 2021 Juan Granados 10 | 11 | .TAGS AD Active Directory Reset Password GUI 12 | 13 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 14 | 15 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/GUI%20Password%20Reset%20Tool%20for%20Active%20Directory 16 | 17 | .RELEASENOTES 18 | Initial release 19 | 20 | #> 21 | 22 | <# 23 | .DESCRIPTION 24 | Powershell script for IT support team which allow to reset AD users password in a GUI interface. 25 | Requirements: 26 | - PowerShell 4 (Windows Management Framework 4) o highter. 27 | - Support users must be able to reset AD users password: 28 | https://community.spiceworks.com/how_to/1464-how-to-delegate-password-reset-permissions-for-your-it-staff 29 | - Support computers must install RSAT (Remote Server Administration Tools): 30 | dism /Add-Capability /CapabilityName:$((Get-WindowsCapability -Online -Name "*Rsat.ActiveDirectory*").Name) /Online 31 | .NOTES 32 | Author: Juan Granados 33 | Thanks to: https://foxdeploy.com/2015/04/10/part-i-creating-powershell-guis-in-minutes-using-visual-studio-a-new-hope/ 34 | #> 35 | $inputXML = @" 36 | 42 | 43 | 71 | 72 | "@ 73 | 74 | $inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^Right click here and select "Save link as" to download 4 | 5 | Powershell script for IT support team which allow to reset AD users password in a GUI interface. 6 |

7 | GUI screenshot 8 |

9 | -------------------------------------------------------------------------------- /Install JRE/Download-JRE.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Download Java Runtime Environment (jre) latest version for Windows for later installation. 4 | .DESCRIPTION 5 | This script Download Java Runtime Environment 32 and 64 bit (jre) latest version for Windows for later installation. 6 | .PARAMETER DownloadPath 7 | Path to download both JRE installers: 32 and 64 bit. 8 | Example: D:\Software\JRE 9 | .PARAMETER LogPath 10 | Log path (optional). 11 | Example: D:\Software\JRE\Log 12 | .NOTES 13 | Author: Juan Granados 14 | Date: April 2022 15 | #> 16 | Param( 17 | [Parameter(Mandatory = $true, Position = 0)] 18 | [ValidateNotNullOrEmpty()] 19 | [string]$downloadPath, 20 | [Parameter(Mandatory = $false, Position = 1)] 21 | [ValidateNotNullOrEmpty()] 22 | [string]$logPath 23 | ) 24 | #Requires -RunAsAdministrator 25 | if (-not [string]::IsNullOrWhiteSpace($logPath) -and $logPath.Chars($logPath.Length - 1) -eq '\') { 26 | $logPath = ($logPath.TrimEnd('\')) 27 | } 28 | if ($downloadPath.Chars($downloadPath.Length - 1) -eq '\') { 29 | $downloadPath = ($downloadPath.TrimEnd('\')) 30 | } 31 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { 32 | Start-Transcript -Path "$($logPath)\$($env:COMPUTERNAME)_downloadJRE.log" | Out-Null 33 | } 34 | Write-Host "Surfing https://www.java.com/en/download/manual.jsp" 35 | 36 | $URL = "https://www.java.com/en/download/manual.jsp" 37 | $global:ie = New-Object -com "InternetExplorer.Application" 38 | $global:ie.visible = $false 39 | $global:ie.Navigate($URL) 40 | 41 | do { 42 | Start-Sleep -s 1 43 | } until(!($global:ie.Busy)) 44 | $global:doc = $global:ie.Document 45 | 46 | $links = @($global:doc.links) 47 | $ProgressPreference = 'SilentlyContinue' 48 | $link = $links | Where-Object { $_.href -like "http*" } | Where-Object { $_.title -like "Download Java software for Windows (64-bit)" } 49 | Write-Host "Downloading JRE x64" 50 | Invoke-WebRequest $link[0].href -OutFile $downloadPath\jrex64.exe 51 | $link = $links | Where-Object { $_.href -like "http*" } | Where-Object { $_.title -like "Download Java software for Windows Offline" } 52 | Write-Host "Downloading JRE x32" 53 | Invoke-WebRequest $link[0].href -OutFile $downloadPath\jrex32.exe 54 | 55 | if (-not [string]::IsNullOrWhiteSpace($logPath)) { 56 | Stop-Transcript 57 | } -------------------------------------------------------------------------------- /Install JRE/Install-JRE.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Install Java Runtime Environment (jre) for Windows if previous or no version detected and uninstall previous versions. 4 | .DESCRIPTION 5 | This script install Java Runtime Environment only if a previous version or no version was detected. 6 | You can avoid uninstall old versions with -NoUninstall switch. 7 | .PARAMETER InstallPath 8 | Java Runtime Environment full installer path. It could be download from https://www.java.com/en/download/manual.jsp. 9 | Authenticated users must have read permissions over shared folder. 10 | Example: \\FILESERVER-01\Skype\jre-10.0.2_windows-x64_bin.exe 11 | .PARAMETER LogPath 12 | Log path (optional). ComputerName.log file will be created. 13 | Authenticated users must have write permissions over log shared folder. 14 | Example: \\FILESERVER-01\JRE\Logs (Log will be saved to \\FILESERVER-01\JRE\computername.log) 15 | .PARAMETER NoUninstall 16 | Default: false. 17 | Avoid uninstall previous versions before install Java Runtime Environment. 18 | .PARAMETER x64 19 | Default: false. 20 | 64 bit version is selected for installing: InstallPath contains a 64 Bit JRE Installer. 21 | .EXAMPLE 22 | Install 64 bit Java Runtime Environment from network share, saving log in Log folder of network share and uninstall previous versions. 23 | Note: network share must have read permissions on "\\FILESERVER-01\JRE\" and write on "\\FILESERVER-01\JRE\Logs" for "Authenticated Users" group. 24 | InstallJRE.ps1 "\\FILESERVER-01\JRE\jre-10.0.2_windows-x64_bin.exe" "\\FILESERVER-01\JRE\Logs" -x64 25 | .EXAMPLE 26 | Install 32 bit Java Runtime Environment from network share, saving log in Log folder of network share and uninstall previous versions. 27 | Note: network share must have read permissions on "\\FILESERVER-01\JRE\" and write on "\\FILESERVER-01\JRE\Logs" for "Authenticated Users" group. 28 | InstallJRE.ps1 "\\FILESERVER-01\JRE\jre-8u181-windows-i586.exe" "\\FILESERVER-01\JRE\Logs" 29 | .NOTES 30 | Author: Juan Granados 31 | Date: April 2022 32 | #> 33 | Param( 34 | [Parameter(Mandatory = $true, Position = 0)] 35 | [ValidateNotNullOrEmpty()] 36 | [string]$InstallPath, 37 | [Parameter(Mandatory = $false, Position = 1)] 38 | [ValidateNotNullOrEmpty()] 39 | [string]$LogPath, 40 | [Parameter(Mandatory = $false, Position = 2)] 41 | [ValidateNotNullOrEmpty()] 42 | [switch]$NoUninstall, 43 | [Parameter(Mandatory = $false, Position = 3)] 44 | [ValidateNotNullOrEmpty()] 45 | [switch]$x64 46 | ) 47 | #Requires -RunAsAdministrator 48 | function Get-InstalledApps { 49 | if ([IntPtr]::Size -eq 4) { 50 | $regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 51 | } 52 | else { 53 | $regpath = @( 54 | 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 55 | 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' 56 | ) 57 | } 58 | Get-ItemProperty $regpath | . { process { if ($_.DisplayName -and $_.UninstallString) { $_ } } } | 59 | Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString | 60 | Sort-Object DisplayVersion 61 | } 62 | 63 | function StringVersionToFloat($version) { 64 | while (($version.ToCharArray() | Where-Object { $_ -eq '.' } | Measure-Object).Count -gt 1) { 65 | $aux = $version.Substring($version.LastIndexOf('.') + 1) 66 | $version = $version.Substring(0, $version.LastIndexOf('.')) + $aux 67 | } 68 | return [float]$version 69 | } 70 | 71 | if (-not [string]::IsNullOrWhiteSpace($LogPath) -and $logPath.Chars($logPath.Length - 1) -eq '\') { 72 | $logPath = ($logPath.TrimEnd('\')) 73 | } 74 | 75 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { 76 | if ($x64) { 77 | Start-Transcript -Path "$($LogPath)\$($env:COMPUTERNAME)_x64.log" | Out-Null 78 | } 79 | else { 80 | Start-Transcript -Path "$($LogPath)\$($env:COMPUTERNAME).log" | Out-Null 81 | } 82 | } 83 | 84 | if (-not [Environment]::Is64BitOperatingSystem -and $x64) { 85 | Write-Error "Error: 64 Bit version can not be installed on 32 Bit Operating System." 86 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { Stop-Transcript } 87 | Exit 1 88 | } 89 | 90 | $ErrorActionPreference = "Stop" 91 | $Install = $true 92 | 93 | try { 94 | $jreExeFile = Get-Item -Path $InstallPath 95 | } 96 | catch { 97 | Write-Error "Error accessing $($InstallPath)." 98 | Write-Error "$($Error[0])" 99 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { Stop-Transcript } 100 | Exit 1 101 | } 102 | 103 | $jreExeVersion = StringVersionToFloat $jreExeFile.VersionInfo.ProductVersion 104 | 105 | Write-Host "JRE $($jreExeVersion) selected for installation." 106 | 107 | if ($x64) { 108 | $jreInstalledVersion = Get-InstalledApps | Where-Object { $_.DisplayName -like '*Java*(64-bit)' } 109 | } 110 | else { 111 | $jreInstalledVersion = Get-InstalledApps | Where-Object { $_.DisplayName -like '*Java*' -and $_.DisplayName -notlike '*Java*(64-bit)' } 112 | } 113 | 114 | if ($jreInstalledVersion) { 115 | 116 | foreach ($installation in $jreInstalledVersion) { 117 | 118 | $version = StringVersionToFloat $installation.DisplayVersion 119 | 120 | if (($version -lt $jreExeVersion)) { 121 | Write-Host "JRE $($version) detected." 122 | if (-not $NoUninstall) { 123 | Write-Host "Unnistalling JRE $($version)." 124 | if ($installation.UninstallString -like '*msiexec*') { 125 | Start-Process -FilePath cmd.exe -ArgumentList '/c', $installation.UninstallString, '/qn /norestart' -Wait 126 | } 127 | else { 128 | Start-Process -FilePath cmd.exe -ArgumentList '/c', $installation.UninstallString, '/verysilent' -Wait 129 | } 130 | } 131 | } 132 | else { 133 | Write-Host "JRE $($version) or greater already installed." 134 | $Install = $false 135 | } 136 | } 137 | } 138 | else { 139 | Write-Host "No JRE version detected." 140 | } 141 | 142 | if ($Install) { 143 | Write-Host "Starting installation of JRE $($jreExeVersion) and exiting." 144 | Start-Process $InstallPath -ArgumentList "/s SPONSORS=0" -Wait -PassThru | Wait-Process -Timeout 200 145 | } 146 | 147 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { Stop-Transcript } -------------------------------------------------------------------------------- /Install JRE/readme.md: -------------------------------------------------------------------------------- 1 | # Install or update Java Runtime Environment 32 or 64 bits on domain computers using GPO and uninstall old versions. 2 | 3 | Install Java Runtime Environment (JRE) if previous or no version was detected and uninstall previous versions for security reasons. 4 | 5 | This script install Java only if a previous version or no version was detected. 6 | 7 | You can skip uninstallation of old versions with -NoUninstall switch. 8 | 9 | [Install-JRE](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20JRE/Install-JRE.ps1): to install latest JRE on Windows Computers with GPO. 10 | 11 | [Download-JRE](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20JRE/Download-JRE.ps1): to run scheduled task on server in order to keep JRE updated in file share. 12 | 13 | ## Instructions 14 | 15 | ### Download and update daily 16 | 17 | To download and keep Java Runtime Environment updated 18 | 19 | Create a scheduled task on server that nightly executes 'Download-JRE.ps1' to your server path. 20 | 21 | ```powershell 22 | Download-JRE.ps1 -downloadPath "D:\Software\JRE" -logPath "C:\Logs" 23 | ``` 24 | Will download jrex64.exe and jrex32.exe to D:\Software\JRE 25 | 26 | ### Instructions for 64 bit installation 27 | 28 | 1. Create shared folder to deploy Java Runtime Environment: ```\\FILESERVER-01\JRE``` 29 | 2. Grant 'Authenticated users' read access to ```\\FILESERVER-01\JRE``` 30 | 3. Copy ```Install-JRE.ps1``` to ```\\FILESERVER-01\JRE\Install-JRE.ps1``` 31 | 4. Create shared folder for logs: ```\\FILESERVER-01\JRE\logs``` 32 | 5. Grant 'Authenticated users' write access to ```\\FILESERVER-01\JRE\logs``` 33 | 6. Create a computer GPO that runs PowerShell Script: 34 | ``` 35 | Name: \\FILESERVER-01\JRE\Install-JRE.ps1 36 | Parameters: \\FILESERVER-01\JRE\jrex64.exe \\FILESERVER-01\JRE\logs -x64 37 | ``` 38 | 39 | ### Instructions for 32 bit installation 40 | 41 | 1. Create shared folder to deploy Java Runtime Environment: ```\\FILESERVER-01\JRE``` 42 | 2. Grant 'Authenticated users' read access to ```\\FILESERVER-01\JRE``` 43 | 3. Copy ```Install-JRE.ps1``` to ```\\FILESERVER-01\JRE\Install-JRE.ps1``` 44 | 4. Create shared folder for logs: ```\\FILESERVER-01\JRE\logs``` 45 | 5. Grant 'Authenticated users' ```write access to \\FILESERVER-01\JRE\logs``` 46 | 6. Create a computer GPO that runs PowerShell Script: 47 | ``` 48 | Name: \\FILESERVER-01\JRE\Install-JRE.ps1 49 | Parameters: \\FILESERVER-01\JRE\jrex32.exe \\FILESERVER-01\JRE\logs 50 | ``` 51 | 52 | ## Parameters 53 | 54 | ### Install-JRE 55 | 56 | **InstallPath:** JRE installer path. 57 | 58 | **LogPath:** Path where save log file. 59 | 60 | **NoUninstall:** Will not uninstall JRE previous versions. 61 | 62 | **x64:** Install JRE 64 bits version. By default 32 bits version is installed. 63 | 64 | ```powershell 65 | # Install JRE 32 bit 66 | Install-JRE.ps1 -InstallPath \\FS01\JRE\jrex32.exe -LogPath \\FS01\JRE\Logs 67 | ``` 68 | ```powershell 69 | # Install JRE 32 bit without uninstall previous versions 70 | Install-JRE.ps1 -InstallPath \\FS01\JRE\jrex32.exe -LogPath \\FS01\JRE\Logs -NoUninstall 71 | ``` 72 | ```powershell 73 | # Install JRE 64 bit 74 | Install-JRE.ps1 -InstallPath \\FS01\JRE\jrex32.exe -LogPath \\FS01\JRE\Logs -x64 75 | ``` 76 | ```powershell 77 | # Install JRE 64 bit without uninstall previous versions 78 | Install-JRE.ps1 -InstallPath \\FS01\JRE\jrex32.exe -LogPath \\FS01\JRE\Logs -x64 -NoUninstall 79 | ``` 80 | 81 | ### Download-JRE 82 | 83 | **DownloadPath:** Path where save jrex32.exe and jrex64.exe. 84 | 85 | **LogPath:** Path where save log file. 86 | 87 | ```powershell 88 | # Download JRE 32 and 64 bit to D:\Software\JRE and save log in C:\Logs 89 | Download-JRE.ps1 -downloadPath "D:\Software\JRE" -logPath "C:\Logs" 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /Install LibreOffice/Install-LibreOffice.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Install LibreOffice for Windows if previous or no version detected and uninstall previous versions. 4 | .DESCRIPTION 5 | This script LibreOffice only if a previous version or no version was detected. 6 | You can avoid uninstall old versions with -NoUninstall switch. 7 | .PARAMETER InstallPath 8 | LibreOffice full installer path. It could be download from https://www.libreoffice.org/download/download-libreoffice/ 9 | Authenticated users must have read permissions over shared folder. 10 | Example: \\FILESERVER-01\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi 11 | .PARAMETER LogPath 12 | Log path (optional). ComputerName.log file will be created. 13 | Authenticated users must have write permissions over log shared folder. 14 | Example: \\FILESERVER-01\LibreOffice\Logs (Log will be saved to \\FILESERVER-01\JRE\computername.log) 15 | .PARAMETER MSIArguments 16 | Parameters of LibreOffice installation options (optional). 17 | Default "/qn /norestart ALLUSERS=1 CREATEDESKTOPLINK=0 REGISTER_ALL_MSO_TYPES=0 REGISTER_NO_MSO_TYPES=1 ISCHECKFORPRODUCTUPDATES=0 QUICKSTART=0 ADDLOCAL=ALL UI_LANGS=en_US" 18 | .PARAMETER NoUninstall 19 | Default: false. 20 | Avoid uninstall previous versions before install. 21 | .EXAMPLE 22 | Install LibreOffice from network share, saving log in Log folder of network share. 23 | Note: network share must have read permissions on "\\FILESERVER-01\LibreOffice\" and write on "\\FILESERVER-01\LibreOffice\Logs" for "Authenticated Users" group. 24 | InstallLibreOffice.ps1 "\\FILESERVER-01\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi" "\\FILESERVER-01\LibreOffice\Logs" 25 | .NOTES 26 | Author: Juan Granados 27 | Date: November 2023 28 | #> 29 | Param( 30 | [Parameter(Mandatory = $true, Position = 0)] 31 | [ValidateNotNullOrEmpty()] 32 | [string]$InstallPath, 33 | [Parameter(Mandatory = $false, Position = 1)] 34 | [ValidateNotNullOrEmpty()] 35 | [string]$LogPath, 36 | [Parameter(Mandatory = $false, Position = 2)] 37 | [ValidateNotNullOrEmpty()] 38 | [string]$MSIArguments= "/qn /norestart ALLUSERS=1 CREATEDESKTOPLINK=0 REGISTER_ALL_MSO_TYPES=0 REGISTER_NO_MSO_TYPES=1 ISCHECKFORPRODUCTUPDATES=0 QUICKSTART=0 ADDLOCAL=ALL UI_LANGS=en_US" 39 | ) 40 | #Requires -RunAsAdministrator 41 | function Get-InstalledApps { 42 | if ([IntPtr]::Size -eq 4) { 43 | $regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 44 | } 45 | else { 46 | $regpath = @( 47 | 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 48 | 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' 49 | ) 50 | } 51 | Get-ItemProperty $regpath | . { process { if ($_.DisplayName -and $_.UninstallString) { $_ } } } | 52 | Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString | 53 | Sort-Object DisplayVersion 54 | } 55 | 56 | function StringVersionToFloat($version) { 57 | while (($version.ToCharArray() | Where-Object { $_ -eq '.' } | Measure-Object).Count -gt 1) { 58 | $aux = $version.Substring($version.LastIndexOf('.') + 1) 59 | $version = $version.Substring(0, $version.LastIndexOf('.')) + $aux 60 | } 61 | return [float]$version 62 | } 63 | 64 | function Get-MSIFileVersion ([System.IO.FileInfo]$msiFilePath) { 65 | try { 66 | $WindowsInstaller = New-Object -com WindowsInstaller.Installer 67 | $Database = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $WindowsInstaller, @($msiFilePath.FullName, 0)) 68 | $Query = "SELECT Value FROM Property WHERE Property = 'ProductVersion'" 69 | $View = $database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $Database, ($Query)) 70 | $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null) | Out-Null 71 | $Record = $View.GetType().InvokeMember( "Fetch", "InvokeMethod", $Null, $View, $Null ) 72 | $Version = $Record.GetType().InvokeMember( "StringData", "GetProperty", $Null, $Record, 1 ) 73 | return $Version 74 | } catch { 75 | throw "Failed to get MSI file version: {0}." -f $_ 76 | } 77 | } 78 | 79 | if (-not [string]::IsNullOrWhiteSpace($LogPath) -and $logPath.Chars($logPath.Length - 1) -eq '\') { 80 | $logPath = ($logPath.TrimEnd('\')) 81 | } 82 | try{Stop-Transcript} catch{} 83 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { 84 | Start-Transcript -Path "$($LogPath)\$($env:COMPUTERNAME).txt" | Out-Null 85 | } 86 | 87 | $ErrorActionPreference = "Stop" 88 | $Install = $true 89 | 90 | try { 91 | $loExeFile = Get-Item -Path $InstallPath 92 | } 93 | catch { 94 | Write-Error "Error accessing $($InstallPath)." 95 | Write-Error "$($Error[0])" 96 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { Stop-Transcript } 97 | Exit 1 98 | } 99 | 100 | $loExeVersion = StringVersionToFloat $(Get-MSIFileversion $loExeFile) 101 | 102 | Write-Host "LibreOffice $($loExeVersion) selected for installation." 103 | 104 | $loInstalledVersion = Get-InstalledApps | Where-Object { $_.DisplayName -like '*LibreOffice*' } 105 | 106 | if ($loInstalledVersion) { 107 | 108 | foreach ($installation in $loInstalledVersion) { 109 | 110 | $version = StringVersionToFloat $installation.DisplayVersion 111 | 112 | if (($version -lt $loExeVersion)) { 113 | Write-Host "LibreOffice $($version) installed." 114 | } 115 | else { 116 | Write-Host "LibreOffice $($version) or greater already installed." 117 | $Install = $false 118 | } 119 | } 120 | } 121 | else { 122 | Write-Host "No LibreOffice version detected." 123 | } 124 | 125 | if ($Install) { 126 | Write-Host "Starting installation of LibreOffice $($loExeVersion) and exiting." 127 | $MSIArgs = "/i $($InstallPath) " + $MSIArguments + " /l $($env:LOCALAPPDATA)\InstallLibreOfficeInstallation.txt" 128 | $exitCode = (Start-Process "msiexec.exe" -ArgumentList $MSIArgs -Wait -NoNewWindow -PassThru).ExitCode 129 | get-content "$($env:LOCALAPPDATA)\InstallLibreOfficeInstallation.txt" 130 | if ($exitCode -eq 0) { 131 | Write-Host "LibreOffice installed sucessfully" 132 | } 133 | else { 134 | Write-Host "Error $exitCode installing LibreOffice" 135 | } 136 | } 137 | 138 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { Stop-Transcript } 139 | -------------------------------------------------------------------------------- /Install LibreOffice/readme.md: -------------------------------------------------------------------------------- 1 | # Install or update LibreOffice on domain computers using GPO and PowerShell. 2 | 3 | Install LibreOffice if previous or no version was detected. 4 | 5 | This script install LibreOffice only if a previous version or no version was detected. 6 | 7 | [Install-LibreOffice](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20LibreOffice/Install-LibreOffice.ps1): script to install LibreOffice with GPO. 8 | 9 | ## Instructions 10 | 11 | 1. Create shared folder to deploy LibreOffice: ```\\FILESERVER-01\LibreOffice``` 12 | 2. Grant 'Authenticated users' read access to ```\\FILESERVER-01\LibreOffice``` 13 | 3. Copy ```Install-LibreOffice.ps1``` to ```\\FILESERVER-01\LibreOffice\Install-LibreOffice.ps1``` 14 | 4. Create shared folder for logs: ```\\FILESERVER-01\LibreOffice\logs``` 15 | 5. Grant 'Authenticated users' write access to ```\\FILESERVER-01\LibreOffice\logs``` 16 | 6. Create a computer GPO that runs PowerShell Script: 17 | ``` 18 | Name: \\FILESERVER-01\JRE\Install-LibreOffice.ps1 19 | Parameters: -InstallPath "\\INFSRV003\Software$\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi" -LogPath"\\INFSRV003\Software$\LibreOffice\Logs" 20 | ``` 21 | ## Parameters 22 | 23 | **InstallPath:** LibreOffice installer path. 24 | 25 | **LogPath:** Path where save log file. 26 | 27 | **MSIArguments:** LibreOffice installation options (optional). 28 | 29 | ```powershell 30 | # Install LibreOffice 31 | \\INFSRV003\Software$\LibreOffice\Install-LibreOffice.ps1 -InstallPath "\\INFSRV003\Software$\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi" -LogPath"\\INFSRV003\Software$\LibreOffice\Logs" 32 | ``` 33 | -------------------------------------------------------------------------------- /Install MSI/Install-MSI.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Install any MSI if previous or no version detected 4 | Allows to update installed software and check the result of the installation using a log file. 5 | Is a more complete alternative to the msi installation via gpo. 6 | .DESCRIPTION 7 | This script any MSI only if a previous version or no version was detected. 8 | Allows to update installed software and check the result of the installation using a log file. 9 | Is a more complete alternative to the msi installation via gpo. 10 | .PARAMETER InstallPath 11 | MSI full installer path 12 | Example: \\FILESERVER-01\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi 13 | .PARAMETER SearchName 14 | Name of the application to search for it in the registry in order to get the version installed. 15 | It does not need to be the exact name, but search by this name must return only one item or nothing. 16 | You can simulate the search using the command: 17 | Get-WmiObject Win32_Product | Where-Object {$_.Name -like '*Office*'} 18 | .PARAMETER LogPath 19 | Log path (optional). ComputerName.log file will be created. 20 | Example: \\FILESERVER-01\LibreOffice\Logs (Log will be saved to \\FILESERVER-01\JRE\computername.log) 21 | .PARAMETER MSIArguments 22 | Parameters of MSI file. 23 | Warning! There seems to be a maximum number of 256 characters that can be used in the Script Parameters setting in a GPO Startup/Shutdown/Logon/Logoff PowerShell script. 24 | Sometimes scripts do not run even with fewer characters, so you can create a script that calls this script with all its parameters and run it via GPO. 25 | Optional, /qn is already applied. 26 | .EXAMPLE 27 | Install LibreOffice from network share, saving log in Log folder of network share. 28 | Note: network share must have read permissions on "\\FILESERVER-01\LibreOffice\" and write on "\\FILESERVER-01\LibreOffice\Logs" for "Authenticated Users" group in order to run it with GPO. 29 | Install-MSI.ps1 -InstallPath "\\FILESERVER-01\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi" -SearchName "LibreOffice" -LogPath "\\FILESERVER-01\LibreOffice\Logs" -MSIArguments "UI_LANGS=en_US,es" 30 | .NOTES 31 | Author: Juan Granados 32 | Date: November 2023 33 | #> 34 | Param( 35 | [Parameter(Mandatory = $true, Position = 0)] 36 | [ValidateNotNullOrEmpty()] 37 | [string]$InstallPath, 38 | [Parameter(Mandatory = $true, Position = 1)] 39 | [ValidateNotNullOrEmpty()] 40 | [string]$SearchName, 41 | [Parameter(Mandatory = $false, Position = 2)] 42 | [ValidateNotNullOrEmpty()] 43 | [string]$LogPath, 44 | [Parameter(Mandatory = $false, Position = 3)] 45 | [ValidateNotNullOrEmpty()] 46 | [string]$MSIArguments 47 | ) 48 | #Requires -RunAsAdministrator 49 | function Get-InstalledApps { 50 | if ([IntPtr]::Size -eq 4) { 51 | $regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 52 | } 53 | else { 54 | $regpath = @( 55 | 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 56 | 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' 57 | ) 58 | } 59 | Get-ItemProperty $regpath | . { process { if ($_.DisplayName -and $_.UninstallString) { $_ } } } | 60 | Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString | 61 | Sort-Object DisplayVersion 62 | } 63 | 64 | function StringVersionToFloat($version) { 65 | while (($version.ToCharArray() | Where-Object { $_ -eq '.' } | Measure-Object).Count -gt 1) { 66 | $aux = $version.Substring($version.LastIndexOf('.') + 1) 67 | $version = $version.Substring(0, $version.LastIndexOf('.')) + $aux 68 | } 69 | return [float]$version 70 | } 71 | 72 | function Get-MSIFileVersion ([System.IO.FileInfo]$msiFilePath) { 73 | try { 74 | $WindowsInstaller = New-Object -com WindowsInstaller.Installer 75 | $Database = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $WindowsInstaller, @($msiFilePath.FullName, 0)) 76 | $Query = "SELECT Value FROM Property WHERE Property = 'ProductVersion'" 77 | $View = $database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $Database, ($Query)) 78 | $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null) | Out-Null 79 | $Record = $View.GetType().InvokeMember( "Fetch", "InvokeMethod", $Null, $View, $Null ) 80 | $Version = $Record.GetType().InvokeMember( "StringData", "GetProperty", $Null, $Record, 1 ) 81 | return $Version 82 | } 83 | catch { 84 | Write-Host "Failed to get MSI file version: {0}." -f $_ 85 | Stop-Transcript 86 | Exit 87 | } 88 | } 89 | function Get-MSIFileName ([System.IO.FileInfo]$msiFilePath) { 90 | try { 91 | $WindowsInstaller = New-Object -com WindowsInstaller.Installer 92 | $Database = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $WindowsInstaller, @($msiFilePath.FullName, 0)) 93 | $Query = "SELECT Value FROM Property WHERE Property = 'ProductName'" 94 | $View = $database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $Database, ($Query)) 95 | $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null) | Out-Null 96 | $Record = $View.GetType().InvokeMember( "Fetch", "InvokeMethod", $Null, $View, $Null ) 97 | $ProductName = $Record.GetType().InvokeMember( "StringData", "GetProperty", $Null, $Record, 1 ) 98 | return $ProductName 99 | } 100 | catch { 101 | Write-Host "Failed to get MSI file ProductName: {0}." -f $_ 102 | Stop-Transcript 103 | Exit 104 | } 105 | } 106 | 107 | if (-not [string]::IsNullOrWhiteSpace($LogPath) -and $logPath.Chars($logPath.Length - 1) -eq '\') { 108 | $logPath = ($logPath.TrimEnd('\')) 109 | } 110 | if (Test-Path -Path $logPath) { 111 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { 112 | try { Stop-Transcript | Out-Null } catch {} 113 | $logFilename = "$($env:COMPUTERNAME)_$($SearchName).txt" 114 | $logFilename.Split([IO.Path]::GetInvalidFileNameChars()) -join '_' | Out-Null 115 | Start-Transcript -Path "$($LogPath)\$($logFilename)" | Out-Null 116 | } 117 | } 118 | else { 119 | Write-Host "Log path does not exists" 120 | Exit 121 | } 122 | 123 | $ErrorActionPreference = "Stop" 124 | $Install = $true 125 | 126 | try { 127 | $MSIFile = Get-Item -Path $InstallPath 128 | } 129 | catch { 130 | Write-Error "Error accessing $($InstallPath)." 131 | Write-Error "$($Error[0])" 132 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { Stop-Transcript } 133 | Exit 1 134 | } 135 | 136 | $MSIVersion = StringVersionToFloat $(Get-MSIFileversion $MSIFile) 137 | $MSIName = $(Get-MSIFileName $MSIFile).ToString() 138 | 139 | Write-Host "$($MSIName) version $($MSIVersion) selected for installation." 140 | 141 | $widcardSearch = "*$($SearchName)*" 142 | $installedVersion = Get-InstalledApps | Where-Object { $_.DisplayName -like $widcardSearch } 143 | 144 | if ($installedVersion) { 145 | 146 | foreach ($installation in $installedVersion) { 147 | 148 | $version = StringVersionToFloat $installation.DisplayVersion 149 | 150 | if (($version -lt $MSIVersion)) { 151 | Write-Host "Version $($version) installed." 152 | } 153 | else { 154 | Write-Host "Version $($version) or greater already installed." 155 | $Install = $false 156 | } 157 | } 158 | } 159 | else { 160 | Write-Host "No $($MSIName) detected." 161 | } 162 | 163 | if ($Install) { 164 | $msiLog = "$($env:LOCALAPPDATA)\msi_Installation.txt" 165 | if (Test-Path $msiLog) { 166 | Remove-Item $msiLog -Force 167 | } 168 | if (-not ([string]::IsNullOrEmpty($MSIArguments))) { 169 | $MSIArgs = "/i `"$($InstallPath)`" /qn $($MSIArguments) /l $($msiLog)" 170 | } else { 171 | $MSIArgs = "/i `"$($InstallPath)`" /qn /l $($msiLog)" 172 | } 173 | Write-Host "Starting installation of $($MSIName) version $($MSIVersion) and exiting." 174 | Write-Host "Running: msiexec.exe $($MSIArgs)" 175 | $exitCode = (Start-Process "msiexec.exe" -ArgumentList $MSIArgs -Wait -NoNewWindow -PassThru).ExitCode 176 | get-content $msiLog 177 | if ($exitCode -eq 0) { 178 | Write-Host "$($MSIName) installed sucessfully" 179 | } 180 | else { 181 | Write-Host "Error $exitCode installing $($MSIName)" 182 | } 183 | } 184 | 185 | if (-not [string]::IsNullOrWhiteSpace($LogPath)) { Stop-Transcript } 186 | -------------------------------------------------------------------------------- /Install MSI/readme.md: -------------------------------------------------------------------------------- 1 | # Install or update any MSI on domain computers using GPO and PowerShell. 2 | 3 | Install any MSI if previous or no version detected 4 | 5 | Allows to update installed software and check the result of the installation using a log file. 6 | 7 | Is a more complete alternative to the msi installation via gpo. 8 | 9 | [Install-MSI](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20MSI/Install-MSI.ps1): script to install MSI with GPO. 10 | 11 | ## Parameters 12 | 13 | **InstallPath** 14 | MSI full installer path 15 | Example: \\FILESERVER-01\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi 16 | 17 | **SearchName** 18 | Name of the application to search for it in the registry in order to get the version installed. 19 | It does not need to be the exact name, but search by this name must return only one item or nothing. 20 | You can simulate the search using the command: 21 | Get-WmiObject Win32_Product | Where-Object {$_.Name -like '*Office*'} 22 | 23 | **LogPath** 24 | Log path (optional). ComputerName.log file will be created. 25 | Example: \\FILESERVER-01\LibreOffice\Logs (Log will be saved to \\FILESERVER-01\JRE\computername.log) 26 | 27 | **MSIArguments** 28 | Parameters of MSI file. 29 | Warning! There seems to be a maximum number of 256 characters that can be used in the Script Parameters setting in a GPO Startup/Shutdown/Logon/Logoff PowerShell script. 30 | Sometimes scripts do not run even with fewer characters, so you can create a script that calls this script with all its parameters and run it via GPO. 31 | Optional, /qn is already applied. 32 | 33 | ## Instructions for deploying LibreOffice MSI 34 | 35 | 1. Create shared folder to deploy LibreOffice: ```\\FILESERVER-01\LibreOffice``` 36 | 2. Grant 'Authenticated users' read access to ```\\FILESERVER-01\LibreOffice``` 37 | 3. Copy ```Install-MSI.ps1``` to your script folder ```\\FILESERVER-01\Scripts$\Install-MSI.ps1``` 38 | 4. Create shared folder for logs: ```\\FILESERVER-01\LibreOffice\logs``` 39 | 5. Grant 'Authenticated users' write access to ```\\FILESERVER-01\LibreOffice\logs``` 40 | 6. Create a powershell script named _Install-LibreOffice.ps1_ to avoid GPO parameter limit and run the installation on background: 41 | ``` 42 | Start-Job -ScriptBlock {\\FILESERVER-01\Scripts$\Install-MSI.ps1 -InstallPath "\\FILESERVER-01\Software$\LibreOffice\LibreOffice_7.5.8_Win_x86-64.msi" -LogPath "\\FILESERVER-01\Software$\LibreOffice\Logs" -SearchName "LibreOffice" -MSIArguments "/norestart ALLUSERS=1 CREATEDESKTOPLINK=0 REGISTER_ALL_MSO_TYPES=0 REGISTER_NO_MSO_TYPES=1 ISCHECKFORPRODUCTUPDATES=0 QUICKSTART=0 ADDLOCAL=ALL UI_LANGS=en_US,ca,es"} 43 | ``` 44 | 7. Create a GPO that runs _Install-LibreOffice.ps1_: 45 | -------------------------------------------------------------------------------- /Install Print Drivers Remotely/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Install Print Drivers Remotely/1.PNG -------------------------------------------------------------------------------- /Install Print Drivers Remotely/2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Install Print Drivers Remotely/2.PNG -------------------------------------------------------------------------------- /Install Print Drivers Remotely/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Install Print Drivers Remotely/3.png -------------------------------------------------------------------------------- /Install Print Drivers Remotely/Install-PrinterDriversRemotely.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.0 4 | 5 | .GUID ede5f6c5-50d3-42a9-b958-daff4b31972d 6 | 7 | .AUTHOR Juan Granados 8 | 9 | .COPYRIGHT 2021 Juan Granados 10 | 11 | .TAGS Install Printer Drivers Remote Remotely PrintNightmare PrinterExport 12 | 13 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 14 | 15 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Install%20Print%20Drivers%20Remotely 16 | 17 | .RELEASENOTES 18 | Initial release 19 | #> 20 | 21 | <# 22 | .SYNOPSIS 23 | Install printer drivers export file (*.printerExport) in a list of computers. 24 | .DESCRIPTION 25 | Install printer drivers export file (*.printerExport) in a list of computers (plain computer list, OU or CSV file) using PSExec. 26 | To generate a printer export file with all print drivers, run PrintbrmUI.exe from the computer or server you want to export them. 27 | Be carefully because this tool exports all printers and ports too. I recommend install all drivers in a test computer without printers and export them using PrintbrmUI.exe. 28 | Alternatively, you can export all from print server, import in a test computer, delete printers and ports and export again to obtain a printerExport file with only drivers. 29 | If PSExec is not found on computer, script asks to the user for download it and extract in system folder. 30 | I recommend use PSExec latest version because v2.2 does not launch printbrm.exe properly. 31 | .PARAMETER printerExportFile 32 | Path to the printerExport file 33 | To generate a printer export file with all print drivers, run PrintbrmUI.exe from the computer or server you want to export them. 34 | Be carefully because this tool exports all printers and ports too. I recommend install all drivers in a test computer without printers and export them using PrintbrmUI.exe. 35 | Alternatively, you can export all from print server, import in a test computer, delete printers and ports and export again to obtain a printerExport file with only drivers. 36 | .PARAMETER ComputerList 37 | List of computers in install printer drivers. You can only use one source of target computers: ComputerList, OU or CSV. 38 | Example: SRV-RDSH-001,SRV-RDSH-002,SRV-RDSH-003 (Without quotation marks) 39 | .PARAMETER OU 40 | OU containing computers in which install printer drivers. 41 | RSAT for AD module for PowerShell must be installed in order to query AD. 42 | - Install on Windows 10: Get-WindowsCapability -Online |? {$_.Name -like "*RSAT.ActiveDirectory*" -and $_.State -eq "NotPresent"} | Add-WindowsCapability -Online 43 | - Install on server: Install-WindowsFeature RSAT-AD-PowerShell 44 | Restart console after installation. 45 | If you run script from a Domain Controller, AD module for PowerShell is already enabled. 46 | You can only use one source of target computers: ComputerList, OU or CSV. 47 | Example: 'OU=Test,OU=Computers,DC=CONTOSO,DC=COM' 48 | .PARAMETER CSV 49 | CSV file containing computers in which install printer drivers. You can only use one source of target computers: ComputerList, OU or CSV. 50 | Example: 'C:\Scripts\Computers.csv' 51 | CSV Format: 52 | Name 53 | Computer001 54 | Computer002 55 | Computer003 56 | .PARAMETER LogPath 57 | Path where save log file. 58 | Default: My Documents 59 | Example: C:\Logs 60 | .PARAMETER Credential 61 | Script will ask for an account to perform remote installation. 62 | .EXAMPLE 63 | Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -OU "OU=RDS,OU=Datacenter,DC=CONTOSO,DC=COM" 64 | .EXAMPLE 65 | Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -ComputerList SRVRSH-001,SRVRSH-002,SRVRSH-003 -Credential -LogPath C:\Temp\Logs 66 | .EXAMPLE 67 | Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -CSV "C:\scripts\computers.csv" 68 | .LINK 69 | https://github.com/juangranados/powershell-scripts/tree/main/Install%20Print%20Drivers%20Remotely 70 | .NOTES 71 | Author: Juan Granados 72 | #> 73 | 74 | Param( 75 | [Parameter(Mandatory=$true,Position=0)] 76 | [ValidateNotNullOrEmpty()] 77 | [string]$printerExportFile, 78 | [Parameter(Mandatory=$false,Position=1)] 79 | [ValidateNotNullOrEmpty()] 80 | [string]$LocalPath="C:\temp", 81 | [Parameter(Mandatory=$false,Position=2)] 82 | [ValidateNotNullOrEmpty()] 83 | [string[]]$ComputerList, 84 | [Parameter(Mandatory=$false,Position=3)] 85 | [ValidateNotNullOrEmpty()] 86 | [string]$OU, 87 | [Parameter(Mandatory=$false,Position=4)] 88 | [ValidateNotNullOrEmpty()] 89 | [string]$CSV, 90 | [Parameter(Mandatory=$false,Position=5)] 91 | [ValidateNotNullOrEmpty()] 92 | [string]$LogPath=[Environment]::GetFolderPath("MyDocuments"), 93 | [Parameter(Position=6)] 94 | [switch]$Credential 95 | ) 96 | 97 | #Requires -RunAsAdministrator 98 | 99 | #Functions 100 | 101 | Add-Type -AssemblyName System.IO.Compression.FileSystem 102 | Import-Module BitsTransfer 103 | 104 | function Unzip 105 | { 106 | param([string]$zipfile, [string]$outpath) 107 | 108 | [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) 109 | } 110 | 111 | $ErrorActionPreference = "Stop" 112 | 113 | #Initialice log 114 | 115 | $LogPath += "\InstallPrinterExportRemoteComputers_" + $(get-date -Format "yyyy-mm-dd_hh-mm-ss") + ".txt" 116 | Start-Transcript $LogPath 117 | Write-Host "Start remote installation on $(get-date -Format "yyyy-mm-dd hh:mm:ss")" 118 | 119 | #Initial validations. 120 | 121 | If (!(Test-Path $printerExportFile)){ 122 | Write-Host "Error accessing $($printerExportFile). Script can not continue" 123 | Stop-Transcript 124 | Exit 1 125 | } 126 | if (-not($printerExportFile -Like "*.printerExport")) { 127 | Write-Host "File $($printerExportFile) does not has .printerExport extension. Script can not continue" 128 | Stop-Transcript 129 | Exit 1 130 | } 131 | 132 | if (!(Get-Command "psexec.exe" -ErrorAction SilentlyContinue)){ 133 | Write-Host "Error. Microsoft Psexec not found on system. Download it from https://download.sysinternals.com/files/PSTools.zip and extract all in C:\Windows\System32" -ForegroundColor Yellow 134 | $Answer=Read-Host "Do you want to download and install PSTools (y/n)?" 135 | if (($Answer -eq "y") -or ($Answer -eq "Y")){ 136 | Write-Host "Downloading PSTools" 137 | If (Test-Path "$($env:temp)\PSTools.zip"){ 138 | Remove-Item "$($env:temp)\PSTools.zip" -Force 139 | } 140 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 141 | (New-Object System.Net.WebClient).DownloadFile("https://download.sysinternals.com/files/PSTools.zip", "$($env:temp)\PSTools.zip") 142 | if (Test-Path "$($env:temp)\PSTools.zip"){ 143 | Write-Host "Unzipping PSTools" 144 | If (Test-Path "$($env:temp)\PSTools"){ 145 | Remove-Item "$($env:temp)\PSTools" -Force -Recurse 146 | } 147 | Unzip "$($env:temp)\PSTools.zip" "$($env:temp)\PSTools" 148 | Copy-Item "$($env:temp)\PSTools\*.exe" "$($env:SystemRoot)\System32" -Force 149 | if (Test-Path "$($env:SystemRoot)\System32\psexec.exe"){ 150 | Write-Host "PSTools installed" -ForegroundColor Green 151 | } 152 | else{ 153 | Write-Host "Error unzipping PSTools" -ForegroundColor Red 154 | Remove-Item "$($env:temp)\PSTools.zip" -Force 155 | Stop-Transcript 156 | Exit 1 157 | } 158 | } 159 | else{ 160 | Write-Host "Error downloading PSTools" -ForegroundColor Red 161 | Stop-Transcript 162 | Exit 1 163 | } 164 | } 165 | else{ 166 | Stop-Transcript 167 | Exit 1 168 | } 169 | } 170 | 171 | If ($OU){ 172 | if (!(Get-Command "Get-ADComputer" -ErrorAction SilentlyContinue)){ 173 | Write-Host "Error. Get-ADComputer not found on system. You have to install the PowerShell Active Directory module order to query Active Directory." -ForegroundColor Red 174 | Write-Host 'Windows 10: Get-WindowsCapability -Online |? {$_.Name -like "*RSAT.ActiveDirectory*" -and $_.State -eq "NotPresent"} | Add-WindowsCapability -Online' 175 | Write-Host "Server: Install-WindowsFeature RSAT-AD-PowerShell" 176 | Write-Host "Restart console after installation" 177 | Stop-Transcript 178 | Exit 1 179 | } 180 | try{ 181 | $ComputerList = Get-ADComputer -Filter * -SearchBase "$OU" | Select-Object -Expand name 182 | }catch{ 183 | Write-Host "Error querying AD: $($_.Exception.Message)" -ForegroundColor Red 184 | Stop-Transcript 185 | Exit 1 186 | } 187 | } 188 | ElseIf ($CSV){ 189 | try{ 190 | $ComputerList = Get-Content $CSV | where {$_ -notmatch 'Name'} | Foreach-Object {$_ -replace '"', ''} 191 | }catch{ 192 | Write-Host "Error getting CSV content: $($_.Exception.Message)" -ForegroundColor Red 193 | Stop-Transcript 194 | Exit 1 195 | } 196 | } 197 | ElseIf(!$ComputerList){ 198 | Write-Host "You have to set a list of computers, OU or CSV." -ForegroundColor Red 199 | Stop-Transcript 200 | Exit 1 201 | } 202 | If ($Credential){ 203 | $Cred = Get-Credential 204 | } 205 | $usingCredential = $false 206 | If(!$Cred -or !$Credential){ 207 | Write-Host "No credential specified. Using logon account" 208 | } 209 | Else{ 210 | $usingCredential = $true; 211 | Write-Host "Using user $($Cred.UserName)" 212 | $UserName = $Cred.UserName 213 | $Password = $Cred.GetNetworkCredential().Password 214 | } 215 | ForEach ($Computer in $ComputerList) { 216 | Write-Host "Processing computer $Computer" 217 | $Destination = "\\$Computer\C$\Temp\printer_drivers.printerExport" 218 | try { 219 | if (-not(Test-Path "\\$Computer\C$\Temp\")) { 220 | mkdir "\\$Computer\C$\Temp\" 221 | } 222 | Start-BitsTransfer -Source $printerExportFile -Destination $Destination -Description "Copy $driverFile to $Computer" -DisplayName "Copying" 223 | } catch { 224 | Write-Host "Error copying file: $($_.Exception.Message)" 225 | continue 226 | } 227 | Write-Host "Launching installation using PSExec in $Computer. This may take a while, please be patient..." 228 | try { 229 | if ($usingCredential) { 230 | psexec.exe /accepteula -h -i "\\$Computer" -u $UserName -p $Password C:\Windows\System32\spool\tools\Printbrm.exe -F C:\Temp\printer_drivers.printerExport -R 231 | } 232 | else { 233 | psexec /accepteula -h "\\$Computer" C:\Windows\System32\spool\tools\Printbrm.exe -F C:\Temp\printer_drivers.printerExport -R 234 | } 235 | } catch { 236 | Write-Host "PSExec return an error. Check console output above" 237 | } 238 | 239 | try { 240 | Write-Host "Removing remote file" 241 | Remove-Item $Destination -Force; 242 | } catch { 243 | Write-Host "Error removing remote file: $($_.Exception.Message)" -ForegroundColor Red 244 | } 245 | } 246 | Stop-Transcript -------------------------------------------------------------------------------- /Install Print Drivers Remotely/readme.md: -------------------------------------------------------------------------------- 1 | # PrintNightmare - Install printer drivers on remote computers using printerExport file 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Print%20Drivers%20Remotely/Install-PrinterDriversRemotely.ps1) 4 | 5 | ![Screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Print%20Drivers%20Remotely/3.png) 6 | 7 | On August 10, Microsoft posted a [blog post](https://msrc-blog.microsoft.com/2021/08/10/point-and-print-default-behavior-change/) about changes to the point and print. 8 | 9 | After the August patches, standard users cant add any printers. This means that you need to pre-install all drivers on your workstations or Remote Session Host servers. 10 | 11 | In [KB5005652](https://support.microsoft.com/topic/873642bf-2634-49c5-a23b-6d8e9a302872) documentation, Microsoft recommends this four possible solutions after install KB5005652 patch: 12 | 13 | ![Solutions to KB5005652](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Print%20Drivers%20Remotely/1.PNG) 14 | 15 | This script allows remote printer drivers installation on a group of computers using printerExport file and [PSExec](https://docs.microsoft.com/en-us/sysinternals/downloads/pstools). If PSExec is not found on computer, script asks to the user for download it and extract in system folder. I recommend use PSExec latest version because v2.2 does not launch printbrm.exe properly. 16 | 17 | To generate a printer export file with all printer drivers, run `PrintbrmUI.exe` from the computer or server you want to export them. 18 | 19 | Be carefully because this tool exports all printers and ports too. I recommend install all drivers in a test computer without printers and export them using `PrintbrmUI.exe`. Alternatively, you can export all from print server, import in a test computer, delete printers and ports and export again to obtain a printerExport file with only drivers. 20 | 21 | ![PrintbrmUI screenshot](https://github.com/juangranados/powershell-scripts/blob/main/Install%20Print%20Drivers%20Remotely/2.PNG?raw=true) 22 | 23 | ## Parameters 24 | 25 | **printerExportFile**: Path to the printerExport file. 26 | 27 | *Example: \\\SRVFS01\Drivers\drivers.printerExport* 28 | 29 | **ComputerList**: List of computers in install printer drivers. You can only use one source of target computers: ComputerList, OU or CSV. 30 | 31 | *Example: Computer001,Computer002,Computer003 (Without quotation marks)* 32 | 33 | **OU**: OU containing computers in which install printer drivers. You can only use one source of target computers: ComputerList, OU or CSV. 34 | RSAT for AD module for PowerShell must be installed in order to query AD. 35 | If you run script from a Domain Controller, AD module for PowerShell is already enabled. 36 | 37 | To install it from Windows 10 computer 38 | 39 | ```powershell 40 | Get-WindowsCapability -Online |? {$_.Name -like "*RSAT.ActiveDirectory*" -and $_.State -eq "NotPresent"} | Add-WindowsCapability -Online 41 | ``` 42 | 43 | To install it from server 44 | 45 | ```powershell 46 | Install-WindowsFeature RSAT-AD-PowerShell 47 | ``` 48 | 49 | *Example: : "OU=RDSH,OU=Servers,DC=CONTOSO,DC=COM"* 50 | 51 | **CSV**: CSV file containing computers in which install printer drivers. You can only use one source of target computers: ComputerList, OU or CSV. 52 | 53 | *Example: "C:\Scripts\Computers.csv"* 54 | CSV Format: 55 | 56 | ```CSV 57 | Name 58 | RDSH01 59 | RDSH03 60 | RDSH02 61 | ``` 62 | 63 | **LogPath:** Path where save log file. 64 | Default: My Documents 65 | 66 | *Example: C:\Logs* 67 | 68 | **Credential**: Script will ask for an account to perform remote installation. 69 | 70 | ## Remote installation examples 71 | ```powershell 72 | Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -OU "OU=RDS,OU=Datacenter,DC=CONTOSO,DC=COM" 73 | Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -ComputerList SRVRSH-001,SRVRSH-002,SRVRSH-003 -Credential -LogPath C:\Temp\Logs 74 | Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -CSV "C:\scripts\computers.csv" 75 | ``` 76 | -------------------------------------------------------------------------------- /Install Software Remotely/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Install Software Remotely/Screenshot.png -------------------------------------------------------------------------------- /Install Software Remotely/Screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Install Software Remotely/Screenshot2.png -------------------------------------------------------------------------------- /Install Software Remotely/readme.md: -------------------------------------------------------------------------------- 1 | # Install software on multiple computers remotely with PowerShell 2 | 3 | Right click here and select "Save link as" to download 4 | 5 | This script install software remotely in a group of computers and retry the installation in case of error. It uses PowerShell to perform remote installation. 6 | 7 | *Screenshot of Edge remote installation on 20 RDS Servers.* 8 | 9 | ![screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Software%20Remotely/Screenshot2.png) 10 | 11 | *Screenshot of TightVNC remote installation on 77 computers.* 12 | 13 | ![screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Software%20Remotely/Screenshot.png) 14 | 15 | As it uses Powershell to perform the remote installation, target computer must allow Windows PowerShell Remoting. Script can try to enable Windows PowerShell Remoting using Microsoft Sysinternals Psexec with the paramenter -EnablePSRemoting. If PSExec is not found on computer, script asks to the user for download it and extract to system folder. 16 | 17 | Please, read parameter description carefully before running. 18 | 19 | **AppPath:** Path to the application executable, It can be a network or local path because entire folder will be copied to remote computer before installing and deleted after installation. 20 | 21 | ​ *Example:* `C:\Software\TeamViewer\TeamvieverHost.msi` *(Folder TeamViewer will be copied to remote computer before run ejecutable).* 22 | 23 | **AppArgs:** Application arguments to perform silent installation. 24 | 25 | ​ *Example:* `/S /R settings.reg` 26 | 27 | **LocalPath:** Local path of the remote computer where copy application directory. 28 | 29 | ​ *Default:* `C:\temp` 30 | 31 | **Retries:** Number of times to retry failed installations. 32 | 33 | ​ *Default: 5.* 34 | 35 | **TimeBetweenRetries:** Seconds to wait before retrying failed installations. 36 | 37 | ​ *Default: 60* 38 | 39 | **ComputerList:** List of computers in install software. You can only use one source of target computers: ComputerList, OU or CSV. 40 | 41 | ​ *Example:* `Computer001,Computer002,Computer003` *(Without quotation marks)* 42 | 43 | **OU:** OU containing computers in which install software. RSAT for AD module for Powershell must be installed in order to query AD. If you run script from a Domain Controller, AD module for PowerShell is already enabled. 44 | 45 | ​ *Example:* `OU=Test,OU=Computers,DC=CONTOSO,DC=COM` 46 | 47 | **CSV:** CSV file containing computers in which install software. 48 | 49 | ​ *Example:* `C:\Scripts\Computers.csv` 50 | 51 | ​ *CSV Format:* 52 | 53 | ​ `Name` 54 | 55 | ​ `Computer001` 56 | 57 | ​ `Computer002` 58 | 59 | ​ `Computer003.` 60 | 61 | **LogPath:** Path where save log file. 62 | 63 | *Default: My Documents.* 64 | 65 | **Credential:** Script will ask for an account to perform remote installation. 66 | 67 | **EnablePSRemoting:** Try to enable PSRemoting on failed computers using Psexec. Psexec has to be on system path. If PSExec is not found. Script ask to download automatically PSTools and copy them to C:\Windows\System32. 68 | 69 | **AppName:** App name as shown in registry to check if app is installed on remote computer and not reinstall it. 70 | 71 | You can check app name on a computer with it installed looking at: 72 | 73 | ​ `HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\` 74 | 75 | ​ `HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\` 76 | 77 | ​ *Example: 'TightVNC'* 78 | 79 | ​ *Default: None* 80 | 81 | **AppVersion:** App version as shown in registry. If not specified and AppName has a value, version will be ignored. 82 | If computer app version is lower than AppVersion variable, software will be installed and if it is equal or greater, software installation will be skipped. 83 | 84 | You can check app version on a computer with it installed looking at: 85 | 86 | ​ `HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\` 87 | 88 | ​ ` HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\` 89 | 90 | ​ *Example: '2.0.8.1'* 91 | 92 | ​ *Default: all* 93 | 94 | **WMIQuery:** WMI Query to execute in remote computers. Software will be installed if query returns any value. 95 | 96 | ​ *Example:* `select * from Win32_Processor where DeviceID="CPU0" and AddressWidth="64"' (64 bit computers)` 97 | 98 | ​ *Example:* `select * from Win32_Processor where DeviceID="CPU0" and AddressWidth="32"' (32 bit computers)` 99 | 100 | ​ *Default: None* 101 | 102 | ## Examples 103 | 104 | Thanks Terence Luk for his amazing post: 105 | 106 | [http://terenceluk.blogspot.com/2019/02/using-installsoftwareremotelyps1-to.html](https://web.archive.org/web/20200318211458/http://terenceluk.blogspot.com/2019/02/using-installsoftwareremotelyps1-to.html) 107 | 108 | Other examples: 109 | ```powershell 110 | # Edge remote installation on all servers in OU 111 | Install-SoftwareRemotely.ps1 -AppPath "\\HSRV-FS02\Scripts\InstallSoftware\MicrosoftEdgeEnterpriseX64.msi" -AppArgs '/qn' -OU "OU=RDS,OU=Datacenter,DC=CONTOSO,DC=COM" -LogPath '\\HSRV-FS02\Scripts\InstallSoftware\Logs' 112 | ``` 113 | 114 | ```powershell 115 | #Install TightVNC mirage Driver using computer list with different credentials checking before if it is installed and computers have 32 bits, enabling PSRemoting on connection error. 116 | Install-SoftwareRemotely.ps1 ` 117 | -AppPath 'C:\Scripts\TightVNC\dfmirage-setup-2.0.301.exe' ` 118 | -AppArgs '/verysilent /norestart' ` 119 | -ComputerList PC01,PC03,PC12,PC34,PC43,PC50 ` 120 | -Retries 2 ` 121 | -AppName 'DemoForge Mirage Driver for TightVNC 2.0' ` 122 | -AppVersion '2.0' ` 123 | -WMIQuery 'select * from Win32_Processor where DeviceID="CPU0" and AddressWidth="32"' ` 124 | -EnablePSRemoting ` 125 | -Credential 126 | ``` 127 | ```powershell 128 | #Install TightVNC on 64 bits computers in a OU checking before if it is installed and enablig PSRemoting on connection error. 129 | Install-SoftwareRemotely.ps1 ` 130 | -AppPath 'C:\Scripts\TightVNC\tightvnc-2.8.8-gpl-setup-64bit.msi' ` 131 | -AppArgs '/quiet /norestart ADDLOCAL="Server" SERVER_REGISTER_AS_SERVICE=1 SERVER_ADD_FIREWALL_EXCEPTION=1 SERVER_ALLOW_SAS=1 SET_USEVNCAUTHENTICATION=1 VALUE_OF_USEVNCAUTHENTICATION=1 SET_PASSWORD=1 VALUE_OF_PASSWORD=P@ssw0rd SET_USECONTROLAUTHENTICATION=1 VALUE_OF_USECONTROLAUTHENTICATION=1 SET_CONTROLPASSWORD=1 VALUE_OF_CONTROLPASSWORD=P@ssw0rd' ` 132 | -OU 'OU=Central,OU=Computers,DC=Contoso,DC=local' ` 133 | -Retries 2 ` 134 | -AppName 'TightVNC' ` 135 | -AppVersion '2.8.8.0' ` 136 | -WMIQuery 'select * from Win32_Processor where DeviceID="CPU0" and AddressWidth="64"' ` 137 | -EnablePSRemoting 138 | ``` 139 | ```powershell 140 | #Upgrade VMware Tools in datacenter OU 141 | Install-SoftwareRemotely.ps1 -AppPath "\\mv-srv-fs01\Software\VMware Tools\setup64.exe" -AppArgs '/s /v "/qn reboot=r"' -OU "OU=Datacenter,DC=CONTOSO,DC=COM" 142 | ``` 143 | -------------------------------------------------------------------------------- /Install Software/Install-Software.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Install/Update/Uninstall software using RZGet repository from https://ruckzuck.tools/ 4 | .DESCRIPTION 5 | Install/Update/Uninstall software using RZGet repository from https://ruckzuck.tools/ 6 | .PARAMETER tempFolder 7 | Folder to download installers 8 | Default: "C:\temp\InstallSoftware" 9 | .PARAMETER software 10 | List of software to install. Check https://ruckzuck.tools/Home/Repository 11 | Default: None 12 | Example: "7-Zip","Notepad++","Edge","3CXPhone for Windows","Google Chrome","Teams","Postman" 13 | .PARAMETER logFolder 14 | Log file path. 15 | Default: "C:\temp\InstallSoftware" 16 | Example: "\\ES-CPD-BCK02\scripts\InstallSoftware\Log" 17 | .PARAMETER uninstall 18 | Uninstall software if it is already installed 19 | .PARAMETER checkOnly 20 | Check if software list is already installed 21 | .PARAMETER runAsAdmin 22 | Check if script is elevated 23 | .PARAMETER sleep 24 | Amount of seconds to sleep. 25 | When running as logon script sometimes Windows profile is not ready and installation fails. 26 | Default 0 27 | .EXAMPLE 28 | .\Install-Software -tempFolder C:\temp\InstallSoftware -software "7-Zip","Notepad++","Edge","3CXPhone for Windows","Google Chrome","Teams","Postman" -logFolder "\\ES-CPD-BCK02\scripts\InstallSoftware\Log" 29 | .LINK 30 | https://github.com/juangranados/powershell-scripts/tree/main/Install%20Software%20Locally 31 | .NOTES 32 | Thanks to Roger Zander for his amazing tool: https://ruckzuck.tools/ 33 | Author: Juan Granados 34 | #> 35 | Param( 36 | [Parameter(Mandatory = $false)] 37 | [ValidateNotNullOrEmpty()] 38 | [string]$tempFolder = 'C:\temp\InstallSoftware', 39 | [Parameter(Mandatory = $true)] 40 | [string[]]$software, 41 | [Parameter(Mandatory = $false)] 42 | [string]$logFolder = 'C:\temp\InstallSoftware', 43 | [Parameter()] 44 | [switch]$uninstall, 45 | [Parameter()] 46 | [switch]$checkOnly, 47 | [Parameter()] 48 | [switch]$runAsAdmin, 49 | [Parameter()] 50 | [int]$sleep = 0 51 | ) 52 | function Set-Folder([string]$folderPath) { 53 | if ($folderPath.Chars($folderPath.Length - 1) -eq '\') { 54 | $folderPath = ($folderPath.TrimEnd('\')) 55 | } 56 | if (!(Test-Path $folderPath)) { 57 | try { 58 | New-Item $folderPath -ItemType directory 59 | } 60 | catch { 61 | Write-Error "Error creating $folderPath" 62 | try { 63 | Stop-Transcript 64 | } 65 | catch { Write-Warning $Error[0] } 66 | Exit 1 67 | } 68 | } 69 | } 70 | function Get-FileHashIsOk([string]$filePath, [string]$hashType, [string]$hash) { 71 | if ($hashType.ToUpper() -eq "X509") { 72 | $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($filePath) 73 | $certHash = $cert.GetCertHashString().ToLower().Replace(" ", "") 74 | if ($certHash -ne $hash) { 75 | return $false 76 | } 77 | else { 78 | return $true 79 | } 80 | } 81 | elseif (($hashType.ToUpper() -eq "MD5")) { 82 | $fileHash = Get-FileHash $filePath -Algorithm MD5 83 | if ($fileHash.Hash -ne $hash) { 84 | return $false 85 | } 86 | else { 87 | return $true 88 | } 89 | } 90 | elseif (($hashType.ToUpper() -eq "SHA1")) { 91 | $fileHash = Get-FileHash $filePath -Algorithm SHA1 92 | if ($fileHash.Hash -ne $hash) { 93 | return $false 94 | } 95 | else { 96 | return $true 97 | } 98 | } 99 | elseif (($hashType.ToUpper() -eq "SHA256")) { 100 | $fileHash = Get-FileHash $filePath -Algorithm SHA256 101 | if ($fileHash.Hash -ne $hash) { 102 | return $false 103 | } 104 | else { 105 | return $true 106 | } 107 | } 108 | } 109 | function Get-AppInstaller ($files) { 110 | foreach ($file in $files) { 111 | $filePath = "$tempFolder\$($file.FileName)" 112 | try { 113 | if (-not $file.URL.StartsWith("http")) { 114 | try { 115 | $file.URL = Invoke-Expression -Command $file.URL 116 | } 117 | catch { Write-Warning $Error[0] } 118 | if (-not $file.URL) { 119 | Write-Warning "Error getting file URL" 120 | return $false 121 | } 122 | } 123 | Write-Host "Downloading $($file.URL)" 124 | $ProgressPreference = 'SilentlyContinue' 125 | Invoke-WebRequest $file.URL -OutFile $filePath -UseBasicParsing 126 | } 127 | catch { 128 | Write-Warning "Error downloading file" 129 | return $false 130 | } 131 | if (Test-Path $filePath) { 132 | Write-Host "File downloaded" 133 | } 134 | else { 135 | Write-Warning "File $filePath not found" 136 | return $false 137 | } 138 | if ((Get-Item $filePath).Length -eq $file.FileSize) { 139 | Write-Host "File size is ok" 140 | } 141 | else { 142 | Write-Warning "File size mismatch" 143 | } 144 | if (Get-FileHashIsOk $filePath $file.HashType $file.FileHash) { 145 | Write-Host "File hash is ok" 146 | } 147 | else { 148 | Write-Warning "File hash mismatch" 149 | return $false 150 | } 151 | } 152 | return $true 153 | } 154 | function Invoke-FilesDeletion ($files) { 155 | foreach ($file in $files) { 156 | $filePath = "$tempFolder\$($file.FileName)" 157 | Write-Host "Deleting file $filePath" 158 | Remove-Item -Path $filePath -Force -Confirm:$false 159 | } 160 | } 161 | function Get-InstalledApps { 162 | if ([IntPtr]::Size -eq 4) { 163 | $regpath = @( 164 | 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 165 | 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 166 | ) 167 | } 168 | else { 169 | $regpath = @( 170 | 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 171 | 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' 172 | 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' 173 | ) 174 | } 175 | Get-ItemProperty $regpath | . { process { if ($_.DisplayName -and $_.UninstallString) { $_ } } } | 176 | Select-Object DisplayName, Publisher, InstallDate, DisplayVersion, UninstallString | 177 | Sort-Object DisplayVersion 178 | } 179 | function StringVersionToFloat($version) { 180 | while (($version.ToCharArray() | Where-Object { $_ -eq '.' } | Measure-Object).Count -gt 1) { 181 | $aux = $version.Substring($version.LastIndexOf('.') + 1) 182 | $version = $version.Substring(0, $version.LastIndexOf('.')) + $aux 183 | } 184 | return [float]$version 185 | } 186 | function Get-AppIsInstalled($shortName) { 187 | $app = $catalog | Where-Object ShortName -in $shortName 188 | if (-not $app) { 189 | Write-Warning "$shortName not found in the catalog" 190 | return $null 191 | } 192 | $appInstalled = Get-InstalledApps | Where-Object { $_.DisplayName -like $app.ProductName } 193 | if (-not $appInstalled) { 194 | Write-Host "$shortName not found in computer" 195 | return $false 196 | } 197 | Write-Host "$shortName found in computer, installed version is $([version]$appInstalled.DisplayVersion) and current version is $([version]$app.ProductVersion)" 198 | if ([version]$appInstalled.DisplayVersion -lt [version]$app.ProductVersion) { 199 | return $false 200 | } 201 | else { 202 | return $appInstalled 203 | } 204 | } 205 | #https://cdn.ruckzuck.tools/rest/v2/GetCatalog 206 | $ErrorActionPreference = 'Stop' 207 | Set-Folder $tempFolder 208 | Set-Folder $logFolder 209 | Set-Location $tempFolder 210 | $apiKey = "26WRbGLCiABYG6xTuGYo14mxnwmBiBmUy6jyjBB7u5dR" 211 | $transcriptFile = "$logFolder\$(get-date -Format yyyy_MM_dd)_$($env:COMPUTERNAME)_InstallSoftware.txt" 212 | try { 213 | Start-Transcript $transcriptFile 214 | } 215 | catch { Write-Warning "Start-Transcript can not be started: $($Error[0])" } 216 | if ($sleep -ne 0) { 217 | Write-Host "Waiting $sleep seconds" 218 | Start-Sleep $sleep 219 | } 220 | if ($runAsAdmin) { 221 | Write-Host "Checking for elevated permissions" 222 | if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { 223 | Write-Error "Insufficient permissions to run this script. Execute PowerShell script as an administrator." 224 | try { 225 | Stop-Transcript 226 | } 227 | catch { Write-Warning $Error[0] } 228 | Exit 1 229 | } 230 | Write-Host "Script is elevated" 231 | } 232 | Write-Host "Checking for software in computer" 233 | if ($software.Count -eq 1) { 234 | $software = $software.Split(',') 235 | } 236 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 237 | $apiUrl = Invoke-RestMethod -Uri "https://ruckzuck.tools/rest/v2/geturl" -UseBasicParsing 238 | if ([string]::IsNullOrEmpty($apiUrl)) { 239 | Write-Error "RuckZuck API can not be found" 240 | try { 241 | Stop-Transcript 242 | } 243 | catch { Write-Warning $Error[0] } 244 | Exit 1 245 | } 246 | $catalog = Invoke-WebRequest "$apiUrl/rest/v2/GetCatalog" -UseBasicParsing | ConvertFrom-Json 247 | if ([string]::IsNullOrEmpty($catalog)) { 248 | Write-Error "RuckZuck Catalog can not be found" 249 | try { 250 | Stop-Transcript 251 | } 252 | catch { Write-Warning $Error[0] } 253 | Exit 1 254 | } 255 | foreach ($app in $software) { 256 | $ULine = '-' * $app.Length 257 | Write-Host -Object $ULine -ForegroundColor DarkCyan 258 | Write-Host $app -ForegroundColor DarkCyan 259 | Write-Host -Object $ULine -ForegroundColor DarkCyan 260 | $uriApp = [uri]::EscapeDataString($app) 261 | $appInstalled = Get-AppIsInstalled $app 262 | if ($appInstalled -eq $false) { 263 | try { 264 | $appJson = Invoke-WebRequest "$apiUrl/rest/v2/getsoftwares?apikey=$apiKey&shortname=$uriApp" -UseBasicParsing | ConvertFrom-Json 265 | } 266 | catch { 267 | Write-Warning $Error[0] 268 | } 269 | if ($appJson) { 270 | if (-not $checkOnly -and -not $uninstall) { 271 | if ($appJson.PSPreReq) { 272 | If (Get-AppInstaller $appJson.Files) { 273 | try { 274 | if (-not [string]::IsNullOrEmpty($appJson.PSPreInstall)) { 275 | Write-Host "Running pre install command" 276 | 277 | Invoke-Expression -Command $appJson.PSPreInstall 278 | } 279 | 280 | Write-Host "Running install command" 281 | Invoke-Expression -Command $appJson.PSInstall 282 | if ($ExitCode -eq 0) { 283 | Write-Host "$app installation sucessful" -ForegroundColor Green 284 | } 285 | else { 286 | Write-Warning "$app installation returned $ExitCode" 287 | } 288 | if (-not [string]::IsNullOrEmpty($appJson.PSPostInstall)) { 289 | Write-Host "Running post install command" 290 | Invoke-Expression -Command $appJson.PSPostInstall 291 | } 292 | Invoke-FilesDeletion $appJson.Files 293 | } 294 | catch { 295 | Write-Warning $Error[0] 296 | } 297 | } 298 | } 299 | else { 300 | Write-Warning "$app can not be installed on computer because $($appJson.PSPreReq) is false" 301 | } 302 | } 303 | } 304 | else { 305 | Write-Warning "$app not found in RuckZuck repository" 306 | } 307 | } 308 | elseif ($appInstalled -and $uninstall -and -not $checkOnly) { 309 | Write-Host "Uninstalling $app" 310 | try { 311 | $appJson = Invoke-WebRequest "$apiUrl/rest/v2/getsoftwares?apikey=$apiKey&shortname=$uriApp" -UseBasicParsing | ConvertFrom-Json 312 | } 313 | catch { 314 | Write-Warning $Error[0] 315 | } 316 | if ($appJson) { 317 | Write-Host "Running uninstall command" 318 | try { 319 | Invoke-Expression -Command $appJson.PSUninstall 320 | if ($ExitCode -eq 0) { 321 | Write-Host "$app uninstallation sucessful" -ForegroundColor Green 322 | } 323 | else { 324 | Write-Warning "$app uninstallation returned $ExitCode" 325 | } 326 | } 327 | catch { 328 | Write-Warning $Error[0] 329 | } 330 | } 331 | else { 332 | Write-Warning "$app not found in RuckZuck repository" 333 | } 334 | } 335 | } 336 | Set-Location $PSScriptRoot 337 | try { 338 | Stop-Transcript 339 | } 340 | catch { Write-Warning $Error[0] } -------------------------------------------------------------------------------- /Install Software/locally.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Install Software/locally.png -------------------------------------------------------------------------------- /Install Software/psexec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Install Software/psexec.png -------------------------------------------------------------------------------- /Install Software/readme.md: -------------------------------------------------------------------------------- 1 | # Install or update or uninstall software on computers using GPO/Intune/PSExec with PowerShell. 2 | 3 | This script Install/Update/Uninstall software using RZGet repository from https://ruckzuck.tools/. 4 | 5 | ⚠ **WARNING: Currently not working due to the need for an APIKey** 6 | 7 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Software/Install-Software.ps1) 8 | 9 | ![screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Software/locally.png) 10 | 11 | ## Parameters 12 | 13 | ### tempFolder 14 | * Folder to download installers 15 | * Default: "C:\temp\InstallSoftware" 16 | 17 | ### software 18 | * List of software to install. Check https://ruckzuck.tools/Home/Repository 19 | * Default: None 20 | * Example: "7-Zip","Notepad++","Edge","3CXPhone for Windows","Google Chrome","Teams","Postman" 21 | 22 | ### logFolder 23 | * Log file path. 24 | * Default: "C:\temp\InstallSoftware" 25 | * Example: "\\ES-CPD-BCK02\scripts\InstallSoftware\Log" 26 | 27 | ### uninstall 28 | * Uninstall software if it is already installed 29 | 30 | ### checkOnly 31 | * Check if software list is already installed 32 | 33 | ### runAsAdmin 34 | * Check if script is elevated and exit if false. 35 | 36 | ### sleep 37 | * Amount of seconds to sleep. 38 | * When running as logon script sometimes Windows profile is not ready and installation fails. 39 | * Default 0 40 | 41 | ## Examples 42 | 43 | Install ```7-Zip, Notepad++, Edge, 3CXPhone for Windows, Google Chrome, Teams and Postman``` if not installed or if is outdated and save log in ```\\ES-CPD-BCK02\scripts\InstallSoftware\Log``` using ```C:\temp\InstallSoftware``` as temp folder. 44 | 45 | ```powershell 46 | Install-Software.ps1 -tempFolder C:\temp\InstallSoftware -software "7-Zip","Notepad++","Edge","3CXPhone for Windows","Google Chrome","Teams","Postman" -logFolder "\\ES-CPD-BCK02\scripts\InstallSoftware\Log" 47 | ``` 48 | Uninstall ```Teams and Postman``` if installed and save log in ```\\ES-CPD-BCK02\scripts\InstallSoftware\Log```. 49 | 50 | ```powershell 51 | Install-Software.ps1 -software "Teams","Postman" -logFolder "\\ES-CPD-BCK02\scripts\InstallSoftware\Log" -uninstall 52 | ``` 53 | 54 | Check if ```Teams and Postman``` are installed and save log in ```\\ES-CPD-BCK02\scripts\InstallSoftware\Log```. 55 | 56 | ```powershell 57 | Install-Software.ps1 -software "Teams","Postman" -logFolder "\\ES-CPD-BCK02\scripts\InstallSoftware\Log" -checkOnly 58 | ``` 59 | ### Instructions for GPO deployment 60 | 61 | 1. Create shared folder to save script file Install-Software.ps1: ```\\FILESERVER-01\Install-Software``` 62 | 2. Grant 'Authenticated users' read access to ```\\FILESERVER-01\Install-Software``` 63 | 3. Copy ```Install-Software.ps1``` to ```\\FILESERVER-01\Install-Software\Install-Software.ps1``` 64 | 4. Create shared folder for logs: ```\\FILESERVER-01\Install-Software\logs``` 65 | 5. Grant 'Authenticated users' write access to ```\\FILESERVER-01\Install-Software\logs``` 66 | 6. Create a computer GPO that runs PowerShell Script: 67 | ``` 68 | Name: \\FILESERVER-01\Install-Software\Install-Software.ps1 69 | Parameters: -software "7-Zip","Notepad++","Edge" -logFolder "\\FILESERVER-01\Install-Software\logs" -sleep 60 70 | ``` 71 | ### Instructions for Intune deployment 72 | As Intune does not allow parameters, you must harcode ```software``` parameter. 73 | 74 | Change line 35 by: 75 | ```powershell 76 | [Parameter(Mandatory = $false)] 77 | ``` 78 | 79 | And line 36 with the software to install 80 | ```powershell 81 | [string[]]$software="7-Zip","Notepad++","Edge" 82 | ``` 83 | 84 | ### Instructions for Remote Execution 85 | You can download PSExec from [here](https://docs.microsoft.com/en-us/sysinternals/downloads/psexec). 86 | 87 | **Run PSExec** 88 | 89 | psexec.exe -s \\```COMPUTER_NAME``` powershell.exe "-Command" "\\```NETWORKSHARE\Install-Software.ps1 -parameters```" 90 | 91 | Example 92 | ``` 93 | psexec.exe -s \\WK-MARKETING01 powershell.exe "-Command" "\\FILESERVER-01\Install-Software\Install-Software.ps1 -software '7-Zip','Notepad++','Edge' -logFolder '\\ES-CPD-BCK02\scripts\InstallSoftware\Log'" 94 | ``` 95 | ![screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Install%20Software/psexec.png) 96 | -------------------------------------------------------------------------------- /Optimize and cleanup of WSUS on Windows Server 2012 R2 and 2016/Optimize-WSUS.ps1: -------------------------------------------------------------------------------- 1 |  2 | <#PSScriptInfo 3 | 4 | .VERSION 1.0 5 | 6 | .GUID c90bbc5c-9a05-471c-9020-741b5fc59d51 7 | 8 | .AUTHOR Juan Granados 9 | 10 | .COPYRIGHT 2021 Juan Granados 11 | 12 | .TAGS WSUS Optimice Database SQL WID DB 13 | 14 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 15 | 16 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Optimize%20and%20cleanup%20of%20WSUS%20on%20Windows%20Server%202012%20R2%20and%202016 17 | 18 | .RELEASENOTES Initial release 19 | 20 | #> 21 | 22 | <# 23 | .SYNOPSIS 24 | Optimize WSUS DB and performs maintenance. 25 | 26 | .DESCRIPTION 27 | Optimize WSUS and its DB using official Microsoft SQL script and performs server maintenance. 28 | 29 | Prerequisites for running Invoke-Sqlcmd 30 | 31 | 1. Save T-SQL script as WsusDBMaintenance.sql from: https://docs.microsoft.com/en-us/troubleshoot/mem/configmgr/reindex-the-wsus-database 32 | 33 | 2. Navigate to: https://www.microsoft.com/en-US/download/details.aspx?id=55992 and install SQLSysClrTypes.msi 34 | 35 | 3. Run from PowerShell 36 | - Register-PackageSource -provider NuGet -name nugetRepository -location https://www.nuget.org/api/v2 37 | - Install-Package Microsoft.SqlServer.SqlManagementObjects 38 | 39 | 4. Run from PowerShell: Install-Module -Name SqlServer 40 | 41 | .NOTES 42 | Author: Juan Granados 43 | #> 44 | #Requires -RunAsAdministrator 45 | $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition 46 | 47 | Start-Transcript "$scriptPath\$(Get-Date -format "yyyyMMdd")_Optimize-WSUS.log" 48 | function check-requisites { 49 | if (-not (Test-Path "$scriptPath\WsusDBMaintenance.sql")) { 50 | Write-Error "WsusDBMaintenance.sql missing. Save T-SQL script as WsusDBMaintenance.sql from: https://docs.microsoft.com/en-us/troubleshoot/mem/configmgr/reindex-the-wsus-database" 51 | Stop-Transcript 52 | Exit 1 53 | } 54 | if (-not (Get-Module -ListAvailable SQLServer)) { 55 | Write-Error "Module SQLServer missing. Install it running: Install-Module -Name SqlServer" 56 | Stop-Transcript 57 | Exit 1 58 | } 59 | if (-not (Test-Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server 2017 Redist\SQL Server System CLR Types\CurrentVersion")) { 60 | Write-Error "SQLSysClrTypes 2017 is misssing. Install it from https://www.microsoft.com/en-US/download/details.aspx?id=55992" 61 | Stop-Transcript 62 | Exit 1 63 | } 64 | if (-not(Get-Package Microsoft.SqlServer.SqlManagementObjects)) { 65 | Write-Error "Microsoft.SqlServer.SqlManagementObjects is missing. Install it running 'Register-PackageSource -provider NuGet -name nugetRepository -location https://www.nuget.org/api/v2' and 'Install-Package Microsoft.SqlServer.SqlManagementObjects'" 66 | Stop-Transcript 67 | Exit 1 68 | } 69 | } 70 | 71 | check-requisites 72 | Get-WsusServer | Invoke-WsusServerCleanup -CleanupObsoleteComputers -CleanupObsoleteUpdates -CleanupUnneededContentFiles -CompressUpdates -DeclineExpiredUpdates -DeclineSupersededUpdates 73 | Invoke-Sqlcmd -ServerInstance "\\.\pipe\microsoft##WID\tsql\query" -InputFile "$scriptPath\WsusDBMaintenance.sql" -Verbose 74 | Stop-Transcript -------------------------------------------------------------------------------- /Optimize and cleanup of WSUS on Windows Server 2012 R2 and 2016/WsusDBMaintenance.sql: -------------------------------------------------------------------------------- 1 | USE SUSDB; 2 | GO 3 | SET NOCOUNT ON; 4 | 5 | -- Rebuild or reorganize indexes based on their fragmentation levels 6 | DECLARE @work_to_do TABLE ( 7 | objectid int 8 | , indexid int 9 | , pagedensity float 10 | , fragmentation float 11 | , numrows int 12 | ) 13 | 14 | DECLARE @objectid int; 15 | DECLARE @indexid int; 16 | DECLARE @schemaname nvarchar(130); 17 | DECLARE @objectname nvarchar(130); 18 | DECLARE @indexname nvarchar(130); 19 | DECLARE @numrows int 20 | DECLARE @density float; 21 | DECLARE @fragmentation float; 22 | DECLARE @command nvarchar(4000); 23 | DECLARE @fillfactorset bit 24 | DECLARE @numpages int 25 | 26 | -- Select indexes that need to be defragmented based on the following 27 | -- * Page density is low 28 | -- * External fragmentation is high in relation to index size 29 | PRINT 'Estimating fragmentation: Begin. ' + convert(nvarchar, getdate(), 121) 30 | INSERT @work_to_do 31 | SELECT 32 | f.object_id 33 | , index_id 34 | , avg_page_space_used_in_percent 35 | , avg_fragmentation_in_percent 36 | , record_count 37 | FROM 38 | sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, 'SAMPLED') AS f 39 | WHERE 40 | (f.avg_page_space_used_in_percent < 85.0 and f.avg_page_space_used_in_percent/100.0 * page_count < page_count - 1) 41 | or (f.page_count > 50 and f.avg_fragmentation_in_percent > 15.0) 42 | or (f.page_count > 10 and f.avg_fragmentation_in_percent > 80.0) 43 | 44 | PRINT 'Number of indexes to rebuild: ' + cast(@@ROWCOUNT as nvarchar(20)) 45 | 46 | PRINT 'Estimating fragmentation: End. ' + convert(nvarchar, getdate(), 121) 47 | 48 | SELECT @numpages = sum(ps.used_page_count) 49 | FROM 50 | @work_to_do AS fi 51 | INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 52 | INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 53 | 54 | -- Declare the cursor for the list of indexes to be processed. 55 | DECLARE curIndexes CURSOR FOR SELECT * FROM @work_to_do 56 | 57 | -- Open the cursor. 58 | OPEN curIndexes 59 | 60 | -- Loop through the indexes 61 | WHILE (1=1) 62 | BEGIN 63 | FETCH NEXT FROM curIndexes 64 | INTO @objectid, @indexid, @density, @fragmentation, @numrows; 65 | IF @@FETCH_STATUS < 0 BREAK; 66 | 67 | SELECT 68 | @objectname = QUOTENAME(o.name) 69 | , @schemaname = QUOTENAME(s.name) 70 | FROM 71 | sys.objects AS o 72 | INNER JOIN sys.schemas as s ON s.schema_id = o.schema_id 73 | WHERE 74 | o.object_id = @objectid; 75 | 76 | SELECT 77 | @indexname = QUOTENAME(name) 78 | , @fillfactorset = CASE fill_factor WHEN 0 THEN 0 ELSE 1 END 79 | FROM 80 | sys.indexes 81 | WHERE 82 | object_id = @objectid AND index_id = @indexid; 83 | 84 | IF ((@density BETWEEN 75.0 AND 85.0) AND @fillfactorset = 1) OR (@fragmentation < 30.0) 85 | SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REORGANIZE'; 86 | ELSE IF @numrows >= 5000 AND @fillfactorset = 0 87 | SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD WITH (FILLFACTOR = 90)'; 88 | ELSE 89 | SET @command = N'ALTER INDEX ' + @indexname + N' ON ' + @schemaname + N'.' + @objectname + N' REBUILD'; 90 | PRINT convert(nvarchar, getdate(), 121) + N' Executing: ' + @command; 91 | EXEC (@command); 92 | PRINT convert(nvarchar, getdate(), 121) + N' Done.'; 93 | END 94 | 95 | -- Close and deallocate the cursor. 96 | CLOSE curIndexes; 97 | DEALLOCATE curIndexes; 98 | 99 | 100 | IF EXISTS (SELECT * FROM @work_to_do) 101 | BEGIN 102 | PRINT 'Estimated number of pages in fragmented indexes: ' + cast(@numpages as nvarchar(20)) 103 | SELECT @numpages = @numpages - sum(ps.used_page_count) 104 | FROM 105 | @work_to_do AS fi 106 | INNER JOIN sys.indexes AS i ON fi.objectid = i.object_id and fi.indexid = i.index_id 107 | INNER JOIN sys.dm_db_partition_stats AS ps on i.object_id = ps.object_id and i.index_id = ps.index_id 108 | 109 | PRINT 'Estimated number of pages freed: ' + cast(@numpages as nvarchar(20)) 110 | END 111 | GO 112 | 113 | 114 | --Update all statistics 115 | PRINT 'Updating all statistics.' + convert(nvarchar, getdate(), 121) 116 | EXEC sp_updatestats 117 | PRINT 'Done updating statistics.' + convert(nvarchar, getdate(), 121) 118 | GO -------------------------------------------------------------------------------- /Optimize and cleanup of WSUS on Windows Server 2012 R2 and 2016/readme.md: -------------------------------------------------------------------------------- 1 | # Optimize and cleanup of WSUS on Windows Server 2 | 3 | Optimize WSUS DB using [official Microsoft SQL script](https://docs.microsoft.com/en-us/troubleshoot/mem/configmgr/reindex-the-wsus-database) and performs server maintenance. 4 | 5 | WsusDBMaintenance.sql and script must be on the same path. 6 | 7 | - Right click here and select "Save link as" to download script 8 | 9 | - Right click here and select "Save link as" to download WsusDBMaintenance.sql 10 | 11 | It saves log in script path: yyyyMMdd__Optimize-WSUS.log 12 | 13 | Prerequisites for running Invoke-Sqlcmd 14 | 15 | 1. Download and install SQLSysClrTypes.msi from: [Microsoft® SQL Server® 2017 Feature Pack](https://www.microsoft.com/en-US/download/details.aspx?id=55992) 16 | 2. Run from PowerShell: 17 | - `Register-PackageSource -provider NuGet -name nugetRepository -location https://www.nuget.org/api/v2` 18 | - `Install-Package Microsoft.SqlServer.SqlManagementObjects` in case of error of circular dependency add `-SkipDependencies` 19 | 3. Run from PowerShell: `Install-Module -Name SqlServer` 20 | -------------------------------------------------------------------------------- /Optimize drives/Invoke-DiskDefrag.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Runs Windows disks defragmentation. 4 | .DESCRIPTION 5 | Runs Windows disks defragmentation in all or selected drives. 6 | .PARAMETER disks 7 | Disks to run defragmentation. 8 | Default: all. 9 | Example: "C:","D:","F:" 10 | .PARAMETER defragPercentage 11 | Percentage of fragmentation in order to run defragmentation. 12 | Default: 10 13 | .PARAMETER forceDefrag 14 | Defrag disks if free space is low. 15 | Default: false 16 | .PARAMETER logPath 17 | Path where save log file. 18 | Default: Temp folder 19 | .EXAMPLE 20 | Defrag all drives if they are 10% fragmented. 21 | Invoke-DiskDefrag.ps1 22 | .EXAMPLE 23 | Defrag only C and D drives if they are 20% fragmented. 24 | Invoke-DiskDefrag.ps1 -disks "C:","D:" -defragPercentage 20 25 | .EXAMPLE 26 | Defrag C: drive if they are 10% fragmented. It runs disk defragmentation even C: disk free space is low. 27 | Invoke-DiskDefrag.ps1 -disks "C:" -forceDefrag 28 | .NOTES 29 | Author: Juan Granados 30 | #> 31 | 32 | Param( 33 | [Parameter(Mandatory = $false)] 34 | [ValidateRange(0, 100)] 35 | [int]$defragPercentage = 10, 36 | [Parameter(Mandatory = $false)] 37 | [ValidateNotNullOrEmpty()] 38 | [string[]]$disks = "all", 39 | [Parameter(Mandatory = $false)] 40 | [ValidateNotNullOrEmpty()] 41 | [string]$logPath = $env:temp, 42 | [Parameter()] 43 | [switch]$forceDefrag 44 | ) 45 | 46 | #Requires -RunAsAdministrator 47 | 48 | Function Invoke-DiskDefragmentation($diskToDefrag) { 49 | 50 | if ([Environment]::UserInteractive) { 51 | Start-Process "C:\Windows\System32\dfrgui.exe" 52 | } 53 | if ($forceDefrag) { 54 | Write-Host "Forcing $($diskToDefrag.DriveLetter) defragmentation" 55 | $result = $diskToDefrag.Defrag($true) 56 | } 57 | else { 58 | Write-Host "Performing $($diskToDefrag.DriveLetter) defragmentation" 59 | $result = $diskToDefrag.Defrag($false) 60 | } 61 | if ($result.ReturnValue -eq 0) { 62 | Write-Host "Defragmentation successful" 63 | Write-Host "Current fragmentation is $($result.DefragAnalysis.FilePercentFragmentation)" 64 | $diskToDefrag.DefragResult = $result 65 | } 66 | else { 67 | Write-Output "CRITICAL: Error $($result.ReturnValue) defragmenting drive $($diskToDefrag.DriveLetter)" 68 | Write-Output "Check error codes: https://docs.microsoft.com/en-us/previous-versions/windows/desktop/vdswmi/defrag-method-in-class-win32-volume" 69 | Exit(2) 70 | } 71 | $global:output += "Disk $($diskToDefrag.DriveLetter) fragmentation is $($result.DefragAnalysis.FilePercentFragmentation)." 72 | } 73 | 74 | $ErrorActionPreference = "SilentlyContinue" 75 | Stop-Transcript | out-null 76 | $ErrorActionPreference = "Stop" 77 | 78 | $global:output = "" 79 | 80 | $logPath = $logPath.TrimEnd('\') 81 | if (-not (Test-Path $logPath)) { 82 | Write-Host "Log path $($logPath) not found" 83 | Exit (1) 84 | } 85 | 86 | Start-Transcript -path "$($logPath)\$(get-date -Format yyyy_MM_dd)_$($env:COMPUTERNAME).txt" 87 | 88 | try { 89 | if ($disks -eq "all") { 90 | $drives = get-wmiobject win32_volume | Where-Object { $_.DriveType -eq 3 -and $_.DriveLetter -and (Get-WMIObject Win32_LogicalDiskToPartition | Select-Object Dependent) -match $_.DriveLetter } 91 | } 92 | else { 93 | foreach ($disk in $disks) { 94 | if (-not ($disk -match '[A-Za-z]:')) { 95 | Write-Output "UNKNOWN: Error $($drive) is not a valid disk unit. Expected N:, where N is drive unit. Example C: or D: or F:" 96 | Exit(3) 97 | } 98 | } 99 | $drives = get-wmiobject win32_volume | Where-Object { $_.DriveType -eq 3 -and $_.DriveLetter -in $disks } 100 | } 101 | if (-not ($drives)) { 102 | Write-Output "UNKNOWN: No drives found with get-wmiobject win32_volume command" 103 | Exit(3) 104 | } 105 | foreach ($drive in $drives) { 106 | Write-Host "Analizing drive $($drive.DriveLetter)" 107 | $result = $drive.DefragAnalysis() 108 | if ($result.ReturnValue -eq 0) { 109 | Write-Host "Current fragmentation is $($result.DefragAnalysis.FilePercentFragmentation)" 110 | $drive | Add-Member -NotePropertyName 'DefragResult' -NotePropertyValue $result 111 | if (($defragPercentage -gt 0) -and ($result.DefragAnalysis.FilePercentFragmentation -gt $defragPercentage)) { 112 | Invoke-DiskDefragmentation -diskToDefrag $drive 113 | } 114 | else { 115 | $global:output += "Disk $($drive.DriveLetter) fragmentation is $($result.DefragAnalysis.FilePercentFragmentation)." 116 | } 117 | } 118 | else { 119 | Write-Output "CRITICAL: Error $($result.ReturnValue) checking status of drive $($drive.DriveLetter)" 120 | Write-Output "Check error codes: https://docs.microsoft.com/en-us/previous-versions/windows/desktop/vdswmi/defraganalysis-method-in-class-win32-volume#return-value" 121 | Exit(2) 122 | } 123 | } 124 | } 125 | catch { 126 | Write-Output "CRITICAL: $($_.Exception.Message)" 127 | Exit(2) 128 | } 129 | Write-Host "-------" 130 | Write-Host "Summary" 131 | Write-Host "-------" 132 | Write-Host $global:output 133 | Stop-Transcript -------------------------------------------------------------------------------- /Optimize drives/Invoke-DiskOptimize.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Runs Windows disks optimization. 4 | .DESCRIPTION 5 | Runs Windows disks optimization in all or selected drives. 6 | .PARAMETER disks 7 | Disks to run optimization. 8 | Default: all. 9 | Example: "C:","D:","F:" 10 | .PARAMETER logPath 11 | Path where save log file. 12 | Default: Temp folder 13 | .PARAMETER params 14 | Params for Optimize-Volume command 15 | Default: -Verbose 16 | Example: "-ReTrim -Verbose" 17 | .EXAMPLE 18 | Optimize all drives. 19 | Invoke-DiskOptimize.ps1 20 | .EXAMPLE 21 | Optimize only C and D drives. 22 | Invoke-DiskOptimize.ps1 -disks "C:","D:" 23 | .NOTES 24 | Author: Juan Granados 25 | #> 26 | 27 | Param( 28 | [Parameter(Mandatory = $false)] 29 | [ValidateNotNullOrEmpty()] 30 | [string[]]$disks = "all", 31 | [Parameter(Mandatory = $false)] 32 | [ValidateNotNullOrEmpty()] 33 | [string]$logPath = $env:temp, 34 | [Parameter(Mandatory = $false)] 35 | [ValidateNotNullOrEmpty()] 36 | [string]$params = "-Verbose" 37 | ) 38 | #Requires -RunAsAdministrator 39 | 40 | $ErrorActionPreference = "SilentlyContinue" 41 | Stop-Transcript | out-null 42 | $ErrorActionPreference = "Stop" 43 | 44 | $logPath = $logPath.TrimEnd('\') 45 | if (-not (Test-Path $logPath)) { 46 | Write-Host "Log path $($logPath) not found" 47 | Exit (1) 48 | } 49 | 50 | Start-Transcript -path "$($logPath)\$(get-date -Format yyyy_MM_dd)_$($env:COMPUTERNAME).txt" 51 | 52 | try { 53 | if ($disks -eq "all") { 54 | $drives = get-wmiobject win32_volume | Where-Object { $_.DriveType -eq 3 -and $_.DriveLetter -and (Get-WMIObject Win32_LogicalDiskToPartition | Select-Object Dependent) -match $_.DriveLetter } 55 | } 56 | else { 57 | foreach ($disk in $disks) { 58 | if (-not ($disk -match '[A-Za-z]:')) { 59 | Write-Output "UNKNOWN: Error $($drive) is not a valid disk unit. Expected N:, where N is drive unit. Example C: or D: or F:" 60 | Exit(3) 61 | } 62 | } 63 | $drives = get-wmiobject win32_volume | Where-Object { $_.DriveType -eq 3 -and $_.DriveLetter -in $disks } 64 | } 65 | if (-not ($drives)) { 66 | Write-Output "UNKNOWN: No drives found with get-wmiobject win32_volume command" 67 | Exit(3) 68 | } 69 | foreach ($drive in $drives) { 70 | Write-Host "-------------------" 71 | Write-Host "Optimizing drive $($drive.DriveLetter)" 72 | Write-Host "-------------------" 73 | Invoke-Expression "Optimize-Volume -Driveletter $($drive.DriveLetter.TrimEnd(':')) $params" 74 | } 75 | } 76 | catch { 77 | Write-Output "CRITICAL: $($_.Exception.Message)" 78 | Exit(2) 79 | } 80 | Stop-Transcript -------------------------------------------------------------------------------- /Optimize drives/readme.md: -------------------------------------------------------------------------------- 1 | # **Optimize or Defrag Windows Drives** 2 | 3 | Right click and select "Save link as" to download 4 | 5 | * [Invoke-DiskOptimize.ps1](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Optimize%20drives/Invoke-DiskOptimize.ps1): Runs Windows disks optimization in all or selected drives. 6 | * [Invoke-DiskDefrag.ps1](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Optimize%20drives/Invoke-DiskDefrag.ps1): Runs Windows disks defragmentation in all or selected drives. 7 | 8 | Examples: 9 | 10 | Optimize all drives. 11 | 12 | ```powershell 13 | Invoke-DiskOptimize.ps1 -LogPath "\\SERVER-FS01\Logs" 14 | ``` 15 | 16 | Optimize only C and D drives. 17 | 18 | ```powershell 19 | Invoke-DiskOptimize.ps1 -disks "C:","D:" 20 | ``` 21 | 22 | Defrag all drives if they are 10% fragmented (default value). 23 | 24 | ```powershell 25 | Invoke-DiskDefrag.ps1 26 | ``` 27 | 28 | Defrag only C and D drives if they are 20% fragmented. 29 | 30 | ```powershell 31 | Invoke-DiskDefrag.ps1 -disks "C:","D:" -defragPercentage 20 -LogPath "\\SERVER-FS01\Logs" 32 | ``` 33 | 34 | Defrag C: drive if they are 10% fragmented. It runs disk defragmentation even C: disk free space is low. 35 | 36 | ```powershell 37 | Invoke-DiskDefrag.ps1 -disks "C:" -forceDefrag 38 | ``` 39 | 40 | ```powershell 41 | <# 42 | .SYNOPSIS 43 | Runs Windows disks optimization. 44 | .DESCRIPTION 45 | Runs Windows disks optimization in all or selected drives. 46 | .PARAMETER disks 47 | Disks to run optimization. 48 | Default: all. 49 | Example: "C:","D:","F:" 50 | .PARAMETER LogPath 51 | Path where save log file. 52 | Default: Temp folder 53 | .EXAMPLE 54 | Optimize all drives. 55 | Invoke-DiskOptimize.ps1 56 | .EXAMPLE 57 | Optimize only C and D drives. 58 | Invoke-DiskOptimize.ps1 -disks "C:","D:" 59 | .NOTES 60 | Author:Juan Granados 61 | #> 62 | ``` 63 | 64 | ```powershell 65 | <# 66 | .SYNOPSIS 67 | Runs Windows disks defragmentation. 68 | .DESCRIPTION 69 | Runs Windows disks defragmentation in all or selected drives. 70 | .PARAMETER disks 71 | Disks to run defragmentation. 72 | Default: all. 73 | Example: "C:","D:","F:" 74 | .PARAMETER defragPercentage 75 | Percentage of fragmentation in order to run defragmentation. 76 | Default: 10 77 | .PARAMETER forceDefrag 78 | Defrag disks if free space is low. 79 | Default: false 80 | .PARAMETER LogPath 81 | Path where save log file. 82 | Default: Temp folder 83 | .EXAMPLE 84 | Defrag all drives if they are 10% fragmented. 85 | Invoke-DiskDefrag.ps1 86 | .EXAMPLE 87 | Defrag only C and D drives if they are 20% fragmented. 88 | Invoke-DiskDefrag.ps1 -disks "C:","D:" -defragPercentage 20 89 | .EXAMPLE 90 | Defrag C: drive if they are 10% fragmented. It runs disk defragmentation even C: disk free space is low. 91 | Invoke-DiskDefrag.ps1 -disks "C:" -forceDefrag 92 | .NOTES 93 | Author:Juan Granados 94 | #> 95 | ``` 96 | -------------------------------------------------------------------------------- /Password Encryption for PowerShell scripts/New-StringDecryption.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Cmdlet will decode a base64 encrypted string 4 | 5 | .DESCRIPTION 6 | Function will take as input a base64 encrypted string and output the unencrypted string 7 | 8 | .PARAMETER EncryptedString 9 | The string to be decrypted 10 | 11 | .PARAMETER EncryptPassPhrase 12 | A string representing the encryption passphrase to be used to decrypt the string. 13 | 14 | If not specified hostname of the computer where function is invoked will be used. 15 | 16 | .PARAMETER EncryptSalt 17 | A string representing the encryption salt to use to decrypt the string. 18 | 19 | If not specified hostname of the computer where function is invoked will be used. 20 | 21 | .PARAMETER IntersectingVector 22 | A string representing the intersecting vector to be used during strict decryption. 23 | 24 | If not specified it will default to a standard value which must match the one used to encrypt the string. 25 | 26 | For better security this should be changed to a custom string eatch time. 27 | 28 | .EXAMPLE 29 | PS C:\> New-StringDecryption 30 | 31 | .OUTPUTS 32 | System.String 33 | #> 34 | 35 | [OutputType([string])] 36 | param 37 | ( 38 | [Parameter(Mandatory = $true)] 39 | [ValidateNotNullOrEmpty()] 40 | [string] 41 | $EncryptedString, 42 | [ValidateNotNullOrEmpty()] 43 | [string] 44 | $EncryptPassPhrase = $env:Computername, 45 | [ValidateNotNullOrEmpty()] 46 | [string] 47 | $EncryptSalt = $env:Computername, 48 | [ValidateNotNullOrEmpty()] 49 | [string] 50 | $IntersectingVector = 'Q!L@2QTCYgsG' 51 | ) 52 | 53 | # Instantiate empty return value 54 | [string]$DecryptedString = $null 55 | 56 | # Regex to check if string is in the correct format 57 | [regex]$base64RegEx = '^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$' 58 | 59 | # Instantiate COM Object for RijndaelManaged Cryptography 60 | [System.Security.Cryptography.RijndaelManaged]$encryptionObject = New-Object System.Security.Cryptography.RijndaelManaged 61 | 62 | # If the value in the Encrypted is a string, convert it to Base64 63 | if ($EncryptedString -is [string]) 64 | { 65 | # Check string is in correct format 66 | if ($EncryptedString -match $base64RegEx) 67 | { 68 | [byte[]]$encryptedStringByte = [Convert]::FromBase64String($EncryptedString) 69 | } 70 | else 71 | { 72 | Write-Warning -Message 'String is not base64 encoded!' 73 | 74 | return 75 | } 76 | } 77 | else 78 | { 79 | Write-Warning 'Input is not a string!' 80 | 81 | return 82 | } 83 | 84 | # Convert Salt and Passphrase to UTF8 Bytes array 85 | [System.Byte[]]$byteEncryptSalt = [Text.Encoding]::UTF8.GetBytes($EncryptSalt) 86 | [System.Byte[]]$bytePassPhrase = [Text.Encoding]::UTF8.GetBytes($EncryptPassPhrase) 87 | 88 | # Create the Encryption Key using the passphrase, salt and SHA1 algorithm at 256 bits 89 | $encryptionObject.Key = (New-Object Security.Cryptography.PasswordDeriveBytes $bytePassPhrase, 90 | $byteEncryptSalt, 91 | 'SHA', 92 | 5).GetBytes(32) # 256/8 - 32byts 93 | 94 | # Create the Intersecting Vector Cryptology Hash with the init 95 | $encryptionObject.IV = (New-Object Security.Cryptography.SHA1Managed).ComputeHash([Text.Encoding]::UTF8.GetBytes($IntersectingVector))[0 .. 15] 96 | 97 | # Create new decryptor Key and IV 98 | [System.Security.Cryptography.RijndaelManagedTransform]$objectDecryptor = $encryptionObject.CreateDecryptor() 99 | 100 | # Create a New memory stream with the encrypted value 101 | [System.IO.MemoryStream]$memoryStream = New-Object IO.MemoryStream @( ,$encryptedStringByte) 102 | 103 | # Read the new memory stream and read it in the cryptology stream 104 | [System.Security.Cryptography.CryptoStream]$cryptoStream = New-Object Security.Cryptography.CryptoStream $memoryStream, $objectDecryptor, 'Read' 105 | 106 | # Read the new decrypted stream 107 | [System.IO.StreamReader]$streamReader = New-Object IO.StreamReader $cryptoStream 108 | 109 | try 110 | { 111 | # Return from the function the stream 112 | [string]$DecryptedString = $streamReader.ReadToEnd() 113 | 114 | # Stop the stream 115 | $streamReader.Close() 116 | 117 | # Stop the crypto stream 118 | $cryptoStream.Close() 119 | 120 | # Stop the memory stream 121 | $memoryStream.Close() 122 | 123 | # Clears all crypto objects 124 | $encryptionObject.Clear() 125 | 126 | # Return decrypted string 127 | return $DecryptedString 128 | } 129 | 130 | catch 131 | { 132 | # Save exception 133 | [string]$reportedException = $Error[0].Exception.Message 134 | 135 | Write-Warning -Message "String $EncryptedString could not be decripted - Use the -Verbose paramter for more details" 136 | 137 | # Check we have an exception message 138 | if ([string]::IsNullOrEmpty($reportedException) -eq $false) 139 | { 140 | Write-Verbose -Message $reportedException 141 | } 142 | else 143 | { 144 | Write-Verbose -Message 'No inner exception reported by Disconnect-AzureAD cmdlet' 145 | } 146 | 147 | return [string]::Empty 148 | } -------------------------------------------------------------------------------- /Password Encryption for PowerShell scripts/New-StringEncription.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Encryps a string using RijndaelManaged Cryptography 4 | 5 | .DESCRIPTION 6 | New-StringEncryption is used to encrypt a string using RijndaelManaged Cryptography allowing 7 | user to specify Salt, Passphrase and Intersecting Vector values. 8 | 9 | For better security Intersecting Vector (IV) value should be changed on each machine where function is used. 10 | 11 | .PARAMETER StringToEncrypt 12 | Any string that needs to be encrypted. Parameter is mandatory and cannot be empty. 13 | 14 | .PARAMETER EncryptPassPhrase 15 | An optional passphrase to be used during the encryption process. 16 | 17 | If parameter is not specified hostname value will be used. 18 | 19 | .PARAMETER EncryptSalt 20 | Specify a custom string to use as the Encryption Salt. 21 | 22 | If parameter is not specified hostname value will be used. 23 | 24 | .PARAMETER IntersectingVector 25 | Intersecting Vector value used in the encryption process. If parameter is not used a 26 | default value is used but for security reasons this should be updated to a custom value. 27 | 28 | .EXAMPLE 29 | PS C:\> New-StringEncryption 30 | #> 31 | 32 | [OutputType([string])] 33 | param 34 | ( 35 | [Parameter(Mandatory = $true)] 36 | [ValidateNotNullOrEmpty()] 37 | [Alias('string')] 38 | [string] 39 | $StringToEncrypt, 40 | [ValidateNotNullOrEmpty()] 41 | [Alias('PassPhrase', 'EncryptionPassPhrase')] 42 | [string] 43 | $EncryptPassPhrase, 44 | [ValidateNotNullOrEmpty()] 45 | [Alias('Salt', 'SaltValue')] 46 | [string] 47 | $EncryptSalt, 48 | [ValidateNotNullOrEmpty()] 49 | [Alias('Vector')] 50 | [string] 51 | $IntersectingVector = 'Q!L@2QTCYgsG' 52 | ) 53 | 54 | # Instantiate empty return value 55 | [string]$EncryptedString = $null 56 | 57 | # Instantiate COM Object for RijndaelManaged Cryptography 58 | [System.Security.Cryptography.RijndaelManaged]$encryptionObject = New-Object System.Security.Cryptography.RijndaelManaged 59 | 60 | # Check if we have a passphrase 61 | if ([string]::IsNullOrEmpty($EncryptPassPhrase) -eq $true) 62 | { 63 | # Use hostname 64 | $EncryptPassPhrase = $env:Computername 65 | } 66 | 67 | # Check if we have a salt value 68 | if ([string]::IsNullOrEmpty($EncryptSalt) -eq $true) 69 | { 70 | # Use hostname 71 | $EncryptSalt = $env:Computername 72 | } 73 | 74 | # Convert Salt and Passphrase to UTF8 Bytes array 75 | [System.Byte[]]$byteEncryptSalt = [Text.Encoding]::UTF8.GetBytes($EncryptSalt) 76 | [System.Byte[]]$bytePassPhrase = [Text.Encoding]::UTF8.GetBytes($EncryptPassPhrase) 77 | 78 | # Create the Encryption Key using the passphrase, salt and SHA1 algorithm at 256 bits 79 | $encryptionObject.Key = (New-Object Security.Cryptography.PasswordDeriveBytes $bytePassPhrase, 80 | $byteEncryptSalt, 81 | 'SHA1', 82 | 5).GetBytes(32) # 256/8 - 32bytes 83 | 84 | # Create the Intersecting Vector (IV) Cryptology Hash with the init value 85 | $paramNewObject = @{ 86 | TypeName = 'Security.Cryptography.SHA1Managed' 87 | } 88 | $encryptionObject.IV = (New-Object @paramNewObject).ComputeHash([Text.Encoding]::UTF8.GetBytes($IntersectingVector))[0 .. 15] 89 | 90 | # Starts the New Encryption using the Key and IV 91 | $encryptorObject = $encryptionObject.CreateEncryptor() 92 | 93 | # Creates a MemoryStream for encryption 94 | [System.IO.MemoryStream]$memoryStream = New-Object IO.MemoryStream 95 | 96 | # Creates the new Cryptology Stream --> Outputs to $MS or Memory Stream 97 | [System.Security.Cryptography.CryptoStream]$cryptoStream = New-Object Security.Cryptography.CryptoStream $memoryStream, $encryptorObject, 'Write' 98 | 99 | # Starts the new Cryptology Stream 100 | $cryptoStreamWriter = New-Object IO.StreamWriter $cryptoStream 101 | 102 | # Writes the string in the Cryptology Stream 103 | $cryptoStreamWriter.Write($StringToEncrypt) 104 | 105 | # Stops the stream writer 106 | $cryptoStreamWriter.Close() 107 | 108 | # Stops the Cryptology Stream 109 | $cryptoStream.Close() 110 | 111 | # Stops writing to Memory 112 | $memoryStream.Close() 113 | 114 | # Clears the IV and HASH from memory to prevent memory read attacks 115 | $encryptionObject.Clear() 116 | 117 | # Takes the MemoryStream and puts it to an array 118 | [byte[]]$result = $memoryStream.ToArray() 119 | 120 | # Converts the array from Base 64 to a string and returns 121 | $EncryptedString = $([Convert]::ToBase64String($result)) 122 | 123 | # Return value 124 | return $EncryptedString -------------------------------------------------------------------------------- /Password Encryption for PowerShell scripts/readme.md: -------------------------------------------------------------------------------- 1 | # Password Encryption for PowerShell scripts 2 | 3 | [Original code from PsCustomObject](https://github.com/PsCustomObject/IT-ToolBox) 4 | 5 | This two scripts generate a encrypted password and allow use it in PowerShell scripts. 6 | 7 | **New-StringEncryption.ps1**: generate a an encrypted string that can only be decrypted on the same machine that performed the original encryption. 8 | 9 | **New-StringDecryption.ps1**: takes a Base64 encoded string and will output the clear text version of it. 10 | 11 | ## Examples 12 | 13 | Encrypt password 14 | 15 | ```powershell 16 | New-StringEncryption.ps1 -StringToEncrypt 'Password' 17 | ``` 18 | Returns ```fzdxB8+jXgchfghU98mbOc5g==``` 19 | 20 | Decrypt password and use it 21 | 22 | ```powershell 23 | $365Username="admin@contoso.onmicrosoft.com" 24 | $365Password="fzdxB8+jXgchfghU98mbOc5g==" 25 | $Secure365AdminPassword = ConvertTo-SecureString -String (C:\Scripts\New-StringDecryption.ps1 -EncryptedString $365Password) -AsPlainText -Force 26 | $365Credentials = New-Object System.Management.Automation.PSCredential $365Username, $Secure365AdminPassword 27 | $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $365Credentials -Authentication Basic -AllowRedirection 28 | Import-PSSession $Session -AllowClobber | Out-Null 29 | # ..... 30 | Remove-PSSession $Session 31 | ``` 32 | -------------------------------------------------------------------------------- /RZGet launcher for Intune/Install-Software.ps1: -------------------------------------------------------------------------------- 1 | ## ------------------------------------------------------------------ 2 | # Arguments 3 | ## ------------------------------------------------------------------ 4 | [string]$folder = 'C:\temp\InstallSoftware' #Folder to download RZGet.exe and save log. 5 | [string]$RZGetArguments = 'install 7-Zip Notepad++(x64) Edge "3CXPhone for Windows" "Google Chrome"' # RZget Arguments. Check https://github.com/rzander/ruckzuck/wiki/RZGet and https://ruckzuck.tools/Home/Repository 6 | # Mail Settings. If you do not want to send and email, leave empty $SMTPServer variable: $SMTPServer = '' 7 | [string]$SMTPServer = 'smtp.office365.com' 8 | [string[]]$recipient = 'juangranados@contoso.com', 'support@contoso.com' 9 | [String]$subject = "Installation on computer $env:COMPUTERNAME" 10 | [string]$sender = 'support@contoso.com' 11 | [string]$username = 'support@contoso.com' 12 | [string]$password = 'P@ssw0rd' 13 | [bool]$enableSsl = $true 14 | [int]$port = 587 15 | ## ------------------------------------------------------------------ 16 | # function Invoke-Process 17 | # https://stackoverflow.com/a/66700583 18 | ## ------------------------------------------------------------------ 19 | function Invoke-Process { 20 | param 21 | ( 22 | [Parameter(Mandatory)] 23 | [ValidateNotNullOrEmpty()] 24 | [string]$FilePath, 25 | 26 | [Parameter()] 27 | [ValidateNotNullOrEmpty()] 28 | [string]$ArgumentList, 29 | 30 | [ValidateSet("Full", "StdOut", "StdErr", "ExitCode", "None")] 31 | [string]$DisplayLevel 32 | ) 33 | 34 | $ErrorActionPreference = 'Stop' 35 | 36 | try { 37 | $pinfo = New-Object System.Diagnostics.ProcessStartInfo 38 | $pinfo.FileName = $FilePath 39 | $pinfo.RedirectStandardError = $true 40 | $pinfo.RedirectStandardOutput = $true 41 | $pinfo.UseShellExecute = $false 42 | $pinfo.WindowStyle = 'Hidden' 43 | $pinfo.CreateNoWindow = $true 44 | $pinfo.Arguments = $ArgumentList 45 | $p = New-Object System.Diagnostics.Process 46 | $p.StartInfo = $pinfo 47 | $p.Start() | Out-Null 48 | $result = [pscustomobject]@{ 49 | Title = ($MyInvocation.MyCommand).Name 50 | Command = $FilePath 51 | Arguments = $ArgumentList 52 | StdOut = $p.StandardOutput.ReadToEnd() 53 | StdErr = $p.StandardError.ReadToEnd() 54 | ExitCode = $p.ExitCode 55 | } 56 | $p.WaitForExit() 57 | 58 | if (-not([string]::IsNullOrEmpty($DisplayLevel))) { 59 | switch ($DisplayLevel) { 60 | "Full" { return $result; break } 61 | "StdOut" { return $result.StdOut; break } 62 | "StdErr" { return $result.StdErr; break } 63 | "ExitCode" { return $result.ExitCode; break } 64 | } 65 | } 66 | } 67 | catch { 68 | Write-Host "An error has ocurred" 69 | } 70 | } 71 | if ($folder.Chars($folder.Length - 1) -eq '\') { 72 | $folder = ($folder.TrimEnd('\')) 73 | } 74 | if (!(Test-Path $folder)) { 75 | mkdir $folder 76 | } 77 | $transcriptFile = "$folder\$(get-date -Format yyyy_MM_dd)_InstallSoftware.txt" 78 | Start-Transcript $transcriptFile 79 | Write-Host "Checking for elevated permissions" 80 | if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { 81 | Write-Warning "Insufficient permissions to run this script. Execute PowerShell script as an administrator." 82 | Stop-Transcript 83 | Exit 84 | } 85 | Write-Host "Script is elevated" 86 | Write-Host "Downloading RZGet.exe" 87 | Invoke-WebRequest "https://github.com/rzander/ruckzuck/releases/latest/download/RZGet.exe" -OutFile "$folder\RZGet.exe" 88 | Write-Host "Running $($folder)\RZGet.exe $($RZGetArguments)" 89 | Invoke-Process -FilePath "$folder\RZGet.exe" -ArgumentList $RZGetArguments -DisplayLevel StdOut 90 | Stop-Transcript 91 | 92 | if (!([string]::IsNullOrEmpty($SMTPServer))) { 93 | $msg = new-object Net.Mail.MailMessage 94 | $smtp = new-object Net.Mail.SmtpClient($SMTPServer, $port) 95 | $msg.From = $sender 96 | $msg.ReplyTo = $sender 97 | ForEach ($mail in $recipient) { 98 | $msg.To.Add($mail) 99 | } 100 | if ($username -ne "None" -and $password -ne "None") { 101 | $smtp.Credentials = new-object System.Net.NetworkCredential($username, $password) 102 | } 103 | $smtp.EnableSsl = $enableSsl 104 | $msg.subject = $subject 105 | $msg.body = Get-Content $transcriptFile -Raw 106 | try { 107 | Write-Output "Sending email" 108 | $smtp.Send($msg) 109 | } 110 | catch { 111 | Write-Host "Error sending email" -ForegroundColor Red 112 | write-host "Caught an exception:" -ForegroundColor Red 113 | write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red 114 | write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 115 | } 116 | } -------------------------------------------------------------------------------- /Redirect Folder/Redirect-Folder.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Redirects user shell folders. 4 | .DESCRIPTION 5 | Redirects user shell folders to a custom path. 6 | .PARAMETER logPath 7 | Log file path. 8 | Default "Documents" 9 | Example: "\\ES-CPD-BCK02\scripts\FolderRedirection\Log" 10 | .PARAMETER folders 11 | Array of folders to redirect. 12 | Check folder names in HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders 13 | Example: "Personal","My Pictures","Desktop" 14 | .PARAMETER paths 15 | Array of paths to redirect, in the same order than folders array. 16 | Example: "D:\%USERNAME%\Documents","D:\%USERNAME%\Pictures","D:\%USERNAME%\Desktop" 17 | .EXAMPLE 18 | .\Redirect-Folder -folders "Personal","My Pictures","Desktop" -paths "D:\%USERNAME%\Documents","D:\%USERNAME%\Pictures","D:\%USERNAME%\Desktop" 19 | .EXAMPLE 20 | .\Redirect-Folder -folders "Personal" -paths "\\SRV-FS01\Users\%USERNAME%\Documents" -logPath "\\SRV-DC01\Scripts\FolderRedirection\Log" 21 | .LINK 22 | https://github.com/juangranados/powershell-scripts/tree/main/Redirect%20Folder 23 | .NOTES 24 | Author: Juan Granados 25 | #> 26 | Param( 27 | [Parameter(Mandatory = $true)] 28 | [ValidateNotNullOrEmpty()] 29 | [string[]]$folders, 30 | [Parameter(Mandatory = $true)] 31 | [string[]]$paths, 32 | [Parameter(Mandatory = $false)] 33 | [string]$logPath = [Environment]::GetFolderPath("MyDocuments") 34 | ) 35 | Function Set-RegistryKey ([string]$path, [string]$name, $value) { 36 | 37 | $currentValue = (Get-Item -Path $path).GetValue($name, $null, 'DoNotExpandEnvironmentNames') 38 | if (-not $currentValue) { 39 | Write-Host "Key $($path)\$($name) do not exist - Changing by $($value)" -ForegroundColor Yellow 40 | Set-ItemProperty -Path $path -name $name -Value $value -Type 'ExpandString' 41 | } 42 | elseif ($currentValue -ne $value) { 43 | Write-Host "Key $($path)\$($name) has a value of $($currentValue) - Changing by $($value)" -ForegroundColor Yellow 44 | Set-ItemProperty -Path $path -name $name -Value $value -Type 'ExpandString' 45 | } 46 | else { 47 | Write-Host "Key $($path)\$($name) already has the value of $($currentValue)" -ForegroundColor Green 48 | } 49 | } 50 | Start-Transcript $logPath 51 | $regKey = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" 52 | $i = 0 53 | foreach ($folder in $folders) { 54 | Write-Host "Setting $folder as $($paths[$i])" 55 | Set-RegistryKey -path $regKey -name $folder -value $paths[$i] 56 | $i++ 57 | } 58 | Stop-Transcript 59 | -------------------------------------------------------------------------------- /Remote Computer Update/Launch-Remote.ps1: -------------------------------------------------------------------------------- 1 | $logPath = "\\ES-CPD-BCK02\scripts\WindowsUpdate\Log" 2 | $rebootMessage = "Se va a reiniciar el equipo dentro de 2 horas para terminar de instalar las actualizaciones de Windows. Por favor, cierra todo antes de esa hora o reinicia el equipo manualmente" 3 | $RZGetPath = "\\ES-CPD-BCK02\scripts\WindowsUpdate\RZGet.exe" 4 | [int]$rebootHours = 2 5 | \\SRVHTS-FS01\Scripts\RemoteComputerUpdate\Update-Computer.ps1 -logPath $logPath -scheduleReboot -rebootHours $rebootHours -rebootMessage $rebootMessage -RZGetPath $RZGetPath -------------------------------------------------------------------------------- /Remote Computer Update/LaunchRemote.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | SETLOCAL 3 | SET computer=192.168.0.26 4 | SET scriptPath="\\SRVHTS-FS01\Scripts\RemoteComputerUpdate\Launch-Remote.ps1" 5 | psexec.exe -s \\%computer% powershell.exe -ExecutionPolicy Bypass -file %scriptPath% 6 | pause -------------------------------------------------------------------------------- /Remote Computer Update/readme.md: -------------------------------------------------------------------------------- 1 | # Remote Computer Update 2 | 3 | Right click here and select "Save link as" to download 4 | 5 | Runs Windows Update and software update using RuckZuck on local or remote computer. 6 | 7 | In order to run in remote computer it has to be executed from [PsExec](https://docs.microsoft.com/en-us/sysinternals/downloads/psexec). See examples below. 8 | 9 | It uses [RZGet](https://github.com/rzander/ruckzuck/releases) to update computer software. 10 | 11 | ![Screenshot](https://github.com/juangranados/powershell-scripts/raw/main/Remote%20Computer%20Update/screenshot.png) 12 | 13 | ## Parameters 14 | 15 | ### logPath 16 | 17 | Log file path. 18 | Default Documents 19 | Example: "\\ES-CPD-BCK02\scripts\ComputerUpdate\Log" 20 | 21 | ### scheduleReboot 22 | 23 | Reboot wil be scheduled if needed. 24 | Default: false 25 | 26 | ### rebootHours 27 | 28 | Number of hours after finish update to reboot computer. 29 | Default: 2 30 | 31 | ### rebootNow 32 | 33 | Reboots after finish update. 34 | Default: false 35 | 36 | ### rebootMessage 37 | 38 | Shows a message to user. 39 | Default: none 40 | 41 | ### RZGetPath 42 | 43 | RZGet.exe path. 44 | Example: \\SRV-FS05\RZGet\rzget.exe 45 | If path not found RZGet will not be called unless you set -downloadRZGet switch and it will be downloaded to this path. 46 | Default: none 47 | 48 | ### RZGetArguments 49 | 50 | RZGet.exe Arguments. 51 | Default: update --all 52 | 53 | ### downloadRZGet 54 | 55 | Download RZGet.exe latest version to RZGetPath 56 | Default: false 57 | 58 | RZGet.exe Arguments. 59 | Default: update --all 60 | 61 | ## Examples 62 | 63 | ### Run remotely 64 | 65 | #### Harcode parameters on script and run PsExec. 66 | 67 | ```bash 68 | psexec.exe -s \\ComputerName powershell.exe -ExecutionPolicy Bypass -file \\ES-CPD-BCK02\scripts\WindowsUpdate\Update-Computer.ps1 69 | ``` 70 | 71 | #### Use an script to run with parameters. 72 | 73 | [LaunchRemote.cmd](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Remote%20Computer%20Update/LaunchRemote.cmd): run [LaunchRemote.ps1](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Remote%20Computer%20Update/LaunchRemote.ps1) with PsExec. 74 | 75 | [LaunchRemote.ps1](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Remote%20Computer%20Update/LaunchRemote.ps1): run [Update-Computer.ps1](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Remote%20Computer%20Update/Update-Computer.ps1) with arguments. 76 | 77 | ### Run locally 78 | 79 | ```powershell 80 | Update-Computer.ps1 -$logPath "\\ES-CPD-BCK02\scripts\WindowsUpdate\Log" -scheduleReboot -rebootHours 2 -rebootMessage "Computer will reboot in two hours. You can reboot now or it will reboot later" -RZGetPath "\\ES-CPD-BCK02\scripts\WindowsUpdate\RZGet.exe" $RZGetArguments 'update "7-Zip" "Google Chrome" "Notepad++(x64)" "AdobeReader DC"' 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /Remote Computer Update/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Remote Computer Update/screenshot.png -------------------------------------------------------------------------------- /SQL Server Backup/BackupSQL.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/SQL Server Backup/BackupSQL.ps1 -------------------------------------------------------------------------------- /SQL Server Backup/readme.md: -------------------------------------------------------------------------------- 1 | # SQL Server Backup 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/SQL%20Server%20Backup/BackupSQL.ps1) 4 | 5 | This script perform full and log backup of a SQL Server databases into a zip file. 6 | 7 | You can specify what databases will be saved and the remote location of backup, also it can delete old backup files to save disk space, e.g keeping one week of backups. It can send an email report with backup result. 8 | 9 | This script can be executed in the SQL Server you want to backup, but if you want to run it from a computer without SQL Server installed: 10 | 11 | 1. Download and install SQLSysClrTypes.msi from: [Microsoft® SQL Server® 2017 Feature Pack](https://www.microsoft.com/en-US/download/details.aspx?id=55992) 12 | 2. Run from PowerShell: 13 | - `Register-PackageSource -provider NuGet -name nugetRepository -location https://www.nuget.org/api/v2` 14 | - `Install-Package Microsoft.SqlServer.SqlManagementObjects` in case of error of circular dependency add `-SkipDependencies` 15 | 3. Run from PowerShell: `Install-Module -Name SqlServer` 16 | 17 | #### *Examples* 18 | 19 | Backup default instance databases to a network share 20 | 21 | `BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL` 22 | 23 | Backup Windows Internal Database (WID) to a network share 24 | 25 | `BackupSQL.ps1 -BackupDirectory "\\MV0SRV-C01\Backups" -Instance "\\.\pipe\MICROSOFT##WID\tsql\query"` 26 | 27 | Backup default instance databases to a network share and send an email with result using gmail 28 | 29 | `BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL -SMTPServer smtp.gmail.com -Recipient jgranados@contoso.com,administrator@contoso.com -Sender backupSQL@gmail.com -Username backupSQL@gmail.com -Password Pa$$W0rd -SSL True -Port 587` 30 | 31 | Backup named instance databases to a network share 32 | 33 | `BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL -Instance SQLSVR01\BKUPEXEC` 34 | 35 | Backup default instance databases to a network share, delete from network share files older than a week and write result in Windows Application Event 36 | 37 | `BackupSQL.ps1 -BackupDirectory \\FS-SERVER01\BackupSQL -RetainDays 7 -WriteEvent True` 38 | 39 | Backup only specified databases of a named instance 40 | 41 | `BackupSQL.ps1 -Instance SQLSVR01\BKUPEXEC -DataBases BEDB,msdb,model` 42 | -------------------------------------------------------------------------------- /System Center DPM 2012 (R2) HTML Report/DPMReport.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Create and send a protection group backup report of System Center Data Protection Manager (DPM) 2012 (R2) Servers 4 | .DESCRIPTION 5 | This script Create and send a protection group backup report of System Center Data Protection Manager (DPM) 2012 (R2) Servers based on the recovery points and protection groups 6 | Usage: DPMProtectionGroupReport.ps1 [-DPMServers ] [-ProtectionGroups ] [-MinLastBackupHours ] [-MinRecoveryPoints ] [ReportLocation ] [SMTPServer ] [Recipient ] [Sender ] [Username ] [Password ] [-SSL ] [Port ] 7 | .PARAMETER DPMPServers 8 | List of DPM servers to get report. 9 | Default: localhost 10 | .PARAMETER ProtectionGroups 11 | List of protection groups to get report. 12 | Default: All (Report will contain data of all protection groups) 13 | .PARAMETER MinLastBackupHours 14 | If last recovery point is older than MinLastBackupHours it will show with red background. 15 | Default: 24 16 | .PARAMETER MinRecoveryPoints 17 | If number of recovery points is less than MinRecoveryPoint it will show with red background. 18 | Default: 1 19 | .PARAMETER ReportLocation 20 | Path where report will be saved 21 | Default: None 22 | .PARAMETER SMTPServer 23 | Sets smtp server in order to sent an email with backup result. 24 | Default: None 25 | .PARAMETER Recipient 26 | Array of emails addresses which will receive the backup result. 27 | Default: None 28 | .PARAMETER Sender 29 | Email address which will send the backup result. 30 | Default: None 31 | .PARAMETER Username 32 | Username in case of smtp server requires authentication. 33 | Default: None 34 | .PARAMETER Password 35 | Password in case of smtp server requires authentication. 36 | Default: None 37 | .PARAMETER SSL 38 | Use of SSL in case of smtp server requires SSL. 39 | Default: False 40 | .PARAMETER Port 41 | Port to connect to smtp server. 42 | Default: 25 43 | .EXAMPLE 44 | DPMReport.ps1 -DPMServers DPM01,DPM02,DPM03 -HtmlReport \\SERVER1\Reports\ -SMTPServer mail.contoso.com -Sender soporte@contoso.com -Recipient jgranados@contoso.com,administrador@contoso.com -Username contoso\jgranados -Password P@ssw0rd 45 | .NOTES 46 | Author: Juan Granados 47 | Date: July 2017 48 | #> 49 | 50 | Param( 51 | [Parameter(Mandatory=$false,Position=0)] 52 | [ValidateNotNullOrEmpty()] 53 | [string[]]$DPMServers=$env:computername, 54 | [Parameter(Mandatory=$false,Position=1)] 55 | [ValidateNotNullOrEmpty()] 56 | [string[]]$ProtectionGroups="All", 57 | [Parameter(Mandatory=$false,Position=2)] 58 | [ValidateNotNullOrEmpty()] 59 | [int]$MinLastBackupHours=24, 60 | [Parameter(Mandatory=$false,Position=2)] 61 | [ValidateNotNullOrEmpty()] 62 | [int]$MinRecoveryPoints=1, 63 | [Parameter(Mandatory=$false,Position=3)] 64 | [ValidateNotNullOrEmpty()] 65 | [string]$ReportLocation="None", 66 | [Parameter(Mandatory=$false,Position=4)] 67 | [ValidateNotNullOrEmpty()] 68 | [string]$SMTPServer="None", 69 | [Parameter(Mandatory=$false,Position=5)] 70 | [ValidateNotNullOrEmpty()] 71 | [string[]]$Recipient, 72 | [Parameter(Mandatory=$false,Position=6)] 73 | [ValidateNotNullOrEmpty()] 74 | [string]$Sender, 75 | [Parameter(Mandatory=$false,Position=7)] 76 | [ValidateNotNullOrEmpty()] 77 | [string]$Username="None", 78 | [Parameter(Mandatory=$false,Position=8)] 79 | [ValidateNotNullOrEmpty()] 80 | [string]$Password="None", 81 | [Parameter(Mandatory=$false,Position=9)] 82 | [ValidateSet("True","False")] 83 | [string[]]$SSL="False", 84 | [Parameter(Mandatory=$false,Position=10)] 85 | [ValidateNotNullOrEmpty()] 86 | [int]$Port=25 87 | ) 88 | 89 | $ErrorActionPreference = "silentlycontinue" 90 | 91 | if (Get-Module -ListAvailable -Name DataProtectionManager) { 92 | Import-Module DataProtectionManager 93 | } else { 94 | Write-Host "Module DataProtectionManager does not exist. Script can not continue" 95 | Exit 96 | } 97 | 98 | $DomainName=((Get-WmiObject Win32_ComputerSystem).Domain).toupper() 99 | 100 | $HTMLFile = @" 101 | 102 | 103 | 104 | 105 | DPM Backup Status 106 | 116 | 117 | 118 |

DPM Backup Status

119 | "@ 120 | 121 | try{ 122 | foreach($DPMServer in $DPMServers){ 123 | $HTMLFile+=@" 124 | 125 | 126 | 127 | 128 | 130 | 131 | 132 | "@ 133 | 134 | $PGs = get-protectiongroup $DPMServer | sort-object Name 135 | 136 | foreach($ProtectionGroup in $PGs){ 137 | $ProtectionGroupname = $ProtectionGroup.friendlyname.toupper() 138 | if(($ProtectionGroups -contains "All") -OR ($ProtectionGroups -contains $ProtectionGroupname)){ 139 | $DataSources = get-datasource $ProtectionGroup | sort-object Computer, Name 140 | foreach($DataSource in $DataSources){ 141 | $Computer = $DataSource.productionservername.toupper().replace(".$DomainName","") 142 | for ($i=0;$i -le 5;$i++){ 143 | if ($Datasource.TotalRecoveryPoints -ne 0) {break} 144 | start-sleep -s 1 145 | } 146 | $TotalRP = $Datasource.TotalRecoveryPoints 147 | if ($Datasource.TotalRecoveryPoints -ne 0){ 148 | for ($i=0;$i -le 5;$i++){ 149 | if([datetime]$DataSource.LatestRecoveryPoint -ne "01/01/0001 0:00:00") {break} 150 | start-sleep -s 1 151 | } 152 | for ($i=0;$i -le 5;$i++){ 153 | if([datetime]$DataSource.OldestRecoveryPoint -ne "01/01/0001 0:00:00") {break} 154 | start-sleep -s 1 155 | } 156 | } 157 | $LastRPTime = [datetime]$DataSource.latestrecoverypoint 158 | $OldestRPTime=[datetime]$Datasource.OldestRecoveryPoint 159 | $DateDif = $(get-date) - $LastRPTime 160 | $Type = $Datasource.ObjectType 161 | $DataSourceName = $DataSource.name 162 | Write-Host "Computer: $Computer" 163 | Write-Host "Type: $Type" 164 | Write-Host "Name: $DataSourceName" 165 | Write-Host "Total recovery points: $TotalRP" 166 | Write-Host "Last recovery point: $LastRPTime" 167 | Write-Host "Oldest recovery point: $OldestRPTime" 168 | Write-Host "------------------------------------------------------------" 169 | 170 | $HTMLFile +="" 171 | 172 | if($OldestRPTime -eq "01/01/0001 0:00:00"){ 173 | $HTMLFile +="" 174 | } 175 | else{ 176 | $HTMLFile +="" 177 | } 178 | 179 | if($DateDif.TotalHours -gt $MinLastBackupHours){ 180 | $HTMLFile +="" 181 | } 182 | else{ 183 | $HTMLFile +="" 184 | } 185 | if($TotalRP -lt $MinRecoveryPoints){ 186 | $HTMLFile +="" 187 | } 188 | else{ 189 | $HTMLFile +="" 190 | } 191 | } 192 | } 193 | } 194 | $HTMLFile+="
$DPMServer
Protection groupComputerObjectTypeOldest RPLast RPNumber of RP
$ProtectionGroupname$Computer$DataSourceName$Type$OldestRPTime$OldestRPTime$LastRPTime$LastRPTime$TotalRP
$TotalRP


" 195 | } 196 | $HTMLFile+="" 197 | 198 | disconnect-dpmserver 199 | }catch 200 | { 201 | write-Host "Error collecting data" -ForegroundColor Red 202 | write-host "Caught an exception:" -ForegroundColor Red 203 | write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red 204 | write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 205 | $HTMLFile="Error collecting data: $($_.Exception.Message)" 206 | } 207 | If ($ReportLocation -ne "None") 208 | { 209 | if(!(Test-Path -Path $ReportLocation)) 210 | { 211 | Write-Output "$ReportLocation does not exists and can not be created." 212 | } 213 | else 214 | { 215 | $timestamp = Get-Date -format yyyy-MM-dd-HH-mm-ss 216 | if ($ReportLocation.Substring($ReportLocation.Length-1) -eq "\") 217 | {$ReportLocation += "$timestamp" + "_DPMBackupReport.html"} 218 | else {$ReportLocation += "\" + "$timestamp" + "_DPMBackupReport.html"} 219 | try{ 220 | ConvertTo-HTML -Body $HTMLFile -Title "DPM Backup Status" | Out-File $ReportLocation 221 | }catch 222 | { 223 | Write-Host "Error storing htlm report" -ForegroundColor Red 224 | write-host "Caught an exception:" -ForegroundColor Red 225 | write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red 226 | write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 227 | } 228 | } 229 | } 230 | 231 | 232 | if($SMTPServer -ne "None") 233 | { 234 | $msg = new-object Net.Mail.MailMessage 235 | $smtp = new-object Net.Mail.SmtpClient($SMTPServer,$Port) 236 | $msg.From = $Sender 237 | $msg.ReplyTo = $Sender 238 | ForEach($mail in $Recipient) 239 | { 240 | $msg.To.Add($mail) 241 | } 242 | if ($Username -ne "None" -and $Password -ne "None") 243 | { 244 | $smtp.Credentials = new-object System.Net.NetworkCredential($Username, $Password) 245 | } 246 | if ($SSL -ne "False") 247 | { 248 | $smtp.EnableSsl = $true 249 | } 250 | $msg.subject = "DPM Backup Status" 251 | $msg.body = $HTMLFile 252 | $msg.IsBodyHtml = $true 253 | try{ 254 | Write-Output "Sending email" 255 | $smtp.Send($msg) 256 | }catch 257 | { 258 | Write-Host "Error sending email" -ForegroundColor Red 259 | write-host "Caught an exception:" -ForegroundColor Red 260 | write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red 261 | write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 262 | } 263 | } -------------------------------------------------------------------------------- /System Center DPM 2012 (R2) HTML Report/readme.md: -------------------------------------------------------------------------------- 1 | # System Center DPM 2012 (R2) HTML Report 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/System%20Center%20DPM%202012%20(R2)%20HTML%20Report/DPMReport.ps1) 4 | 5 | This script Create and send a protection group backup report of System Center Data Protection Manager (DPM) 2012 (R2) Servers based on the recovery points and protection groups. 6 | 7 | ![Report Screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/System%20Center%20DPM%202012%20(R2)%20HTML%20Report/report_screenshot.PNG) 8 | 9 | *Usage* 10 | 11 | `DPMProtectionGroupReport.ps1 [-DPMServers ] [-ProtectionGroups ] [-MinLastBackupHours ] [-MinRecoveryPoints ] [ReportLocation ] [SMTPServer ] [Recipient ] [Sender ] [Username ] [Password ] [-SSL ] [Port ]` 12 | 13 | -------------------------------------------------------------------------------- /System Center DPM 2012 (R2) HTML Report/report_screenshot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/System Center DPM 2012 (R2) HTML Report/report_screenshot.PNG -------------------------------------------------------------------------------- /Take ownership of folder/Take-Own.ps1: -------------------------------------------------------------------------------- 1 | #Requires -RunAsAdministrator 2 | $folder = 'C:\Program Files\WindowsApps' 3 | takeown.exe /f $($folder) /R /D S 4 | icacls.exe $($folder) /grant "$([Security.Principal.WindowsIdentity]::GetCurrent().Name):(OI)(CI)F" /T 5 | $items = Get-ChildItem $folder -ErrorAction SilentlyContinue | Select-Object FullName, Mode, Name 6 | foreach ($item in $items) { 7 | Write-Host "Procesando $($item.FullName)" 8 | takeown.exe /f $($item.FullName) /R /D S >$null 9 | icacls.exe $($item.FullName) /grant "$([Security.Principal.WindowsIdentity]::GetCurrent().Name):(OI)(CI)F" /T >$null 10 | } -------------------------------------------------------------------------------- /Windows Mainteinance/clamav-0.104.0.win.x64.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Windows Mainteinance/clamav-0.104.0.win.x64.zip -------------------------------------------------------------------------------- /Windows Mainteinance/readme.md: -------------------------------------------------------------------------------- 1 | # Windows Maintenance 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Windows%20Mainteinance/Win-Mnt.ps1) 4 | 5 | Performs several commands to check and repair a Windows Computer, Server or Workstation. 6 | 7 | ## Parameters 8 | 9 | - **all** runs all commands. 10 | - **antivirus** runs all antivirus: Windows Defender, Karspersky, McAfee, ClamAV and Adaware. 11 | - **sfc** runs SFC /scannow. 12 | - **dism** runs DISM /Online /Cleanup-Image /RestoreHealth 13 | - **wmi** runs Winmgmt /salvagerepository 14 | - **mof** runs mofcomp.exe from C:\Windows\System32\wbem\AutoRecover 15 | - **defrag** defrag drives if required (Fragmentation > 10%) 16 | - **update** install Microsoft updates (except drivers) 17 | - **defender** runs Windows Defender Update and Quick Scan 18 | - **adaware** runs adaware update and quick/boot Scan 19 | - **kas** runs Kaspersky Virus Removal Tool 20 | - **clamav** runs ClamAV full scan of C:\ 21 | - **mcafee** runs McAfee Stinger 22 | - **LogPath** path where save log file. 23 | *Default: My Documents* 24 | 25 | ## Examples 26 | 27 | Runs all commands. 28 | 29 | ```powershell 30 | Win-Mnt.ps1 -all 31 | ``` 32 | 33 | Runs all antivirus. 34 | 35 | ```powershell 36 | Win-Mnt.ps1 -antivirus 37 | ``` 38 | 39 | Runs sfc and defrag with custom log. 40 | 41 | ```powershell 42 | Win-Mnt.ps1 -sfc -defrag -logPath "\\INFSRV001\Scripts$\Mainteinance\Logs" 43 | ``` 44 | 45 | Runs sfc, dism and adaware scan. 46 | 47 | ```powershell 48 | Win-Mnt.ps1 -sfc -dism -adaware 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /Windows Server Backup Email Report of Several Servers/Get-WSBReport.ps1: -------------------------------------------------------------------------------- 1 | <#PSScriptInfo 2 | 3 | .VERSION 1.1 4 | 5 | .GUID dcdf38ce-c0b8-4fc3-a42e-519ae3191c10 6 | 7 | .AUTHOR Juan Granados 8 | 9 | .COMPANYNAME 10 | 11 | .COPYRIGHT 2021 Juan Granados 12 | 13 | .TAGS Backup Report Windows Server Email 14 | 15 | .LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE 16 | 17 | .PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Windows%20Server%20Backup%20Email%20Report%20of%20Several%20Servers 18 | 19 | .ICONURI 20 | 21 | .EXTERNALMODULEDEPENDENCIES 22 | 23 | .REQUIREDSCRIPTS 24 | 25 | .EXTERNALSCRIPTDEPENDENCIES 26 | 27 | .RELEASENOTES 28 | Collect information about Windows Server Backup on a list of servers and show results on console. It has the posibility of generate an send an html report with backup results. 29 | #> 30 | 31 | <# 32 | .SYNOPSIS 33 | This script collect information about Windows Server Backup on a list of servers. 34 | Requires Windows Server Backup Command Line Tools installed on remote servers (Add-WindowsFeature Backup-Tools) 35 | .DESCRIPTION 36 | This script collect information about Windows Server Backup on a list of servers and show results on console. It has the posibility of generate an send an html report with backup results. 37 | .PARAMETER Servers 38 | Full path of a file containing the list of servers to check Windows Server Backup Status 39 | Default "C:\Scripts\Servers.txt" 40 | .PARAMETER HtmlReport 41 | Folder to store html report file. 42 | Default "C:\Scripts\" 43 | .PARAMETER SMTPServer 44 | Sets smtp server in order to sent an email with backup result. 45 | Default: None 46 | .PARAMETER Recipient 47 | List of emails addresses which will receive the backup result separated by commas. 48 | Default: None 49 | .PARAMETER Sender 50 | Email address which will send the backup result. 51 | Default: None 52 | .PARAMETER Username 53 | Username in case of smtp server requires authentication. 54 | Default: None 55 | .PARAMETER Password 56 | Password in case of smtp server requires authentication. 57 | Default: None 58 | .PARAMETER SSL 59 | Use of SSL in case of smtp server requires SSL. 60 | Default: False 61 | .PARAMETER Port 62 | Port to connect to smtp server. 63 | Default: 25 64 | .EXAMPLE 65 | .\Get-WSBReport.ps1 -ServerList C:\Scripts\servers_contoso.txt -HtmlReport \\SERVER1\Reports\ -SMTPServer mail.contoso.com -Sender soporte@contoso.com -Recipient jgranados@contoso.com,administrador@contoso.com -Username contoso\jgranados -Password P@ssw0rd 66 | .NOTES 67 | Author: Juan Granados 68 | #> 69 | Param( 70 | [Parameter(Mandatory=$false,Position=0)] 71 | [ValidateNotNullOrEmpty()] 72 | [string]$ServerList="C:\Scripts\Servers.txt", 73 | [Parameter(Mandatory=$false,Position=1)] 74 | [ValidateNotNullOrEmpty()] 75 | [string]$HtmlReport="C:\Scripts\", 76 | [Parameter(Mandatory=$false,Position=2)] 77 | [ValidateNotNullOrEmpty()] 78 | [string]$SMTPServer="None", 79 | [Parameter(Mandatory=$false,Position=3)] 80 | [ValidateNotNullOrEmpty()] 81 | [string[]]$Recipient, 82 | [Parameter(Mandatory=$false,Position=4)] 83 | [ValidateNotNullOrEmpty()] 84 | [string]$Sender, 85 | [Parameter(Mandatory=$false,Position=5)] 86 | [ValidateNotNullOrEmpty()] 87 | [string]$Username="None", 88 | [Parameter(Mandatory=$false,Position=6)] 89 | [ValidateNotNullOrEmpty()] 90 | [string]$Password="None", 91 | [Parameter(Mandatory=$false,Position=7)] 92 | [ValidateSet("True","False")] 93 | [string[]]$SSL="False", 94 | [Parameter(Mandatory=$false,Position=8)] 95 | [ValidateNotNullOrEmpty()] 96 | [int]$Port=25 97 | ) 98 | 99 | $timestamp = Get-Date -format yyyy-MM-dd-HH 100 | 101 | #Check if server list exists 102 | If (!(Test-Path $ServerList)){ 103 | Write-Host "Can not get servers list. Script will not continue" -ForegroundColor Red;Exit} 104 | 105 | $servers = @() 106 | 107 | Get-Content $ServerList | Foreach-Object {$servers+=$_} 108 | 109 | $results = (1..$servers.length) 110 | 111 | for ($i=0; $i -lt $servers.length; $i++) 112 | { 113 | $ConnectionError=0 114 | Write-Host "Getting result from server: " $servers[$i] 115 | try{ 116 | $Session = New-PSSession -ComputerName $servers[$i] 117 | $WindowsVersion = Invoke-Command -session $session -ScriptBlock {(Get-WmiObject win32_operatingsystem).version} 118 | if ($WindowsVersion -match "6.1") 119 | {$WBSummary = Invoke-Command -session $session -ScriptBlock {add-pssnapin windows.serverbackup;Get-WBSummary}} 120 | else {$WBSummary = Invoke-Command -session $session -ScriptBlock {Get-WBSummary}} 121 | Remove-PSSession $Session 122 | }catch 123 | { 124 | Write-Host "Error connecting remote server" 125 | write-host "Caught an exception:" -ForegroundColor Red 126 | write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red 127 | write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 128 | $ConnectionError=1 129 | } 130 | 131 | #Storing results 132 | $results[$i] = New-Object Collections.Arraylist 133 | 134 | $results[$i].add("" + $servers[$i] + "") > $null 135 | 136 | if ($ConnectionError -eq 1) 137 | { 138 | $results[$i].add("Unknown") > $null 139 | $results[$i].add("" + "Unknown" + "") > $null 140 | $results[$i].add("" + "Error connecting remote server" + "") > $null 141 | $results[$i].add("" + "Unknown" + "") > $null 142 | } 143 | else 144 | { 145 | if ($WBSummary.LastBackupResultHR -eq 0) {$results[$i].add("Success") > $null;$result="Success"} 146 | else {$results[$i].add("Failure") > $null;$result="Failure"} 147 | 148 | $results[$i].add("" + $WBSummary.LastSuccessfulBackupTime + "") > $null 149 | 150 | if ([string]::IsNullOrEmpty($WBSummary.DetailedMessage)){$results[$i].add("Success") > $null;$message="Success"} 151 | else{$results[$i].add("" + $WBSummary.DetailedMessage + "") > $null;$message= $WBSummary.DetailedMessage} 152 | 153 | $results[$i].add("" + $WBSummary.NumberOfVersions + "") > $null 154 | 155 | 156 | Write-Host "Last Backup Result: $result" 157 | Write-Host "Last Successful Backup Time:" $WBSummary.LastSuccessfulBackupTime 158 | Write-Host "Detailed Message: $message" 159 | Write-Host "Number of Backups:" $WBSummary.NumberOfVersions 160 | Write-Host "-----------------------------------------------------------------" 161 | } 162 | } 163 | 164 | if ([boolean](get-variable "rows" -ErrorAction SilentlyContinue)) 165 | {Clear-Variable -Name "rows" -Scope Global} 166 | 167 | for ($i=0; $i -lt $servers.length; $i++) 168 | { 169 | $rows=$rows +"" + $results[$i] + "" 170 | } 171 | 172 | $HTMLFile = @" 173 | 174 | 175 | 176 | 177 | 181 | 182 | 183 |

Windows Server Backup Status

184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | $rows 193 |
ServerLast Backup ResultLast Backup Result TimeMessageNumber of backups
194 | 195 | 196 | "@ 197 | if ([boolean](get-variable "ReportPath" -ErrorAction SilentlyContinue)) 198 | {Clear-Variable -Name "ReportPath" -Scope Global} 199 | $ReportPath = $ReportPath + "$timestamp" + "_WSBReport.html" 200 | try{ 201 | ConvertTo-HTML -Body $HTMLFile -title "Windows Server Backup Report" | Out-File $ReportPath 202 | }catch 203 | { 204 | Write-Host "Error storing htlm report" -ForegroundColor Red 205 | write-host "Caught an exception:" -ForegroundColor Red 206 | write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red 207 | write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 208 | } 209 | # Mail sending 210 | if($SMTPServer -ne "None") 211 | { 212 | #Creating a Mail object 213 | $msg = new-object Net.Mail.MailMessage 214 | 215 | #Creating SMTP server object 216 | $smtp = new-object Net.Mail.SmtpClient($SMTPServer,$Port) 217 | 218 | #Email structure 219 | $msg.From = $Sender 220 | $msg.ReplyTo = $Sender 221 | ForEach($mail in $Recipient) 222 | { 223 | $msg.To.Add($mail) 224 | } 225 | if ($Username -ne "None" -and $Password -ne "None") 226 | { 227 | $smtp.Credentials = new-object System.Net.NetworkCredential($Username, $Password) 228 | } 229 | if ($SSL -ne "False") 230 | { 231 | $smtp.EnableSsl = $true 232 | } 233 | #Email subject 234 | $msg.subject = "Windows Server Backup Status" 235 | #Email body 236 | $msg.body = $HTMLFile 237 | $msg.IsBodyHtml = $true 238 | #Sending email 239 | try{ 240 | Write-Output "Sending email" 241 | $smtp.Send($msg) 242 | }catch 243 | { 244 | Write-Host "Error sending email" -ForegroundColor Red 245 | write-host "Caught an exception:" -ForegroundColor Red 246 | write-host "Exception Type: $($_.Exception.GetType().FullName)" -ForegroundColor Red 247 | write-host "Exception Message: $($_.Exception.Message)" -ForegroundColor Red 248 | } 249 | } -------------------------------------------------------------------------------- /Windows Server Backup Email Report of Several Servers/Servers.txt: -------------------------------------------------------------------------------- 1 | SRV01 2 | SRV02 3 | SRV03 4 | SRV04 5 | SRV05 6 | SRV06 7 | SRV07 8 | SRV08 9 | SRV09 10 | SRV010 11 | SRV011 12 | SRV012 -------------------------------------------------------------------------------- /Windows Server Backup Email Report of Several Servers/readme.md: -------------------------------------------------------------------------------- 1 | # Windows Server Backup Email Report of Several Servers 2 | 3 | [Right click here and select "Save link as" to download](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Windows%20Server%20Backup%20Email%20Report%20of%20Several%20Servers/Get-WSBReport.ps1) 4 | 5 | Performs a Windows Server Backup HTML report of a servers list and send it via email. 6 | 7 | ![Screenshot](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Windows%20Server%20Backup%20Email%20Report%20of%20Several%20Servers/wsb.PNG) 8 | 9 | It gets server list from a file called 'Servers.txt'. [Right click here and select "Save link as" to download an example of 'Servers.txt'](https://raw.githubusercontent.com/juangranados/powershell-scripts/main/Windows%20Server%20Backup%20Email%20Report%20of%20Several%20Servers/Servers.txt) 10 | 11 | Requires Windows Server Backup Command Line Tools installed on remote servers. On each server, open PowerShell console as administrator and run `Add-WindowsFeature Backup-Tools` 12 | 13 | *Example* 14 | 15 | ```powershell 16 | Get-WSBReport.ps1 -ServerList C:\Scripts\servers_contoso.txt -HtmlReport \\SERVER1\Reports\ -SMTPServer mail.contoso.com -Sender soporte@contoso.com -Recipient jgranados@contoso.com,administrador@contoso.com -Username contoso\jgranados -Password P@ssw0rd 17 | ``` 18 | 19 | *Full description* 20 | 21 | ```powershell 22 | <# 23 | .SYNOPSIS 24 | This script collect information about Windows Server Backup on a list of servers. 25 | Requires Windows Server Backup Command Line Tools installed on remote servers (Add-WindowsFeature Backup-Tools) 26 | .DESCRIPTION 27 | This script collect information about Windows Server Backup on a list of servers and show results on console. It has the posibility of generate an send an html report with backup results. 28 | Usage: Get-WSBReport.ps1 [-Servers ] [-HtmlReport ] [SMTPServer ] [Recipient ] [Sender ] [Username ] [Password ] [-SSL ] [Port ] 29 | .PARAMETER Servers 30 | Full path of a file containing the list of servers to check Windows Server Backup Status 31 | Default "C:\Scripts\Servers.txt" 32 | .PARAMETER HtmlReport 33 | Folder to store html report file. 34 | Default "C:\Scripts\" 35 | .PARAMETER SMTPServer 36 | Sets smtp server in order to sent an email with backup result. 37 | Default: None 38 | .PARAMETER Recipient 39 | List of emails addresses which will receive the backup result separated by commas. 40 | Default: None 41 | .PARAMETER Sender 42 | Email address which will send the backup result. 43 | Default: None 44 | .PARAMETER Username 45 | Username in case of smtp server requires authentication. 46 | Default: None 47 | .PARAMETER Password 48 | Password in case of smtp server requires authentication. 49 | Default: None 50 | .PARAMETER SSL 51 | Use of SSL in case of smtp server requires SSL. 52 | Default: False 53 | .PARAMETER Port 54 | Port to connect to smtp server. 55 | Default: 25 56 | .EXAMPLE 57 | .\Get-WSBReport.ps1 -ServerList C:\Scripts\servers_contoso.txt -HtmlReport \\SERVER1\Reports\ -SMTPServer mail.contoso.com -Sender soporte@contoso.com -Recipient jgranados@contoso.com,administrador@contoso.com -Username contoso\jgranados -Password P@ssw0rd 58 | .NOTES 59 | Author: Juan Granados 60 | #> 61 | ``` 62 | 63 | -------------------------------------------------------------------------------- /Windows Server Backup Email Report of Several Servers/wsb.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juangranados/powershell-scripts/72a4a09a77f385461c2e427359d8e98808084029/Windows Server Backup Email Report of Several Servers/wsb.PNG -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Script index 2 | 3 | Click in any link to read full description 4 | 5 | - [Install software on multiple computers remotely with PowerShell](https://github.com/juangranados/powershell-scripts/tree/main/Install%20Software%20Remotely): install software remotely in a group of computers and retry the installation in case of error. It uses PowerShell to perform remote installation, WMI and reg query to filter target computers. 6 | - [Windows Server Backup Report of several servers](https://github.com/juangranados/powershell-scripts/tree/main/Windows%20Server%20Backup%20Email%20Report%20of%20Several%20Servers): performs a Windows Server Backup HTML report of a servers list and send it via email. 7 | - [Change Lock Screen and Desktop Background in Windows 10 Pro](https://github.com/juangranados/powershell-scripts/tree/main/Change%20Lock%20Screen%20and%20Desktop%20Background%20in%20Windows%2010%20Pro): change logon screen and desktop background in Windows 10 Professional using GPO startup script. 8 | - [GUI Password Reset Tool for Active Directory](https://github.com/juangranados/powershell-scripts/tree/main/GUI%20Password%20Reset%20Tool%20for%20Active%20Directory): script for IT support team which allow to reset AD users password in a GUI interface. 9 | - [Optimize and cleanup of WSUS](https://github.com/juangranados/powershell-scripts/tree/main/Optimize%20and%20cleanup%20of%20WSUS%20on%20Windows%20Server%202012%20R2%20and%202016): Optimize WSUS DB using official Microsoft SQL script and performs server maintenance. Supports Server 2019, 2016 and 2012 R2. 10 | - [Remote computer update](https://github.com/juangranados/powershell-scripts/tree/main/Remote%20Computer%20Update): Launch Windows Update and software update using RuckZuck on remote or local computers. 11 | - [File Server Access Audit Report](https://github.com/juangranados/powershell-scripts/tree/main/File%20Server%20Access%20Audit%20Report%20with%20PowerShell): audit several file servers and send a report in CSV and HTML by mail. HTML report can filter and sorting rows by server, time, user, file or operation (read, delete or write). CSV file can be import on Excel to generate a File Audit Report. 12 | - [Delete reappearing printers that keeps coming back](https://github.com/juangranados/powershell-scripts/tree/main/Delete%20reappearing%20printers%20that%20keeps%20comming%20back): delete ghost printers that keeps coming back after deletion. 13 | - [Email Report of File Permissions on HTML and CSV](https://github.com/juangranados/powershell-scripts/tree/main/Email%20Report%20of%20File%20Permissions%20on%20HTML%20and%20CSV): starting with a root folder, it generates a folders permissions report. Number of sub folders examined depends on FolderDeep parameter. Report is generated in CSV format and can be send attached via mail with a HTML report in the body. 14 | - [SQL Server Backup](https://github.com/juangranados/powershell-scripts/tree/main/SQL%20Server%20Backup): perform full and log backup of Microsoft SQL Server databases into a zip file. You can specify what databases will be saved and the remote location of backup. 15 | - [Install Printer Drivers Remotely](https://github.com/juangranados/powershell-scripts/tree/main/Install%20Print%20Drivers%20Remotely): allows remote printer drivers installation on a group of computers or servers. 16 | - [Bulk Change PrinterDriverAttributes for non Package-Aware printer drivers](https://github.com/juangranados/powershell-scripts/tree/main/Bulk%20Change%20PrinterDriverAttributes%20for%20non%20Package-Aware%20printer%20drivers): since KB3170455, deployed Printers via Group Policy does not install non Package-Aware printer drivers automatically. Users are prompted with a message saying "Do yo trust this printer?" 17 | - [Windows Maintenance](https://github.com/juangranados/powershell-scripts/tree/main/Windows%20Mainteinance): Performs several commands to check and repair a Windows Computer, Server or Workstation. 18 | - [System Center DPM 2012 (R2) HTML Report](https://github.com/juangranados/powershell-scripts/tree/main/System%20Center%20DPM%202012%20(R2)%20HTML%20Report): create and send a protection group backup report of System Center Data Protection Manager (DPM) 2012 (R2) Servers based on the recovery points and protection groups. 19 | --------------------------------------------------------------------------------