I have some PowerShell code that gets me the uptime of a list of servers and outputs the Days, Hours and Minutes that the server is up.
I'm trying to add a statement that will take anything less than 10 hours of uptime and output to the text file - Rebooted
If the server is up for 30 days or more, the output to text would be - Reboot Needed
I'm just not sure how to make that happen. Here is what I have for uptime...
$names = Get-Content "C:\Users\david.sechler\Documents\PowerShell\Get Uptime\servers.txt"
#(
foreach ($name in $names)
{
if ( Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue )
{
$wmi = gwmi -class Win32_OperatingSystem -computer $name
$LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
Write-output "$name Uptime is $($uptime.days) Days $($uptime.hours) Hours $($uptime.minutes) Minutes $($uptime.seconds) Seconds"
}
else {
Write-output "$name is not pinging"
}
}
) | Out-file -FilePath "C:\Users\david.sechler\Documents\PowerShell\Get Uptime\results.txt"
Try this
$toreboot = #()
$rebooted = #()
[timespan]$recentboot = new-timespan $(get-date).AddHours(-10) $(get-date)
[timespan]$needboot = new-timespan $(get-date) $(get-date).Adddays(30)
$recentboot
$needboot
$names = Get-Content "d:\scrap\servers.txt"
foreach ($name in $names)
{
if ( Test-Connection -ComputerName $name -Count 1 -ErrorAction SilentlyContinue )
{
$wmi = gwmi -class Win32_OperatingSystem -computer $name
$LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
Write-output "$name Uptime is $($uptime.days) Days $($uptime.hours) Hours $($uptime.minutes) Minutes $($uptime.seconds) Seconds"
if ($uptime -lt $recentboot)
{$rebooted += $names}
if($uptime -gt $needboot)
{$toreboot += $name}
}
else {
Write-output "$name is not pinging"
}
if ($toreboot -ne $null)
{set-content -Path "d:\scrap\serverstoboot.txt" -Value $toreboot}
if ($rebooted -ne $null)
{set-content -Path "d:\scrap\recentlybooted.txt" -value $rebooted}
}
Related
I'm a beginner with Powershell, also forgive my English which isn't the best.
I have a directory with several subdirectories like this
Directory
My goal is to target directory that are updated while the script is running. So I made this script
$path = "C:\Users\s611284\Desktop\archive"
#Check that the directories have the correct name and calculate their size
Get-ChildItem -Force $path -ErrorAction SilentlyContinue -Directory | foreach {
$Size = (Get-ChildItem $_.fullname -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum/ 1Kb
$FolderName = $_.BaseName -match '1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})' -or $_.BaseName -match '1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})'
$Folder = $_.BaseName
if ($FolderName -eq "true") {
write-host(" name $Folder is correct, $Size Kb")
}
else {
write-host( "name $Folder is incorrect")
}
}
#Break
Start-Sleep -Seconds 30
write-host( "end of break")
#directory size calculation after the break
Get-ChildItem -Force $path -ErrorAction SilentlyContinue -Directory | foreach {
$Size1 = (Get-ChildItem $_.fullname -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum/ 1Kb
$FolderName1 = $_.BaseName -match '1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})' -or $_.BaseName -match '1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})'
$Folder1 = $_.BaseName
if ($FolderName1 -eq "true") {
write-host("name $Folder1 is correct, $Size1 Kb")
}
else {
write-host( "name $Folder1 is incorrect")
}
}
All of this is working great
So now I want to compare the size of the subdirectories before and after the break, to know which have been updated
I tried
if ( $FolderSize -eq $FolderSize1 )
{
Write-Output $True
}
Else
{
Write-Output $False
}
at the end of my second block but it isn't working..
I also tried Compare-object but I don't think this command will help in my case
I hope you guys will understand my post and help me
Thanks !
If I understood correctly, you're looking to filter those folders where it's Size has changed after 30 seconds, if that's the case, you could use a function so that you don't need to repeat your code. You can make your function return a hash table where the Keys are the folder's absolute path and the Values are their calculated size, once you have both results (before 30 seconds and after 30 seconds) you can run a comparison against both hash tables outputting a new object with the folder's Absolute Path, Size Before and Size After only for those folders where their calculated size has changed.
function GetFolderSize {
[cmdletbinding()]
param($path)
$map = #{}
Get-ChildItem $path -Directory -Force | ForEach-Object {
$Size = (Get-ChildItem $_.Fullname -Recurse | Measure-Object -Property Length -Sum).Sum / 1Kb
$FolderName = $_.BaseName -match '1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})' -or $_.BaseName -match '1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})'
if ($FolderName) {
$map[$_.FullName] = $size
}
}
if($map) { $map }
}
$path = "C:\Users\s611284\Desktop\archive"
$before = GetFolderSize $path -ErrorAction SilentlyContinue
Start-Sleep -Seconds 30
$after = GetFolderSize $path -ErrorAction SilentlyContinue
foreach($key in $after.PSBase.Keys) {
if($before[$key] -ne $after[$key]) {
# this is a folder with a different size than before
[PSCustomObject]#{
FullName = $key
SizeBefore = $before[$key]
SizeAfter = $after[$key]
}
}
}
Not to take away from Santiago's helpful answer, but to provide an alternate solution, here's my take:
$path = "C:\Users\s611284\Desktop\archive"
$count = 0
$hashMap = #{}
While ($count -lt 2) {
Get-ChildItem -Path $path -Directory |
ForEach-Object -Begin {
$count++
$toMatch = "1B(\d{6})_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{6})|1B(\d{6})_SML 10_LEAP 1A version aout2021_(\d{4})-(\d{2})-(\d{2})T(\d{2})h(\d{2})m(\d{2})s_S(\d{5})"
} -Process {
$folderMatch = $_.BaseName -match $toMatch
if ($folderMatch) {
$size = (Get-ChildItem -Path $_.FullName -Recurse -EA 0 | Measure-Object -Property "Length" -Sum).Sum / 1kb
if (-not$hashMap.ContainsKey($_.BaseName)) {
$hashMap.Add($_.BaseName,$size)
}
if ($count -ge 2) {
if ($hashMap[$_.BaseName] -ne $size) {
$_.BaseName + " " + "is a different size"
}
}
}
} -End {
if ($count -ne 2) {
Start-Sleep -Seconds 30
}
}
}
Personally, I hate the re-use of code and feel like something can always be done about it if you find yourself repeating code (copy/paste).
My question to you is:
Aren't those folder names pretty unique?
Could you not substitute it for a Wild Card Expression?
i.e.: $_.BaseName -like '*1A version aout2021*'
All in the name of "cleanliness" code. lol
I found some code for searching for strings in a Word Document. I altered it to suit my needs (I need to search from a very long list of strings). Unfortunately, I am getting a weird error.
While the script is running, it opens the word document, searches the word document and here is where it gets weird, instead of closing the document and opening the next, it presents me with a 'save as' dialog box and the script hangs until I cancel out of it. When I cancel out of it, my script continues.
Here is the script I'm using, would anyone see where I'm going south?
$results = #{}
Write-Host "Loading getStringMatch into memory" -ForegroundColor DarkMagenta
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
Write-Host "Searching In ... $($File.FullName) " -ForegroundColor DarkYellow
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
If($range.Text -match ".{$($charactersAround)}$($findtext).{$($charactersAround)}"){
$properties = #{
File = $file.FullName
Match = $findtext
TextAround = $Matches[0]
}
$results += #(New-Object -TypeName PsCustomObject -Property $properties)
}
$document.close()
Write-Host "Closing Document ... $($File.FullName) " -ForegroundColor Red
}
#If($results){
# $results | Export-Csv $output -NoTypeInformation
#}
$application.quit()
}
$searchWords=Get-Content "C:\Temp\USDA_Search_For.txt"
Foreach ($sw in $searchWords)
{
Write-Host "Setting Variables ..." -ForegroundColor DarkMagenta
Set-StrictMode -Version latest
$path = "C:\Temp"
$files = Get-Childitem $path -Include *.docx,*.doc -Recurse | Where-Object { !($_.psiscontainer) }
$output = "C:\Temp\Found.csv"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "First"
$charactersAround = 30
#$results = #{}
$findtext = $sw
Write-Host "Searching For ... $findtext" -ForegroundColor Green
getStringMatch
#clean up stuff
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($application) | Out-Null
Remove-Variable -Name application
[gc]::collect()
[gc]::WaitForPendingFinalizers()
}
If($results){
$results | Export-Csv $output -NoTypeInformation
}
import-csv $output
I´m currently working on a script that should based on user´s choice replace two lines in a file after a matching string.
The file I want to edit looks like this:
[default]
string_a=sadasdasdas
string_b=dasdasdasdas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
I want always to override string_a & string_b after [default].
Note that [default] could also be at the very bottom of the file, therefore I cannot just count lines an do it that static.
The user can pick between (in this case) profile 1 & profile 2. After he picked e.g profile 2, string_a & string_b of profile2 should be replaced with string_a & string_b of default.
My current code like like this:
$filePath = './credentials'
$fileContent = Get-Content $filePath
$profiles = [regex]::Matches($fileContent, '\[(.*?)\]') |ForEach-Object { $_.Groups[1].Value }
Write-Host "Following profiles found: "
for ($i=0; $i -lt $profiles.length; $i++){
Write-Host $i"." $profiles[$i]
}
$userInput = Read-Host "Which profile set to default? "
Write-Host $profiles[$userInput]
$fileContent | Select-String $profiles[$userInput] -Context 1,2 | ForEach-Object {
$stringA = $_.Context.PostContext[0]
$stringB = $_.Context.PostContext[1]
#At this point I have access to the both string´s I want to replace the string´s of the default profile
# I could do this, but then I still have the old lines in the file...
# So the following code is not an option.
$NewContent = Get-Content -Path $filePath |
ForEach-Object {
# Output the existing line to pipeline in any case
$_
# If line matches regex
if($_ -match ('^' + [regex]::Escape('[default]')))
{
# Add output additional line
$stringA
$stringB
}
}
# Write content of $NewContent varibale back to file
$NewContent | Out-File -FilePath $filePath -Encoding Default -Force
}
Example output file, in case the user picked profile1 as the new default
[default]
string_a=xxxxxx
string_b=xsaassaasas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
Hope this is not obvious, but as it is my first real powershell script I was not able to find a solution for my problem yet.
Any help would be great!
Thanks
Example:
# This is sample data
$lines = #(#'
[default]
string_a=sadasdasdas
string_b=dasdasdasdas
[profile1]
string_a=xxxxxx
string_b=xsaassaasas
[profile2]
string_a=yyyyyyy
string_b=yaayayayaya
'# -split "`r`n")
# In real world use:
# $encoding = [System.Text.Encoding]::ASCII
# $lines = [System.IO.File]::ReadAllLines($path, $encoding)
#Read file
$lines = $lines | ForEach-Object { $_.Trim()} # Trim spaces
$sections = #{}
$currentSection = $null
$hasErrors = $false
$lines | ForEach-Object {
if ([String]::IsNullOrWhiteSpace($_)) {
#ignore
} elseif ($_.StartsWith('[') -and $_.EndsWith(']') ) {
$currentSection = $_.Substring($_.IndexOf('[') + 1, $_.LastIndexOf(']') - 1)
$sections[$currentSection] = #{}
} elseif ($sections.ContainsKey($currentSection)) {
$PVPair = [String[]]$_.Split('=',2)
if ($PVPair.Count -eq 2) {
$sections[$currentSection][$PVPair[0]] = $PVPair[1]
} else {
Write-Warning -Message "Wrong line format [$($_)]"
$hasErrors = $true
}
} else {
Write-Warning -Message "Unexpected behaviour on section $currentSection, line $($_)"
$hasErrors = $true
}
}
if ($hasErrors) {
Write-Error -Message 'Errors occured'
return
}
# Choice
$choice = $null
$choiceVariants = #($sections.Keys | Where-Object { $_ -ne 'default' })
while ($choiceVariants -notcontains $choice) {
Write-Host "Choose between $($choiceVariants -join ',')"
$choice = $choiceVariants | Out-GridView -Title 'Choose variant' -OutputMode Single
#Alternative: $choice = Read-Host -Prompt "Your choice"
}
Write-Host -ForegroundColor Yellow "You choose $($choice)"
# Change
$sections[$choice]['string_a'] = $sections['default']['string_a']
$sections[$choice]['string_b'] = 'newXSAA'
# Output
$outputLines = $sections.Keys | ForEach-Object {
$sectionName = $_
Write-Output "[$sectionName]"
$sections[$sectionName].Keys | ForEach-Object {
Write-Output "$_=$($sections[$sectionName][$_])"
}
}
# This is sample output
$outputLines | % { Write-Host $_ -f Magenta }
# In Real world:
# [System.IO.File]::WriteAllLines($path, $outputLines, $encoding)
I have a PowerShell script named script1.ps1 that work perfectly. Here is the script:
Write-Host Script to display members of the local -ForegroundColor Green
Write-Host Administators group of a remote server. -ForegroundColor Green
Write-Host "`n"
$strComputer = Read-Host "Please enter the computer name"
$computer = [ADSI]("WinNT://" + $strComputer + ",computer")
$group = $computer.PSBase.Children.Find("administrators")
Write-Host ""
Write-Host "Computer Name : "$computer.Name
Write-Host "_____________________________________"
Write-Host ""
Write-Host "Group Name : "$Group.Name
Write-Host "_____________________________________"
$domain = $group.Path.Split("/")[2]
$string1 = "WinNT://" + $domain + "/" + $strComputer + "/"
$string2 = $strComputer + "/"
$string3 = "WinNT://"
$members = ($group.PSBase.Invoke("Members") | Foreach-Object {$_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null)}) -replace ($string1,$string2) -replace ($string3,"")
Write-Host ""
Write-Host "Members : "
Write-Host ""
$members
But as you can see, I'm obliged to write each time the name of computer I want.
I ask myself if there is a function or other things which take automatically the computers names from a text or CSV file?
Like this at the beginning of the script, it automatically take all the computers names & give me the members of the local 'Administators' group which then gonna be exported in one text file or CSV file too?
Updated script according to the answer given.
Write-Host Script to display members of the local -ForegroundColor Green
Write-Host Administators group of a remote server. -ForegroundColor Green
Write-Host "`n"
Get-Content 'C:\Users\herbautr\Desktop\List1.txt' | ForEach-Object {
Write-Host "-$_-"
$computer = [ADSI]("WinNT://" + $_ + ",computer")
$group = $computer.PSBase.Children.Find("administrators")
Write-Host ""
Write-Host "Computer Name : "$computer.Name
Write-Host "_____________________________________"
Write-Host ""
Write-Host "Group Name : "$Group.Name
Write-Host "_____________________________________"
$domain = $group.Path.Split("/")[2]
$string1 = "WinNT://" + $domain + "/" + $_ + "/"
$string2 = $_ + "/"
$string3 = "WinNT://"
$members = ($group.PSBase.Invoke("Members") | Foreach-Object {$_.GetType().InvokeMember("Adspath", 'GetProperty', $null, $_, $null)}) -replace ($string1,$string2) -replace ($string3,"")
Write-Host ""
Write-Host "Members : "
Write-Host ""
$members
} | Set-Content 'C:\Users\herbautr\Desktop\administrators.txt'
I have add 1 computer name to the List1.txt:
01SPEAI-TEST1
01SPEAI-TEST2
02SPHPV-TEST1
01SLCPTAAP-PROD
And it works (not) perfectly (Unreadable layout)
01SPEAI-PROD1/Administrator
VNF-PRINCIPAL/Admins du domaine
VNF-PRINCIPAL/svceri
01SPEAI-PROD2/Administrator
VNF-PRINCIPAL/Admins du domaine
VNF-PRINCIPAL/svceri
02SPHPV-PROD1/Administrator
VNF-PRINCIPAL/Admins du domaine
01SLCPTAAP-PROD/Administrator
VNF-PRINCIPAL/Admins du domaine
01SLCPTAAP-PROD/maint
VNF-PRINCIPAL/svcoraas
VNF-PRINCIPAL/svcvisionit
VNF-PRINCIPAL/GopOAS
VNF-PRINCIPAL/svcdigora
Note (15:18pm): I have tried with 5 names, it continue to work.
Why when adding just 1 name it "works"?
You're looking for Get-Content and Set-Content.
Get-Content 'C:\path\to\computers.txt' | ForEach-Object {
$computer = [ADSI]("WinNT://" + $_ + ",computer")
...
} | Set-Content 'C:\path\to\administrators.txt'
Note that you need to replace all occurrences of $strComputer inside the ForEach-Object loop with the current object automatic variable ($_).
If you want to use CSVs for input and output use the Import-Csv and Export-Csv cmdlets.
Import-Csv 'C:\path\to\computers.csv' | ForEach-Object {
$computer = [ADSI]("WinNT://" + $_.ComputerName + ",computer")
...
$members | ForEach-Object {
New-Object -Type PSObject -Property #{
Member = $_
}
}
} | Export-Csv 'C:\path\to\administrators.csv' -NoType
Note that CSVs have some advantages when you need to handle items with multiple properties, but they require somewhat more elaborate handling than simple strings (as you can see in my example above).
Requirement: Need way to handle Special characters like % and &. Need to tweak code below so that Special characters which come via $Control file are treated as it is.
For example: I have one of entry in $control file as 25% Dextrose(25ml). I need a way so that $ie.Navigate should simply navigate to https://www.xxxy.com/search/all?name=25% Dextrose(25ml). Currently it gets routed to https://www.xxxy.com/search/all?name=25%% Dextrose(25ml) (note a extra % in URL) and thus does not find that web-page.
**Few examples of special characters that need to be tackled:**
'/' - 32care Mouth/Throat
'%' - 3d1% Gel(30g)
'&' - Accustix Glucose & Protein
'/' - Ace Revelol(25/(2.5mg)
function getStringMatch
{
# Loop through all 2 digit combinations in the $path directory
foreach ($control In $controls)
{
$ie = New-Object -COMObject InternetExplorer.Application
$ie.visible = $true
$site = $ie.Navigate("https://www.xxxy.com/search/all?name=$control")
$ie.ReadyState
while ($ie.Busy -and $ie.ReadyState -ne 4){ sleep -Milliseconds 100 }
$link = $null
$link = $ie.Document.get_links() | where-object {$_.innerText -eq "$control"}
$link.click()
while ($ie.Busy -and $ie.ReadyState -ne 4){ sleep -Milliseconds 100 }
$ie2 = (New-Object -COM 'Shell.Application').Windows() | ? {
$_.Name -eq 'Windows Internet Explorer' -and $_.LocationName -match "^$control"
}
# NEED outerHTML of new page. CURRENTLY it is working for some.
$ie.Document.body.outerHTML > d:\med$control.txt
}
}
$controls = "Sporanox"
getStringMatch
You want to URL encode the URI. Add this at the very start:
Add-Type -AssemblyName 'System.Web'
And then encode the URL like this:
$controlUri = [System.Web.HttpUtility]::UrlEncode($control)
$site = $ie.Navigate("https://www.xxxy.com/search/all?name=$controlUri")
As Biffen pointed out, Web servers will treat special characters as codes. So in your case, $control needs to be modified so that the Web server understands where you want to go.
One way to fix it is the look for specific characters in the original product name you are looking for, and replace them with something that the server will understand:
Here is the entire code:
function getStringMatch
{
# Loop through all 2 digit combinations in the $path directory
foreach ($control In $controls)
{
$ie = New-Object -COMObject InternetExplorer.Application
$ie.visible = $true
$s = $control -replace '%','%25'
$s = $s -replace ' ','+'
$s = $s -replace '&','%26'
$s = $s -replace '/','%2F'
$site = $ie.Navigate("https://www.xxxy.com/search/all?name=$s")
while ($ie.Busy -and $ie.ReadyState -ne 4){ sleep -Milliseconds 100 }
$link = $null
$link = $ie.Document.get_links() | where-object {if ($_.innerText){$_.innerText.contains($control)}}
$link.click()
while ($ie.Busy){ sleep -Milliseconds 100 }
$ie.Document.body.outerHTML > d:\TEMP\med$control.txt
}
}
$controls = "Accustix Glucose & Protein"
getStringMatch
I tried with the following strings:
"3d1% Gel(30g)"
"Ace Revelol(25/2mg)"
"Accustix Glucose & Protein"
"32care Mouth/Throat"