Check if certain VMs are backed up - powershell

I am looking for a way to check if a list of VMs has backup and what is the status.
I managed to get the status of VM backups but if the VM was not found in the $tasks I am not getting error.
I need to know if a VM is not present in the $tasks so that I know that no backup is configred for this VM.
The script so far.
Write-Host "Enter Backup Server" -ForegroundColor cyan
$h = read-host -Prompt 'Hostname'
Write-Host " "
write-host "Hostname--------Job-------------Status " -ForegroundColor Cyan
Foreach ($i in $Hostname) {
Invoke-Command -ComputerName $h -ScriptBlock {
Add-PSSnapin VeeamPSSnapin
foreach($Job in (Get-VBRJob))
{
$Session = $Job.FindLastSession()
if(!$Session){continue;}
$Tasks = $Session.GetTaskSessions()
$Tasks | ?{$_.Name -eq $using:i} | %{write-host $_.Name ":",$_.JobName,"===>"$_.Status}
}}
}
Thanks in advance!
Valeri

You are nearly there, you just need to unify your exit clause so that whether a VM has backup or not, it outputs something similar. Then when you run against a list of VMs and one or two aren't backed up, they'll appear in the output in a way that makes sense.
Foreach ($i in $Hostname) {
Invoke-Command -ComputerName $h -ScriptBlock {
Add-PSSnapin VeeamPSSnapin
foreach($Job in (Get-VBRJob))
{
$Session = $Job.FindLastSession()
if(!$Session){
[pscustomObject]#{
computerName = $_.Name;
backupStatus = "Not backed up!";
jobs = $null;
}
$Tasks = $Session.GetTaskSessions()
$jobs = $Tasks | ?{ $_.Name -eq $using:i } | Select -ExpandProperty JobName
[PSCustomObject]#{
computerName = $_.Name;
backupStatus = "Backed up";
jobs = ($jobs -join ",")
}
}
}
}
With a few small tweaks, we now emit an object back if a machine doesn't have any results for $Session, but now also return a similar object for machines that do have backups enabled.
You will likely need to tweak the code to your desired results, as I don't have Veeam available I can't quite nail down what you'd like but this should get you going.
And as a perk getting PowrShell objects back is easier to work with. You can save them in a json file or csv, or run this automatically in the off hours and review the results later, all which are much easier than using Write-Host commands.

I managed to acheave my goal by comairing all backed up VMs with the VMs from my list:
Write-host "Unprotected VMs (No backup)" -ForegroundColor RED
Write-host "---------------------------" -ForegroundColor RED
invoke-command -computername $h -ScriptBlock {
Add-PSSnapin VeeamPSSnapin
$backup=Get-VBRBackupSession | Where-Object {$_.JobType -eq "Backup" -or $_.JobType -eq "Replica" -and $_.EndTime}|foreach{$_.gettasksessions() | Where-Object {$_.Status -ne "Failed"}} |foreach{$_.Name} | Sort-Object | Get-Unique
$diff=Compare-Object $using:hostname $backup| ? { $_.SideIndicator -eq "<=" } | Select -ExpandProperty InputObject
$diff
As a result I am getting only the VMs which are missing from $backup and are present only in my list $hostnames .

Related

How to return a list from ForEach-Object in powershell 7.2.1

I have a library of Powershell 5.1 scripts and I want to rewrite some of them in Powershell 7.2.1. Mainly because of the new parallel execution of ForEach-Object.
Here is simplified example of script written in Powershell 5.1 that Test-Connection ForEach-Object in $computers list and add pc either to $OnlinePc list or $OfflinePc list.
$color = "Gray"
$Major = ($PSVersionTable.PSVersion).Major
$Minor = ($PSVersionTable.PSVersion).Minor
Write-Host "My Powershell version: " -NoNewline -ForegroundColor $color
Write-Host "$Major.$Minor"
Write-Host
$computers = #(
'172.30.79.31',
'172.30.79.32',
'172.30.79.33',
'172.30.79.34',
'172.30.79.35',
'172.30.79.36',
'172.30.79.37'
)
Write-Host "List of all computers:" -ForegroundColor $color
$computers
foreach ($pc in $computers) {
if (Test-Connection -Count 1 $pc -ErrorAction SilentlyContinue) {
$OnlinePc+=$pc
}
else {
$OfflinePc+=$pc
}
}
Write-Host
Write-Host "List of online computers:" -ForegroundColor $color
$OnlinePc
Write-Host
Write-Host "List of offline computers:" -ForegroundColor $color
$OfflinePc
Write-Host
pause
And here is same script rewtitten in Powershell 7.2.1
$color = "Gray"
$Major = ($PSVersionTable.PSVersion).Major
$Minor = ($PSVersionTable.PSVersion).Minor
$Patch = ($PSVersionTable.PSVersion).Patch
Write-Host "My Powershell version: " -NoNewline -ForegroundColor $color
Write-Host "$Major.$Minor.$Patch"
Write-Host
$computers = #(
'172.30.79.31',
'172.30.79.32',
'172.30.79.33',
'172.30.79.34',
'172.30.79.35',
'172.30.79.36',
'172.30.79.37'
)
Write-Host "List of all computers:" -ForegroundColor $color
$computers
$computers | ForEach-Object -Parallel {
if (Test-Connection -Count 1 $_ -ErrorAction SilentlyContinue) {
$OnlinePc+=$_
}
else {
$OfflinePc+=$_
}
}
Write-Host
Write-Host "List of online computers:" -ForegroundColor $color
$OnlinePc
Write-Host
Write-Host "List of offline computers:" -ForegroundColor $color
$OfflinePc
Write-Host
pause
Here is picture of output from both scripts.
Outputs
I tried to edit the ForEach-Object syntax in many ways, but I can't get it to work the same way it worked in 5.1, any tips would be apperacited.
PS: Computers 172.30.79.32 and 172.30.79.33 are offline, the others are online.
As commenters noted, script blocks in a ForEach-Object -Parallel don't have direct access to surrounding variables as they run in isolated runspaces.
While you could use the $using keyword to work around this situation (as show in this QA), a more idiomatic approach is to capture the output of ForEach-Object in a variable. This automatically produces an array if more than one objects are output. By storing the online state in a property, we can later split that array to get two separate lists for online and offline PCs.
$computerState = $computers | ForEach-Object -Parallel {
[pscustomobject]#{
Host = $_
Online = Test-Connection -Count 1 $_ -ErrorAction SilentlyContinue -Quiet
}
}
Write-Host
Write-Host "List of online computers:" -ForegroundColor $color
$computerState.Where{ $_.Online -eq $true }.Host
Write-Host
Write-Host "List of offline computers:" -ForegroundColor $color
$computerState.Where{ $_.Online -eq $false }.Host
Notes:
[pscustomobject]#{…} dynamically creates an object and implicitly outputs it, which gets captured in $computerState, automatically creating an array if necessary. This is more efficient than using the array += operator, which has to reallocate and copy the array elements for each new element to be added, because arrays are actually of fixed size (more info).
Parameter -Quiet is used so that Test-Connection outputs a [bool] value to be stored in the Online property.
.Where{…} is an intrinsic method that PowerShell provides for all objects. Similarly to Where-Object it acts as a filter, but is faster and the syntax is more succinct.
Finally by writing .Host we make use of PowerShell's convenient member access enumeration feature, which creates an array from the Host property of the filtered $computerState items.
When using ForEach-Object -Parallel each object is processed in a different runspace and thread (as mentioned by mclayton in a comment). Variables are not accessible across runspaces in most cases. Below are 2 possible alternative ways to use -Parallel
The following code does not assign or use any variables inside the Foreach-Object -Parallel block. Instead online computers are assigned to a variable outside.
$computers = #(
'localhost',
'www.google.com',
'notarealcomputer',
'www.bing.com',
'www.notarealwebsitereally.com'
)
# Assign the output of foreach-object command directly into a variable rather than assigning variables inside
$onlinePc = $computers | ForEach-Object -Parallel {
$_ | Where-Object { Test-Connection -Count 1 -Quiet $_ -ErrorAction SilentlyContinue }
}
# determine offline computers by checking which are not in $onlinePc
$offlinePc = $computers | Where-Object { $_ -notin $onlinePc }
Write-Host '--- Online ---' -ForegroundColor Green
$onlinePc
Write-Host
Write-Host '--- Offline ---' -ForegroundColor Red
$offlinePc
This next method uses a synchronized hashtable and the $using statement to collect the data inside the -Parallel block
$computers = #(
'localhost',
'www.google.com',
'notarealcomputer',
'www.bing.com',
'www.notarealwebsitereally.com'
)
# Create a hashtable containing empty online and offline lists
$results = #{
'online' = [System.Collections.Generic.List[string]]::new()
'offline' = [System.Collections.Generic.List[string]]::new()
}
# thread-safe wrapped hashtable
$syncResults = [hashtable]::Synchronized($results)
$computers | ForEach-Object -Parallel {
if ($_ | Test-Connection -Quiet -Count 2 -ErrorAction SilentlyContinue) {
($using:syncResults).online.add($_)
}
else {
($using:syncResults).offline.add($_)
}
}
$results
Output
Name Value
---- -----
online {localhost, www.bing.com, www.google.com}
offline {www.notarealwebsitereally.com, notarealcomputer}
I do not claim to be an expert. There are likely better ways to do this. Just thought I'd share a couple that I know.

Script to disable RDP

I'm trying to disable RDP using powershell.
I've tried the following code, but the values on the machine name I'm listing aren't changing.
$file = Get-Content c:\PSscripts\regchange\computers.txt
foreach ($computername in $file){
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$computername'" | Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computername )
$regKey= $reg.OpenSubKey("System\\CurrentControlSet\\Control\\Terminal Server" ,$true)
$regKey.SetValue("fDenyTSConnections","1",[Microsoft.Win32.RegistryValueKind]::dword)
}
else {
Write-Host "$computername unreachable"
}
}
I suspect there's something wrong with the way I entered the registry path name. any help would be appreciated.
The issue must be either permissions (which I assume you have as there are no obvious error messages), refreshing issue or in Get-Content and the structure of your file.
In order for Get-Content to work in this manner, each computer on a separate line. e.g.:
MyComputer1
MyComputer2
Another troubleshooting step is to try adding in Write-Host $computername entries to verify that you are looping through properly.:
$file = Get-Content c:\PSscripts\regchange\computers.txt
foreach ($computername in $file){
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$computername'" | Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
Write-Host "$computername set"
}
else {
Write-Host "$computername unreachable"
}
}
You can also confirm by adding in a $regKey.GetValue after setting:
$file = Get-Content c:\PSscripts\regchange\computers.txt
foreach ($computername in $file){
$PingStatus = Gwmi Win32_PingStatus -Filter "Address = '$computername'" | Select-Object StatusCode
If ($PingStatus.StatusCode -eq 0){
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computername )
$regKey= $reg.OpenSubKey("System\\CurrentControlSet\\Control\\Terminal Server" ,$true)
$regKey.SetValue("fDenyTSConnections","1",[Microsoft.Win32.RegistryValueKind]::dword)
Write-Host "$computername set to: $($regKey.GetValue("fDenyTSConnections"))"
}
else {
Write-Host "$computername unreachable"
}
}
Manually setting $computername = "MyComputer" and running the code, I can confirm that the code for setting the registry works... I can also confirm that remotely killing your RDP access to your remote virtual workstation also works.. and... is as terrible as it sounds ;-)
If PSRemoting is enabled, try something like this …
(This needs to be executed in a PowerShell elevated admin session.)
Get-Content -Path 'c:\PSscripts\regchange\computers.txt' |
ForEach{
If (Test-Connection -$PSItem -Count 1 -Quiet)
{
$paramblock = #{
Path = 'HKLM:\System\CurrentControlSet\Control\Terminal Server'
Name = 'fDenyTSConnections'
Value = '1'
}
Invoke-Command –Computername $PSItem –ScriptBlock {Set-ItemProperty #paramblock}
}
Else
{Write-Warning -Message "Either the host $PSItem is offline or not reachable."}
}

Function within a Function - Powershell

OK I am going to try to explain this as best as I can. What started out as a simple script has turned into a huge mess and now I cannot figure out how to get it working. I have been coming here for answers for some time so maybe you guys can help.
What I am trying to do is a import a list of systems and check to see if they are online. If they are online they go in one list and if not they go in another.
foreach ($server in $servers) {
if (Test-Connection $server -Count 1 -ea 0 -Quiet) {
Write-Host "$server Is Up" -ForegroundColor Green
$server | out-file -Append $liveSystems -ErrorAction SilentlyContinue
} else {
Write-Host "$server Is Down" -ForegroundColor Red
$server | out-file -Append $inactive -ErrorAction SilentlyContinue
}
}
From there I check to see if the application I need installed is on the systems. That is where things start to go off-track. When I run the function to process the $liveSystems file all I get is the last line of the file (or the same system over and over) and not each system as it should be.
function Is-Installed( $program ) {
$x86 = ((Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
$x64 = ((Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
}
$program
function process-file1 {
param($filename)
Get-Content $filename -PipelineVariable line | ForEach-Object {
Is-Installed -program "My_Service"
if (Is-Installed -eq "True") {
Write-Host "$server has agent installed" -ForegroundColor Green
$server | Out-File $installed -ErrorAction SilentlyContinue
}
else
{
Write-Host "$server does not have agent installed" -ForegroundColor Red
$server | Out-File -Append $notInstalled -ErrorAction SilentlyContinue
}
}
}
process-file1 -filename $liveSystems
Once I can get the systems to process through the list of installed and not installed I am trying to take the list of installed systems and check which ones have the service running and which ones do not.
$array = #()
foreach($i in (gc $installed)) {
$svc = Get-Service my_service -ComputerName $i -ea "0"
$obj = New-Object psobject -Property #{
Name = $svc.name
Status = $svc.status
Computer = $i
}
$array += $obj
}
$array | Select Computer,Name,Status | Export-Csv -Path $resultsFile -
NoTypeInformation
Last but not least I run through that list of running and not running and attempt to start the service on systems that are not running.
function process-CSVfile2 {
param($filename)
Import-Csv $filename |
ForEach-Object -PipelineVariable object {
if($_.Status -eq "Running") {
Write-Host "Your Service is currently Running on" $_.Computer
}
if($_.Status -eq "Stopped") {
$serviceName = 'my_service'
$service = Get-CimInstance Win32_Service -ComputerName $_.Computer -Filter "Name=$serviceName"
$service.Start()
$service.WaitForStatus("Started",'00:00:30')
Start-Sleep 10
}
}
}
Several of these blocks run separately but when put together they will not run. I can't seem to get past the second block where it just looks at the same line over and over.
In addition there is a piece I have been trying to get working that would install the application on systems that do not have the service installed but that is not working either but I will save that for a different time.
If anyone can help me with this I would really appreciate it. After 3 days of trying to get it running I am at my wits end.
I'd create objects and properties instead of files with computers online etc...
Something like:
$Computers=New-Object -TypeName System.Collections.ArrayList
$Servers = #(Get-Content -path c:\servers.txt)
$Servers = $Servers | ? {$_} | select-object -uniqe |ForEach-Object {$_.TrimEnd()}
$Servers|ForEach-Object {
$tempobj=New-Object -TypeName PSObject
$tempobj | Add-Member -type NoteProperty -name Name -value $_
$tempobj | Add-Member -type NoteProperty -name isOnline -value $FALSE
$tempobj | Add-Member -type NoteProperty -name Installed -value $FALSE
$tempobj | Add-Member -type NoteProperty -name serviceRunning -value $FALSE
[void]$Computers.Add($tempobj)
then You could work on array (no need for additional files)
$Computers|Where-Object {$_.isOnline -eq $TRUE}
etc

Powershell output logging when using a text file to gather server names

Have a bit of an issue whereby would like to figure out the best way to handle success or failures. Have a powershell query which checks the dcom port range, if it is within the specified value output to a success file, if not a failure file. The issue is, it seems to be outputting the entire serverlist.txt for a success and need to know a way to break this down so it only appends a server (either success/failure) to it, not all at once.
Here is the powershell script contents:
powershell -executionpolicy bypass .\DCOMPortRange.ps1
Where DCOMPortRange.ps1 contains
$computername = Get-Content -Path "C:\Folderpath\serverlist.txt"
$val = (Get-ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -ExpandProperty Ports
if($val -eq "50000-50500")
{
Write-Output "$computername" | out-file C:\folderpath\Success.log -append
} Else {
Write-Output "$computername" | out-file C:\folderpath\Failure.log -append
}
The issue is the error path lets say is a success it appends the entire server list.
Please advise?
This is how I would do it. This does require that you do have PSremoting enabled on the servers
$computername = Get-Content -Path "C:\Folderpath\serverlist.txt"
ForEach ($server in $computername) {
$val = Invoke-Command -Computername $server -ScriptBlock {(Get-ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -ExpandProperty Ports}
if ($val -ge 50000 -and $val -le 50500) {
Write-Output "$server" | out-file C:\folderpath\Success.log -append
}
Else {
Write-Output "$server" | out-file C:\folderpath\Failure.log -append
}
}
Edit: A change to the if statement
/Anders
$remotecomputername = #("PC1","PC2","RealServerName")
ForEach ($computer in $remotecomputername) {
Invoke-Command -Computername $computer -ScriptBlock { $val = (Get-
ItemProperty "hklm:SOFTWARE\Microsoft\Rpc\Internet") | Select-Object -
ExpandProperty Ports} }
if($val -eq "50000-50500") {
write-host $computer DCOM Port in Range
} else {
write-host $computer DCOM Port not in range
}

Using Powershell To Distribute Script Level Jobs On Remote Servers

More of a theory question...
I have a powershell script that exists on three servers. In this example the three servers are:
server1
server2
server3
I am using another machine, server4, to call script C:\ExampleScript.ps1 remotely using Invoke-Command while specifying the remote machine via the ComputerName parameter. The ultimate goal of the script is to detect whether powershell is running, if it is not, then the computer is "not busy" and can open up the script being called remotely. If the computer is "busy", move onto the next server and continue on through the three machines until all the parameter values have been exhausted. If all machines are busy, it would be ideal if there was a way to periodically check the processes and see if they are still open. In this way, execution of the script can be balanced across the various machines, in an albeit primitive fashion.
Consider the following code:
$servers = "server1","server2","server3"
$data = "param1", "param2", "param3", "param4", "param5", "param6"
#somehow loop through the different servers/data using the above arrays
$job = Invoke-Command $servers[0] {
$ProcessActive = Get-Process powershell -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
"Running"
Invoke-Command -ComputerName $env:computername -FilePath C:\ExampleScript.ps1 -ArgumentList $data[0]
}
else
{
"Busy go to next machine"
}
} -AsJob
Wait-Job $job
$r = Receive-Job $job
$r
The expected result trying to be achieved is attempting to load balance the script across the machines based on whether there is an active powershell process, if not move onto the next machine and perform the same test and subsequent possible execution. The script should go through all the values as specified in the $data array (or whatever).
I found this question interesting, so I wanted to give it a try.
$servers = "server1","server2","server3"
$data = New-Object System.Collections.ArrayList
$data.AddRange(#("param1", "param2", "param3", "param4", "param5", "param6"))
$jobs = New-Object System.Collections.ArrayList
do
{
Write-Host "Checking job states." -ForegroundColor Yellow
$toremove = #()
foreach ($job in $jobs)
{
if ($job.State -ne "Running")
{
$result = Receive-Job $job
if ($result -ne "ScriptRan")
{
Write-Host " Adding data back to que >> $($job.InData)" -ForegroundColor Green
$data.Add($job.InData) | Out-Null
}
$toremove += $job
}
}
Write-Host "Removing completed/failed jobs" -ForegroundColor Yellow
foreach ($job in $toremove)
{
Write-Host " Removing job >> $($job.Location)" -ForegroundColor Green
$jobs.Remove($job) | Out-Null
}
# Check if there is room to start another job
if ($jobs.Count -lt $servers.Count -and $data.Count -gt 0)
{
Write-Host "Checking servers if they can start a new job." -ForegroundColor Yellow
foreach ($server in $servers)
{
$job = $jobs | ? Location -eq $server
if ($job -eq $null)
{
Write-Host " Adding job for $server >> $($data[0])" -ForegroundColor Green
# No active job was found for the server, so add new job
$job = Invoke-Command $server -ScriptBlock {
param($data, $hostname)
$ProcessActive = Get-Process powershell -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
# This will block the thread on the server, so the JobState will not change till it's done or fails.
Invoke-Command -ComputerName $hostname -FilePath C:\ExampleScript.ps1 -ArgumentList $data
Write-Output "ScriptRan"
}
} -ArgumentList $data[0], $env:computername -AsJob
$job | Add-Member -MemberType NoteProperty -Name InData -Value $data[0]
$jobs.Add($job) | Out-Null
$data.Remove($data[0])
}
}
}
# Just a manual check of $jobs
Write-Output $jobs
# Wait a bit before checking again
Start-Sleep -Seconds 10
} while ($data.Count -gt 0)
Basically I create an array, and keep it constantly populated with one job for each server.
Data is removed from the list when a new job starts, and is added back if a job fails. This is to avoid servers running the script with the same data/params.
I lack a proper environment to test this properly at the moment, but will give it a whirl at work tomorrow and update my answer with any changes if needed.