Using an If Statement with a Piping Command - powershell

I'm completely new to using PowerShell and computer programming in general. I'm trying to create a script that kills a process(firefox in this case) if it exceeds a working set of 10mb. I would like to do this using an if statement and also having a piping command included. So far I have this:
get-process|where-object {$_.ProcessName -eq"firefox"}
if ($_.WorkingSet -gt10000000)
{kill}
else
{"This process is not using alot of the computers resources"}
Can anyone help to fix this? Even though firefox is exceeding 10MB working set, the else statement is always reproduced.

You need to wrap the conditional in a loop:
Get-Process | ? { $_.ProcessName -eq 'firefox' } | % {
if ($_.WorkingSet -gt 10MB) {
kill $_
} else {
"This process is not using alot of the computers resources"
}
}
Otherwise the conditional would be evaluated independently from the pipeline, which means that the current object variable ($_) would be empty at that point.

You can filter the process in question by using the Name parameter (no need to use Where-Object for this purpose) then pipe the objects to the Stop-Process cmdlet. Notice the -WhatIf switch, it shows what would happen if the cmdlet runs (the cmdlet is not run). Remove it to execute the cmdlet.
Get-Process -Name firefox |
Where-Object {$_.WorkingSet -gt 10mb} |
Stop-Process -WhatIf

Related

Using Get-ChildItem with a string variable

I'm using Get-ChildItem to save file locations that match user-input parameters. I can't seem to figure out (I'm new to powershell) how to use string variables with cmdlets.
I'll define $search_param as it is in my script below.
I've tried using the built in debugger on powershell ise, and if anything it's confused me even more.
This is where I found that replacing the use of '$search_param' with the actual value of $search_param produced a csv output, but using '$search_param' gives no output. Some useful information might be that I defined $search_param with double quotations.
The line that is causing all the issues is:
Get-ChildItem 'C:\Users\USER\' -recurse '$search_param'
$search_param is defined as:
| where { $_.name -like "*peanut*" -or $_.name -like "*butter*" -or $_.name -like "*test*"}
| where { $_.extension -in ".txt",".csv",".docx" }
| where { $_.CreationTime -ge "08/07/1998" -and $_.CreationTime -le "08/07/2019" }
| select FullName | Export-Csv -Path .\SearchResults.csv -NoTypeInformation
Funnily enough I had it all working before I went to lunch and came back to a different story..
I'm using Export-csv in another piece of my script and that is working as intended.
I may have touched something small that isn't related to the line I provided as I think this should be working..
Per Technician's comment:
You can use invoke expression to "evaluate or run a specified string as a command and return the results of the expression or command."
Invoke-Expression "Get-ChildItem 'C:\Users\SGouldin\' -recurse $search_param"
From a little bit of research, relying on invoke expression isn't always the best practice.
In my case, I needed it because of the way I was attempting to handle user input (some weeeeird string concatenation/joins).

Read from randomly named text files

I'm finishing a script in PowerShell and this is what I must do:
Find and retrieve all .txt files inside a folder
Read their contents (there is a number inside that must be less than 50)
If any of these files has a number greater than 50, change a flag which will allow me to send a crit message to a monitoring server.
The piece of code below is what I already have, but it's probably wrong because I haven't given any argument to Get-Content, it's probably something very simple, but I'm still getting used to PowerShell. Any suggestions? Thanks a lot.
Get-ChildItem -Path C:\temp_erase\PID -Directory -Filter *.txt |
ForEach-Object{
$warning_counter = Get-Content
if ($warning_counter -gt '50')
{
$crit_counter = 1
Write-Host "CRITICAL: Failed to kill service more than 50 times!"
}
}
but it's probably wrong because I haven't given any argument to Get-Content
Yes. That is the first issue. Have a look at Get-Help <command> and or docs like TechNet when you are lost. For the core cmdlets you will always see examples.
Second, Get-Content, returns string arrays (by default), so if you are doing a numerical comparison you need to treat the value as such.
Thirdly you have a line break between foreach-object cmdlet and its opening brace. That will land you a parsing problem and PS will prompt for the missing process block. So changing just those mentioned ....
Get-ChildItem -Path C:\temp_erase\PID -Directory -Filter *.txt | ForEach-Object{
[int]$warning_counter = Get-Content $_.FullName
if ($warning_counter -gt '50')
{
$crit_counter = 1
Write-Host "CRITICAL: Failed to kill service more than 50 times!"
}
}
One obvious thing missing from this is you do not show which file triggered the message. You should update your notification/output process. You also have no logic validating file contents. The could easily fail, either procedural or programically, on files with non numerical contents.

How do I exclude a block from -WhatIf processing?

I'm writing a Powershell cmdlet that needs to execute a command and store its stderr output to a temporary file for later processing. This output lists COM ports that the cmdlet may use later.
# mostly side-effect-free information gathering
$info = [IO.Path]::GetTempFileName()
info-gather.exe 2>$info
Get-Content $info | ForEach-Object {
# processing
}
Remove-Item $info
# serious things with serious side effects start here
I would like this cmdlet to implement -WhatIf since it will have non-trivial side effects. However, the -WhatIf behavior takes over the info-gather.exe command and it is simply never executed. Instead, it prints:
What if: Performing the operation "Output to File" on target "temp\path\to\tmp78A4.tmp"
Because this command is never executed, the inner processing doesn't happen either, and the rest of my cmdlet that has actual side effects isn't executed because it doesn't know which ports to use, making -WhatIf largely useless.
How can I bypass -WhatIf for this block without overriding it in the rest of the cmdlet?
You could use try...finally to set and then reset the $WhatIfPreference variable.
$script:oldWhatIfPrefernence = $WhatIfPreference
try {
$WhatIfPreference = $false
$info = [IO.Path]::GetTempFileName()
info-gather.exe 2>$info
Get-Content $info | ForEach-Object {
# processing
}
Remove-Item $info
} finally {
$WhatIfPreference = $script:oldWhatIfPreference
}
If you weren't using the 2> redirection, there is another way. You can pass the -WhatIf switch explicitly to many cmdlets and override -WhatIf for just that part.
Remove-Item $info -WhatIf:$false
Though, zneak's answer that doesn't involve a temporary file is pretty elegant too.
One solution is to "redirect" stderr to stdout. Doing this, Powershell appends error records to the standard output, which can be converted to regular strings using Out-String. In my case (YMMV depending on the program), the first error record says "there was an error with this program" and the second error record is the actual, full stderr output. This is also simplified by the fact that the command doesn't write anything to the standard output, so there's no filtering to do there.
I was able to change the foreach loop to use this:
((info-gather.exe 2>&1)[1] | Out-String) -split "[`r`n]" | foreach-object
Since this does not write to a file, -WhatIf no longer overrides it.
There is probably a simpler and cleaner way to do this (for instance, if there was a way to pipe stderr to Out-String directly).

How can I effectively conserve resources using the PowerShell pipeline?

I have several PowerShell scripts that I use to manage a medium sized Microsoft Exchange organization (~10,000 mailboxes). Several of the scripts process all of the organization's mailboxes in some way. One common problem I run into while running these scripts is resource exhaustion. These scripts end up using gigabytes of RAM.
My research suggests that using the pipeline avoids memory consumption because the results aren't loaded into an array prior to processing. However, under certain conditions, Get-Mailbox still seems to load the entire list of results into memory before it attempts to pass those results to the next command in the pipeline.
For instance, I assumed the following example code would start listing the mobile devices associated with each mailbox as soon as the the command is executed:
EXAMPLE 1
function GetMailboxDevices
{
process
{
Write-Host $_.Alias -ForegroundColor Green
Get-MobileDevice -Mailbox $_
}
}
Get-Mailbox -ResultSize Unlimited | GetMailboxDevices
However, this code does not appear to process the results in real time while the Get-Mailbox cmdlet is running. Instead, Get-Mailbox appears to take a few minutes to run and then passes all of the results to the second command in the pipeline at once. The PowerShell session's RAM usage climbs to 1.5 GB or higher during this process.
Nevertheless, I can work around the issue using code similar to the following:
EXAMPLE 2
function GetMailboxAliases
{
process
{
Write-Host $_.Alias -ForegroundColor Green
$_.Alias
}
}
$aliases = Get-Mailbox -ResultSize Unlimited | GetMailboxAliases
foreach ($alias in $aliases)
{
Get-MobileDevice -Mailbox $alias
}
In the second example, Get-Mailbox does pass each result down the pipeline in real time as opposed to all at once (Write-Host confirms this) and the RAM usage does not increase significantly. Of course, this code is not as elegant as I have to collect the aliases into an array and then process the array with a foreach statement.
The pipeline seems to be effective if I do something simple in the function (such as simply returning the alias of each mailbox), but the behavior changes as soon as I introduce another Exchange cmdlet into the function (such as Get-MobileDevices).
My question is this: why doesn't the code in example 1 leverage the pipeline efficiently but example 2 does? What steps can be taken to ensure the pipeline is leveraged efficiently?
I'am not using Exchange so mush, but in my scripts I would do this :
function GetMailboxDevices ($mb)
{
process
{
Write-Host $_.Alias -ForegroundColor Green
Get-MobileDevice -Mailbox $mb
}
}
Get-Mailbox -ResultSize Unlimited | Foreach-object { GetMailboxDevices $_}
or
Get-Mailbox -ResultSize Unlimited | % { GetMailboxDevices $_}
When you are running a script that returns a lot of objects, loading them to a variable is a great way to help memory usage. This is why version 2 performs better.
Also, running this script remotely from another server or workstation could also make code execution easier and make resource usage easier to troubleshoot.
You can try this to check your impressions:
Measure-Command { script1.ps1 }
Measure-command { script2.ps1 }

Use of property names in PowerShell pipeline $_ variable

As a C# developer, I'm still learning the basics of PowerShell and often getting confused.
Why does the $_. give me the intellisense list of vaild property names in the first example, but not the second?
Get-Service | Where {$_.name -Match "host" }
Get-Service | Write-Host $_.name
What is the basic difference in these two examples?
When I run the second one, it gives this error on each iteration of Get-Service:
Write-Host : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters
that take pipeline input.
At line:3 char:15
+ Get-Service | Write-Host $_.name
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (wuauserv:PSObject) [Write-Host], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.WriteHostCommand
My coworker and I were first using a foreach loop to iterate a Get-commandlet, and we were stuck getting the property names to appear. We tried to simplify till we go to the core basics above.
Just figured out sometimes it's a typo in the commandlet, below first one fails because the commandlet should be Get-Service instead of Get-Services.
foreach ($item in Get-Services)
{
Write-Host $item.xxx #why no intellisense here?
}
foreach ($item in Get-Service)
{
Write-Host $item.Name
}
First part: you can't use $_ like that, it represents current pipeline object only within script blocks. These script blocks are usually used with any *-Object cmdlet (but there are other use cases too). Not all parameters/ cmdlet support it. Write-Host is one of those that don't.
Second: It looks like you are using own function (GetServices). PowerShell v3 intellisense is depending on command metadata (OutputType). If any cmdlet/ function produces object but is silent about OutputType, intellisense won't work. It's pretty simple to get it, and you can lie and still get proper intellisense for any existing type:
function Get-ServiceEx {
[OutputType('System.ServiceProcess.ServiceController')]
param ()
'This is not really a service!'
}
(Get-ServiceEx).<list of properties of ServiceController object>
You can read more about it on my blog.
Intellisense will work if you put $_ inside a scriptblock.
The following will trigger intellisense:
Get-Service | Foreach-Object {$_.Name} # Intellisense works
Get-Service | Where-Object {$_.Name} # Intellisense works
Get-Service | Write-Host {$_.Name} # Intellisense works
Note that your command need not be a valid command: the third example will not work but intellisense will display auto-complete for $_ anyway because it is inside a scriptblock.
I suspect it is because $_ is only usable inside a scriptblock (e.g. switch, %, ?) but I have no hard-evidence for it.
$_ is the current object in the pipeline. It's the same as $item in a foreach ($item in $array).
Get-Service | Where {$_.name -Match "host" }
Get-Service | Write-Host $_.name
The difference between these two lines is a fundamental part of the PowerShell design. Pipelines are supposed to be easy. You should be able to ex. search for and delete files as simply as:
Get-ChildItem *.txt | Remove-Item
This is clean, simple, and everyone can use it. The cmdlets are built to identify the type of the incoming object, adapt to support the specific type, and process the object.
However, sometimes you need more advanced object manipulation in the pipeline, and that's where Where-Object and Foreach-Object comes in. Both cmdlets lets you write your own processing logic without having to create an function or cmdlet first. To be able to access the object in your code(processing logic), you need an identifier for it, and that is $_. $_ is also supported in some other special cmdlets, but it's not used in most cmdlets(including Write-Host).
Also, Intellisense in foreach ($item in Get-Service) works. You had a typo.