PowerShell update variable while script is running - windows

How do i update a variable in PowerShell while the script is running state?
I have a situation where the script monitors the size of a disk continuously and compares it with a number in a text file on a shared drive (say Z:\quota\software-share-size.txt). If the number in text file is greater than disk size it monitors, then it sends out an email to expand the disk to new size as mentioned in text file. But once the script starts, its not pulling in the new number from file and i dont want to stop and start the script to load new content from the text file. Help please

Maybe this can help you :
while($true)
{
#Here i pull my disk (C:) infomations (i use localhost for my computer, but you can use an IP or a file with multiple IP)
$diskinfo = Get-WmiObject Win32_LogicalDisk -ComputerName localhost | where {$_.DeviceId -eq 'C:'}
#Here you pull only the freespace value with Gb format (default is byte)
$freespace = $diskinfo.freespace/1Gb
$freespace = [int]$freespace
#here you pull the "limit number" off your file, must be in Gb and only the number is written in the file.
$limit=Get-Content -Path "B:\Dev\filewithsizeingo.txt"
$limit = [int]$limit
if ($freespace -gt $limit) #The free diskspace is greater than the limit
{
Write-Host "Diskfreespace is Above Limit" -f Green
}
elseif ($freespace -lt $limit) #The free diskspace is inferior than the limit
{
Write-Host "Free diskspace below limit" -f Red
#break
#code mail sending
}
Start-Sleep 1
}
Because it's a loop, you can modify the filewithsizeingo.txt without stoping the script, and the script will refresh the free diskspace and limit value at each loop.
In the elseif statement, you can insert a break and code the sending of the email (which I do not know yet), dont forget the break or it will send a mail every second.
I hope it helps, or at least it gave you fresh's ideas (i'm a beginner with powershell, the code CAN be improved).

Related

Breaking out of a for loop and then deleting a temporary txt file

I am a beginner and know just enough to get my self in trouble and completely confused. I am creating a small utility to read data send from a serial device. It uses a user input com port number, and then keeps the com port open with a for loop. the loop will then read the data out put a txt file to desktop and then convert txt to csv. I would like to break out of this for loop after the data has been read and csv has been created, and then delete the txt file (it is only a temporary file). I have tried using a remove item inside the for loop and it deletes the txt file but the csv creates with no data. I have also tried using a start sleep for 10 seconds but this doesn't work either. the rest of the script works like I want it just creates 2 files on the desktop.
Write-Host "Select Serial Port"
$portName = Read-Host -Prompt 'Input Serial Device Name'
Write-Host "Using $portName..."
$port= new-Object System.IO.Ports.SerialPort $portName, 9600, None, 8, one
$port.Open()
Write-Output $port
for(;;)
{
if ($port.IsOpen)
{
$data = $port.ReadLine()
Write-Output $data >> C:\Users\desktop\SBS.txt
import-csv SBS.txt -delimiter "`," | export-csv SBS2003_Results.csv
Start-Sleep -Seconds 15
Remove-Item path C:\Users\desktop\SBS.txt
}
}

Adding values from multiple computers with custom headers in a single csv

I need to accomplish the scenario below and for that I have to create a couple of powershell scripts to accomplish it.
The environment: Windows servers and Windows clients
Scenario
1- Create a script to be run in a specific time every day (with Task Scheduler) on windows clients. This script will push the current computer's hostname and IP address to a csv file with a specific headers (let's call these "Hostnames" and "IP Address"). These header shouldn't be changed as these scripts run from multiple computers at that time and all computers' data should be appended to each header, not overwrite them as this operation continues.
2- From a server, (after 15 mins) as fetching this "computer" list (csv), there should be a ping check for each of them using their IP addresses. If pings are successful on these remote computers, it should say "This computer is up" next to the each computer name and its IP address. If pings are unsuccessful, it should say "This computer is down" next to the each computer name and its IP address. Above these "status" information, there should be another header (let's say "IsAlive"). This header should be added to the csv as well.
So with this setup, I could be able to learn which computers are UP at a specific time and after I trigger some actions them, I could be able to learn if they're still up or down.
To be honest, I couldn't take a long way for it. I started to write a script about the first step but I couldn't combine the headers with values adding under them.
$header="Hostname"
$outputfile="\\10.10.10.40\reports\upcomputers.csv"
Add-Content $outputfile -Value $header
$hostname >> $outputfile
If I use this script (even if with one header), it's adding "NULL" after each alphabet of hostname and it doesn't append the other hostname under the "Hostname" header.
Additionally, I have no idea where to start adding the third header (IsAlive) and add each Test-NetConnection query's output as checking their IP addresses. I request you to show me a way to start with this section as well.
Finally, my output should be like that;
For the first step;
For the second step;
Thank you for your help and information
Stated on the main body of the request
Stage 2:
The easy way of doing this using PSCustomobject. Please find the sample code below:
$outputcsv = "C:\PowerShell\pingstatus.csv"
$hostlist = import-csv "C:\PowerShell\hostlist.csv"
$result = foreach($line in $hostlist){
$pingtest = Test-Connection -ComputerName $line.Hostname -Quiet -Count 1 -ErrorAction SilentlyContinue
if($pingtest) {
$OutputMessage = "This computer is up"
}
else {
$OutputMessage = "This computer is down"
}
[pscustomobject][ordered]#{
HostName = $line.Hostname
IPAddress = $line.IPaddress
IsAlive = $OutputMessage
}
}
$result | Export-csv -Path $outputcsv -NoTypeInformation
The Hostname and IPAddress input will be taken as input.
Note: Your input csv file should contain Hostname IPaddress as header.
Stage1:
why not?
$outputfile="\\10.10.10.40\reports\upcomputers.csv"
$serverDetails = [PSCustomObject]#{
Hostname = "$env:COMPUTERNAME"
IPAddress = (Get-WmiObject -Class Win32_NetworkAdapterConfiguration | where {$_.DHCPEnabled -ne $null -and $_.DefaultIPGateway -ne $null}).IPAddress | Select-Object -First 1
}
$serverDetails | Export-csv $outputfile -Append -NoTypeInformation
There are multiple ways to get IP address a computer, I used Get-WMIObject. You may use other simple ways like Test-Connection or Get-NetIPAddress.
To learn more: Please see
[PsCustomObject]: https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/everything-about-pscustomobject?view=powershell-7.3

How can I use "IF" statement to determine the amount of free disk space to add to a VMDK?

The script I am using is below. We are upgrading our 1500 VDI's from Windows 10 1809 to 1909 and we want to automate the process as much as possible since this will be a process that we will have to do regularly with each new version Windows puts out. We are using SCCM (System Center Configuration Manager). The script works perfect for extending disk space for multiple machines. The .csv file it is importing only contains a list of virtual machines the script is to expand disk space on. I want to use an "if" statement to determine if the amount of free space is below 30GB and, if so, to add the needed disk space to bring it up to 30GB free space. Example: "if" free disk space is -lt 30GB, Set-HardDisk -CapacityGB (the difference to make 30GB free space available), "if" free space is -eq to 30GB, do nothing. I have researched Google and this site for anything remotely similar and wasn't able to find what I need. All help accomplishing this is appreciated.
##Task Change Disk Size
##Variable clear
$csvobjects = #()
$cskobject = #()
$network = #()
$isalive = #()
##Import VM name(s)
$csvobjects = Import-CSV -path "C:\Temp\ExpandHDDiskList.csv"
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false
connect-viserver -server anyserver.com -User anyuser#anyserver.com
foreach ($csvobject in $csvobjects){
##Variable clear
$network = #()
$isalive = #()
##Pre-change data gathering
$beforechange = (GET-VM -Name $csvobject.vmname | FT -auto CapacityGB|out-string)
##Stop VM
GET-VM -Name $csvobject.vmname | Get-HardDisk -Name 'Hard disk 1' | Set-HardDisk -CapacityGB 75 -Confirm:$false
start-sleep -s 60
# Variable specifying the drive you want to extend
$drive_letter = "C"
# Script to get the partition sizes and then resize the volume
$size = (Get-PartitionSupportedSize -DriveLetter $drive_letter)
Resize-Partition -DriveLetter $drive_letter -Size $size.SizeMax
}
You can use Get-VMGuest to get the free space within the guest VM.
Set the target amount of free space in a variable. 30GB (GB = 1024^3)
$gb = 1024 * 1024 * 1024
$space = 30 * $gb
Get the free space from the VM using Get-VMGuest
$vm = Get-VMGuest $csvobject.vmname
Now, assuming a single hard disk in the machine, determine if the free space is less than 30GB. If it is, increase the hard disk size so that there is 30GB of free space. The calculation takes the capacity of the disk and adds on the required free space (30GB) and then subtracts the current free space. This is all done in bytes so convert it into GB and round it to a whole number to avoid weird disk sizes:
if ($vm.Disks[0].FreeSpace -lt $space) {
Get-HardDisk $vm.Vm | Select -first 1 | Set-HardDisk -CapacityGB ([math]::round(($vm.Disks[0].Capacity + $space - $vm.Disks[0].FreeSpace) / $gb))
}
Take care when running, particularly if you have machines with multiple hard disks.
Assuming Windows VMs, you don't need to stop the VM. You can resize the filesystem remotely using Invoke-Command or Invoke-VMScript.
With PowerShell 5 or newer you can replace
$gb = 1024 * 1024 * 1024
with
1GB
And replace
$space = 30 * $gb
with
30GB
So, the entire script would look like this
$vm = Get-VMGuest $csvobject.vmname
if ($vm.Disks[0].FreeSpace -lt 30GB) {
Get-HardDisk $vm.Vm | Select -first 1 | Set-HardDisk -CapacityGB ([math]::round(($vm.Disks[0].Capacity + 30GB - $vm.Disks[0].FreeSpace) / 1GB))}

Is there a way to make a link/symlink/shortcut to the latest file in Windows? Keep tailing the latest log file

I searched high and low, found how to do it in *nix, but nothing about Windows.
First place I've seen this was Tomcat's catalina.out, and now I was wondering how to do a similar thing on Windows: considering a folder where log files are created, how to make a file that reads the/points to latest log created?
I'm thinking a Powershell solution might be possible, but I honestly can't think or find any way to do it.
(edit) You guys downvoting could at least leave a comment to tell me what did I do wrong or how can I improve this question?
(edit) The idea here is to have some way to create a symlink that points to the latest log file in a folder, so a program can monitor always the same file, no matter if the latest file changes its name - like tail -f catalina.out always reads the latest catalina log file.
The only way out I can see, and that I wanted to avoid, would be to write a powershell script that would monitor a folder (https://superuser.com/questions/226828/how-to-monitor-a-folder-and-trigger-a-command-line-action-when-a-file-is-created) and would dynamically create a symlink to the latest file found (https://stackoverflow.com/a/11211005/1985023), then set it as a service, so it would be always running on the background.
Instead of looking for a dynamically self-updating symlink (which would be quite cumbersome to implement - see the helpful hints from BACON in the comments in the question), you can make this work as a self-contained function/script with the help of PowerShell background jobs:
Run in a loop that periodically gets the latest log-file lines from a background job that does the equivalent of Unix tail -f via Get-Content -Wait -Tail 10.
If a new log file is found, terminate the previous background job and start one for the new log file.
Note that this relies on periodic polling of the background job that tails the log. The code below allows you to adjust the polling interval.
Note that Get-Content -Wait itself polls the target file for changes every second.
Here's the code; run $VerbosePreference = 'Continue' to see what's going on inside the loop:
$dir = 'C:\path\to\logs' # the log-file directory
$logFilePattern = '*.log' # wildcard pattern matching log files
$sleepIntervalMs = 1000 # how many msec. to sleep between getting new lines from the background job
Write-Host -ForegroundColor Green "Tailing the latest log(s) in $dir...`nPress any key to quit."
$currJob = $currLog = $null
while ($true) {
# If the user pressed a key, clean up and exit.
if ([console]::KeyAvailable) {
$null = [console]::ReadKey($True) # consume the key - it will still have printed, though
if ($currJob) { Remove-Job -Job $currJob -Force }
break
}
# Get the latest lines from the current log from the background job.
if ($currJob) {
Write-Verbose "Checking for new lines in $newLog..."
Receive-Job -Job $currJob
Start-Sleep -Milliseconds $sleepIntervalMs # sleep a little
}
# Determine the first / newest log.
$newLog = Get-ChildItem -LiteralPath $dir -Filter $logFilePattern | Sort-Object CreationTimeUtc -Descending | Select-Object -First 1
if ($newLog.FullName -ne $currLog.FullName) { # new log file found.
Write-Verbose "(New) log file found: $newLog"
if ($currJob) {
Write-Verbose "Terminating background job for previous log ($currLog)."
Remove-Job -Job $currJob -Force
# When a *new* log was just started, we show *all* lines (and keep listening for more).
$tailArg = #{}
} else {
# When we first start monitoring, we start with the *last 10* lines
# of the current log (and keep listening for more).
$tailArg = #{ Tail = 10 } # On first
}
$currLog = $newLog
Write-Verbose "Starting background job for $currLog..."
# Start the background job for the new log.
$currJob = Start-Job { Get-Content -Wait #using:tailArg -LiteralPath $using:newLog.FullName }
}
}
Write-Host -ForegroundColor Green "Terminated."

Extract hostnames from Perfmon blg with Powershell

I'm writing a script which will automate the extraction of data from .blg Perfmon logs.
I've worked out the primary Import-Counter commands I will need to use to get the data out, but am trying to parametrise this so that I can do it for each machine in the log file (without having to open the log up in Perfmon, which can take 15 minutes or sometimes more, and is the reason I'm writing this script), and find out what each hostname is.
The script I have does the job, but it still takes a minute to return the data I want, and I wondered if there was a simpler way to do this, as I'm not too familiar with Powershell?
Here's what I have:
$counters = Import-Counter -Path $log_path$logfile -ListSet * | Select-Object paths -ExpandProperty paths
$svrs = #()
# for each line in the list of counters, extract the name of the server and add it to the array
foreach ($line in $counters) {
$svrs += $line.split("\")[2]
}
# remove duplicates and sort the list of servers
$sorted_svrs = $svrs | sort -unique
foreach ($svr in $sorted_svrs) {
Write-Host $svr
}
I'm just printing the names for the moment, but they'll go into an array in the proper script, and then I'll run my Import-Counter block with each of these hosts parametrised in.
Just wondered if there was a better way of doing this?
$sorted_svrs=Import-Counter "$log_path$logfile" -Counter "\\*\physicaldisk(_total)\% disk time" | %{$_.countersamples.path.split("\")[2]} | sort -Unique

Resources