if/elseif not working right - powershell

I have a script that checks how much memory the top w3wp process is using. Then depending on how close to 1 gig it is using, it will give out a message.
param($warn, $crit)
$LastExitCode = 3
$Output1 = ""
$output = ""
$myW3WP = Get-Process w3wp | Sort -Descending WS | select -first 1 |
Measure-Object WS -Sum
$myW3WP = ("{0:N2} " -f ($myW3WP.sum / 1mb))
$myW3WP.trim()
if ($myW3WP -gt $crit) {
$LastExitCode = 2
$output1 = "CRITICAL: Memory for W3WP has passed 1 gig $myW3WP"
$output = $output1
} elseif ($myW3WP -gt $warn) {
$LastExitCode = 1
$output1 = "WARN: Memory for W3WP is getting close to 1 gig $myW3WP"
$output = $output1
} else {
$LastExitCode = 0
}
$output = $output1
Write-Host $output $LastExitCode "$myW3WP >= " $crit ($myW3WP -gt $crit) "$myW3WP >= " $warn ($myW3WP -gt $warn)
$myW3WP = ""
In values for $crit is 1000 and the value for $warn is 900.
(Note: the Write-Host line is like that for troubleshooting)
Here is the output I am getting:
CRITICAL: Memory for W3WP has passed 1 gig 161.03 2 161.03 >= 1000 True 161.03 >= 900 False
The 161.03 is the megs of RAM being used.
Any ideas on why it is saying 161.03 is greater than 1000?

Don't compare apples and oranges!
You'll need a numerical value if you want to compare it to other numerical values
When you call:
$myW3WP = ("{0:N2} " -f ($myW3WP.sum / 1mb))
$myW3WP.trim()
you create a string.
Replace those two lines with just:
$myW3WP = $myW3WP.Sum / 1MB
And it'll work

$myW3WP is a string, not a number, so you're doing a textual comparison, rather than a numeric one. The line:
$myW3WP = ("{0:N2} " -f ($myW3WP.sum / 1mb))
Is what forces the variable to be a string. Just leave it as a number and don't worry about the decimal places:
$myW3WP = $myW3WP.sum / 1mb

Related

Operator -gt and -le / Problem with negative numbers

i have the following code snippet where i change the values in a column (named G) of a csv to Y if the integer value is greater then 1 and to N if it is equal to 1 and smaller.
ForEach-Object {if ($_.G -gt '1') {$_.G = 'Y'} if ($_.G -le '1') {$_.G = 'N'} $_}
It works fine with the exception of negative numbers. I always get a Y. I don't have any idea. Example data:
F,G
item1, -58
item2, -77
item3, 562
Does anyone have an idea?
Regards, Hubertus
In order to evaluate the $_.G property as a number you need to specify the type as [int]. Example using your code:
$testObject = New-Object PSObject -Property #{
G='-1'
}
$testObject| %{
if ([int]$_.G -gt 1)
{
$out = "{0} is greater than 1" -f $_.G
Write-Host $out -ForegroundColor Green
[string]$_.G = "Y"
}
elseif ([int]$_.G -le 1)
{
$out = "{0} is Less than 1" -f $_.G
Write-Host $out -ForegroundColor Green
[string]$_.G = "N"
}
}
Note: In order to assign $_.G as a string you have to change the type to [string]. In my opinion, I would use another property to indicate "Y/N" instead of flipping the type back and forth on the property.
The left side of -le or -gt controls the type for both sides, int32 (integer) in this case. You probably want an else in there, to not look at the G again after changing it.
'G
-1
1
2' |
convertfrom-csv |
ForEach-Object {
if (1 -le $_.G)
{$_.G = 'Y'}
else
{$_.G = 'N'}
$_
}
G
-
N
Y
Y

Powershell video length calculation

I have a calculation issue I cannot solve, any help appreciated! I receive video length of files in a more complex loop context using the following code:
$movs ="..\..\MOV"
$dura = New-Object -ComObject Shell.Application
$dura = Get-ChildItem -Path $movs -Recurse -Force | ForEach {
$Folder = $Shell.Namespace($_.DirectoryName)
$File = $Folder.ParseName($_.Name)
$Duration = $Folder.GetDetailsOf($File, 27)
[PSCustomObject]#{
vid-file= $_.Name -replace ".mov",""
duration = $Duration
}
}
Later on I match some IDs to $dura so that the result looks like this:
ID vid-file duration
1 move 00:01:08
1 run 00:01:12
1 fly 00:01:30
1 swim 00:01:08
1 sleep 00:02:20
2 move 00:01:08
2 swim 00:01:08
2 sleep 00:02:20
3 move 00:01:08
3 run 00:01:12
3 fly 00:01:30
3 swim 00:01:08
3 sleep 00:02:20
3 think 00:03:20
Now I need to calculate the starting points for each concatenated video case, i.e. I have to sum up the duration of the video for each part until the current position for every ID context and create a new column with it (every new ID starts at 00:00:00). The result would look like this:
ID vid-file duration videopart-start-at
1 move 00:01:08 00:00:00
1 run 00:01:12 00:01:08
1 fly 00:01:30 00:02:20
1 swim 00:01:08 00:03:50
1 sleep 00:02:20 00:04:58
2 move 00:01:08 00:00:00
2 swim 00:01:08 00:01:08
2 sleep 00:02:20 00:02:16
3 move 00:01:08 00:00:00
3 run 00:01:12 00:01:08
3 fly 00:01:30 00:02:20
3 swim 00:01:08 00:03:50
3 sleep 00:02:20 00:04:58
3 think 00:03:20 00:07:18
I think there could be some calculated object in the PSCustomObject but I can't figure it out..
[PSCustomObject]#{
vid-file= $_.Name -replace ".mov",""
duration = $Duration
videopart-start-at= $Duration | Measure-Object -Sum $Duration
}
Thanks, Daniel
I would think that there's an easier way of handling this - but I converted the time into seconds - then worked on the [TimeSpan] datatype.
$movs = 'c:\temp\sample' | Get-ChildItem -Recurse -Force -ErrorAction Stop
$dura = New-Object -ComObject Shell.Application
$result = Foreach ($mov in $movs) {
$Folder = $dura.Namespace($mov.DirectoryName)
$File = $Folder.ParseName($mov.Name)
$Duration = $Folder.GetDetailsOf($File, 27)
[PSCustomObject]#{
vidfile = $mov.Name -replace ".mov", ""
# Convert the string into an actual time data type
duration = $Duration
durationinseconds = ([TimeSpan]::Parse($Duration)).TotalSeconds
}
}
$i = 0
Foreach ($object in $result) {
# Skipping first and stopping on last (foreach will run out of objects to process)
if ($i -eq 0 -or $i -gt ($result.count)) {
# Adding one to counter
$i++
continue
}
$object.durationinseconds = $Object.durationinseconds + $result.durationinseconds[$i - 1]
$object.duration = [timespan]::fromseconds($object.durationinseconds)
("{0:hh\:mm\:ss}" -f $object.duration)
$i++
}
Thanks to Sebastian I found the following solution (I added the "startat" column in the pscustomobject, identical to durationinseconds):
$i = 0
Foreach ($object in $result) {
# skip lines gt result count
if ($i -gt ($result.count)) {
$i++
continue
}
# set start to 0 for first line
if ($i -eq 0) {
$object.startat = 0
}
# calculate start time for all following lines
if ($i -gt 0) {
$object.startat = $result.durationinseconds[$i - 1] + $result.startat[$i - 1]
}
# transform seconds to time value in duration var
$object.duration = [timespan]::fromseconds($object.startat)
# counter +1
$i++
}
$result
To calculate date/time differences, try something like this...
$current = Get-Date
$end = (Get-Date).AddHours(1)
$diff = New-TimeSpan -Start $current -End $end
"The time difference is: $diff"
# Results
<#
The time difference is: 01:00:00.0019997
#>
... then format as you need to.

Efficient way to find and replace many strings in a large text file

The Text file contains a software output on a time domain analysis. 10800 seconds simulation and 50 nodes being considered. We have 540,000 strings to be replaced in 540 MB text file with 4.5 million lines.
Which is currently projected to take more than 4 days. Something is going wrong. Don't know what. Please suggest me a better efficient approach.
Below is the function which does the find and replace.
To replace the string the script goes through the original text file line by line at the same time it generates a duplicate file with replaced strings. So another 540 MB file with 4.5 million lines will be generated at the end of the script.
Function ReplaceStringsInTextFile
{
$OutputfilebyLine = New-Object -typename System.IO.StreamReader $inputFilePathFull
$uPreviousValue = 0
$time = 60
$u = 0; $LastStringWithoutFindResult = 0
$lineNumber = 0
while ($null -ne ($line = $OutputfilebyLine.ReadLine())) {
$lineNumber = $lineNumber + 1
if ($time -le $SimulationTimeSeconds) # time simulation start and end checks
{
# 10800 strings corresponds to one node
# there are 50 nodes.. Thus 540,000 values
# $StringsToFindFileContent contains strings to find 540,000 strings
# $StringsToReplaceFileContent contains strings to replace 540,000 strings
$StringToFindLineSplit = -split $StringsToFindFileContent[$time-60]
$StringToReplaceLineSplit = -split $StringsToReplaceFileContent[$time-60]
if($u -le $NumberofNodes-1)
{
$theNode = $Nodes_Ar[$u]
$StringToFindvalue = $StringToFindLineSplit[$u]
$StringToReplacevalue = $StringToReplaceLineSplit[$u]
if (($line -match $theNode) -And ($line -match $StringToFindvalue)){
$replacedLine = $line.replace($StringToFindvalue,$StringToReplacevalue)
add-content -path $WriteOutputfilePathFull -value "$replacedLine"
$uPreviousValue = $u
$checkLineMatched = 1
if (($line -match $LastNodeInArray)) {
$time = $time + 1
$LastStringWithoutFindResult = 0
}
} elseIf (($line -match $LastNodeInArray) -And ($checkLineMatched -eq 0)) {
$LastStringWithoutFindResult = $LastStringWithoutFindResult + 1
} else {
#"Printing lines without match"
add-content -path $WriteOutputfilePathFull -value "$line"
$checkLineMatched = 0
}
}
if ($checkLineMatched -eq 1) {
# incrementing the value of node index to next one in case the last node is found
$u = $uPreviousValue + 1
if ($u -eq $Nodes_Ar.count) {
$u = 0
$timeElapsed = (get-date -displayhint time) - $startTime
"$($timeElapsed.Hours) Hours $($timeElapsed.Minutes) Minutes $($timeElapsed.Seconds) Seconds"
}
}
}
# Checking if the search has failed for more than three cycles
if ($LastStringWithoutFindResult -ge 5) { # showing error dialog in case of search error
[System.Windows.Forms.MessageBox]::Show("StringToFind Search Fail. Please correct StringToFind values. Aborting now" , "Status" , 0)
$OutputfilebyLine.close()
}
}
$OutputfilebyLine.close()
}
The above function is the last part of the script. Which is taking the most time.
I had run the script in under 10 hours 1 year ago.
Update The script sped up running after 4 hours and suddenly time to complete projection reduced from 4 days to under 3 hours. The script finished running in 7 hours and 9 minutes. However i am not sure what made the sudden change in speed other than asking the question on stack overflow :)
As per the suggestion by https://stackoverflow.com/users/478656/tessellatingheckler
I have avoided writing one line at a time using
add-content -path $WriteOutputfilePathFull -value "$replacedLine"
Instead i am now writing ten thousand lines at a time using add-content
$tenThousandLines = $tenThousandLines + "`n" + $replacedLine
And at the appropriate time I am using add-content to write 10,000 lines at one go like below. The if block follows my methods logic
if ($lineNumber/10000 -gt $tenThousandCounter){
clear-host
add-content -path $WriteOffpipeOutputfilePathFull -value "$tenThousandLines"
$tenThousandLines = ""
$tenThousandCounter = $tenThousandCounter + 1
}
I have encountered system out of memmory exception error when trying to add 15,000 or 25,000 lines at a time. After using this the time required for the operation has reduced from 7 hours to 5 hours. And at another time to 2 hours and 36 minutes.

Powershell adding numbers in a loop

I have the below code which is meant to total up the time offset as the loop rotates (I will then need to divide this by 10 to get the average but first I need to get this bit working).
I'm assuming I need to cast something as [INT] but I've tried multiple locations that would make sense to no avail. I just end up with O's.
$winTimeStripchart = w32tm /stripchart /computer:0.pool.ntp.org /dataonly /samples:10
$WinTimeOffset = $null
For($i=3; $i -le 12; $i++){
$Offset = $winTimeStripchart[$i].split("-")
$trimmedOffset = $Offset[1].trim("s")
$winTimeOffset = $winTimeOffset + $trimmedOffset
}
Write-Host "Total: $winTimeOffset"
# Now need to divide by 10.
sample data:
20:30:23, -00.0698082s
20:30:25, -00.0704645s
20:30:27, -00.0708694s
20:30:29, -00.0728990s
20:30:31, -00.0719226s
20:30:33, -00.0749031s
20:30:36, -00.0778656s
20:30:38, -00.0782183s
20:30:40, -00.0752974s
20:30:42, -00.0760958s
You can try this one line command :
$a = w32tm /stripchart /computer:0.fr.pool.ntp.org /dataonly /samples:10 | select -Skip 3 | % {[double]$_.Substring(11,10)} | Measure-Object -Average -sum
$a can also give you maximum and minimum adding Measure-Object params.
You'll want to cast to a double rather than int. The following should do it:
[System.Collections.ArrayList]$winTimeStripchart = w32tm /stripchart /computer:0.pool.ntp.org /dataonly /samples:10
$WinTimeOffset = $null
$winTimeStripchart.RemoveRange(0,3)
foreach($entry in $winTimeStripchart){
$Offset = $entry.split("-")
$trimmedOffset = $Offset[1].trim("s")
$winTimeOffset = [double]$winTimeOffset + [double]$trimmedOffset
}
Write-Host "Total: $winTimeOffset"
Write-Host "Average: $($winTimeOffset/$winTimeStripchart.count)"
Sample output:
Total: 6.1581437
Average: 0.61581437
I've make some other tweaks to the script as well to make it more scalable:
Foreach rather than a for loop
Using a list rather than array and stripping first 3 entries.
Dividing buy number of entries in list.
regards
Arcas

Return free space of all drives on a server using powershell

If the free space of a drive is 100% free and I set the minimum threshold space to 25%, the script incorrect reports that the drive is below threshold.. meaning 100 < 25
Although If I were to replace
$FreePercent = "{0:N0}" -f (100 * $objDisk.FreeSpace/$objDisk.Size) with
$FreePercent = (100 * $objDisk.FreeSpace/$objDisk.Size) ...
I get correct results.
How do i fix the rounding off problem.
Here is my code
# For Loop - get % free space of all drives
#Define Variables
$Notify = 0
$MinFreePercent = "25"
#$FileDriveSpace = "c:\temp\DriveSpace.txt"
$ComputerName = $(Get-WmiObject Win32_Computersystem).name
$OutArray = #()
$outarray += "Disk space Alerts and Utilizations on server $ComputerName"
$AllDisks = get-wmiobject Win32_LogicalDisk -Filter “DriveType = 3"
#Omit the a, b drives if exist
$AllDisks = $AllDisks | ? { $_.DeviceID -notmatch "[ab]:"}
foreach ($objdisk in $AllDisks)
{
#$FreePercent = "{0:P0} " -f ($objDisk.FreeSpace/$objDisk.Size)
$FreePercent = "{0:N0}" -f (100 * $objDisk.FreeSpace/$objDisk.Size)
If ($FreePercent -lt $MinFreePercent)
{
$Threshold = "Threshold Reached"
$Notify = 1
}
Else
{ $Threshold = "N/A"}
$DeviceId = $objDisk.DeviceID
$myobj = "" | Select "Drive","PercentFreeSpace", "Threshold"
$myobj.Drive = $DeviceId
$myobj.PercentFreeSpace = $FreePercent
$myobj.Threshold = $Threshold
$outLine = $DeviceId + " " + $FreePercent + " " + $Threshold + " " + $MinFreePercent
$outLine
#Add the object to the out-array
$outarray += $myobj
#Wipe the object just to be sure
$myobj = $null
}
So the problem here is the -f or formatting operator. It's taking your awesome drive percent free [int] and turning it into a plain old boring [string]. I personally use the [math] accelerator and call the round method.
[math]::Round($mynumber,0)
You could also save yourself some time by looking in the technet gallery for this type of thing:
http://gallery.technet.microsoft.com/Get-HardDrive-0eef638f
I don't remember if that version returns data as a string or an integer but you'll see an example of the round method in action.