I'm trying to run on a list of processes and if found put the process name in hashtable with value True and if not False, the True part works just fine I got an issue if the process is not found it won't pair the searched process name with Flase just be blank
CODE
$processStatus = $null
$processStatus = #{}
$processName = "firefox", "discord", "fgdfg"
[System.Diagnostics.Process[]]$processList = Get-Process $processName -ErrorAction SilentlyContinue |
Sort-Object -Property name -Unique
ForEach ($process in $processList) {
if($process -ne $null) {
$processStatus.Add($process.ProcessName,"True")
} else {
$processStatus.Add($process,"Flase")
}
}
Result
Name Value
---- -----
Discord True
firefox True
Expected result
Name Value
---- -----
Discord True
firefox True
fgdfg False
Your quite close. You just need loop through the process names and run Get-Process against each process name. Then check if the process exists before setting the status in the hash table.
$processNames = "firefox", "discord", "fgdfg"
$processStatus = #{}
foreach ($processname in $processNames) {
$processes = Get-Process -Name $processName -ErrorAction SilentlyContinue
if ($null -ne $processes) {
$processStatus[$processName] = $true
}
else {
$processStatus[$processName] = $false
}
}
Also make sure to put $null on the left side of the operator when doing comparisons. If your using VSCode, PSScriptAnalyzer will give a PSPossibleIncorrectComparisonWithNull warning if you don't do this. Have a look at the documentation for more information as to why this is recommended.
We could also use the .NET method System.Diagnostics.Process.GetProcessByName to get the processes that share the same name. This method returns System.Diagnostics.Process[], so we can simply check if the count is greater than 0.
$processNames = "firefox", "discord", "fgdfg", "notepad"
$processStatus = #{}
foreach ($processname in $processNames) {
$processes = [System.Diagnostics.Process]::GetProcessesByName($processName)
if ($processes.Count -gt 0) {
$processStatus[$processName] = $true
}
else {
$processStatus[$processName] = $false
}
}
Execute Get-Process against each individual process name instead:
$processStatus = #{}
$processName = "firefox", "discord", "fgdfg"
$processName |ForEach-Object {
$processStatus[$_] = #(Get-Process -Name $_ -ErrorAction SilentlyContinue).Count -gt 0
}
If you want a list of status objects instead, you could also use Select-Object:
$processNames |Select-Object #{Name='Name';Expression={$_}},#{Name='Status';Expression={#(Get-Process -Name $_ -ErrorAction SilentlyContinue).Count -gt 0}}
Related
I have a simple script that can pull RAM partnumbers from a remote computer and search google for it. But it does not work as intended. If there's only 1 RAM module installed in the remote computer, it works great google opens with the search result for the Partnumber, yay!.
if there are more than 1 RAM module installed in the remote computer, the first Partnumber in the variable gets searched for in Google. The 2'nd, 3'rd, 4'th partnumber gets typed in to Chrome tab 2,3,4 as an address.
How can I get Chrome to search for all Partnumbers via Google?
My script:
$ComputerName = Read-Host "Write Computer Name"
Get-WmiObject Win32_PhysicalMemory -computername $ComputerName
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {$Partnumber = Get-WmiObject Win32_PhysicalMemory -computername $ComputerName | select -expandproperty Partnumber
Start-Process "chrome.exe" ("https://www.google.com/search?q=$Partnumber")}
if ($ToChrome -eq 'n') {Continue}
That is because chrome.exe interprets the space between the part numbers as new addresses.
I took the liberty to pimp the script with try&catch,a logfile output and the computername as a parameter so that you can call it as Get-MemoryPropertyAndSearchWithGoogle.ps1 -ComputerName ComputerName1
For my testing I used the attribute DeviceLocator as my PartNumber was empty.
#Get-MemoryPropertyAndSearchWithGoogle.ps1
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]$ComputerName
)
$ErrorPreference='Stop'
$ErrorActionPreference='Stop'
$LogFilePath = "C:\Temp\$((Get-Date).ToString("yyyy-MM-dd"))$($ComputerName)Get-MemoryPropertyAndSearchWithGoogle.log"
[string]$LogFileString = ""
#$Property = "PartNumber"
$Property = "DeviceLocator"
$ErrorExists = $false
$ComputerMemoryObjects = #()
try
{
$ComputerMemoryObjects = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName -Property *
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#Get-WmiObject Win32_PhysicalMemory -ComputerName $($ComputerName)`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
[string]$SearchString = ""
foreach ($SingleComputerMemoryObject in $ComputerMemoryObjects)
{
if ($SearchString)
{
$SearchString += "+OR+"
}
$SearchString += "$($SingleComputerMemoryObject.$Property)"
}
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y')
{
if ($SearchString)
{
try
{
Start-Process "chrome.exe" ("https://www.google.com/search?q=$($SearchString)")
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#chrome.exe started with searchstring:`"$($SearchString)`"`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
}
else
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#`$SearchString is empty`n"
}
}
if (!($ErrorExists))
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#ScriptCompletedWithoutErrors`n"
}
$LogFileString | Out-File $LogFilePath
$LogFileString
You get multiple objects from Get-WmiObject. You need a loop if you want to do something for each of them.
Also, URL-encoding things that you put into a URL is a good idea. and maybe putting it in double-quotes won't hurt.
Add-Type -AssemblyName System.Web # for [System.Web.HttpUtility]::UrlEncode()
$ComputerName = Read-Host "Write Computer Name"
$installed_memory = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object Manufacturer,PartNumber,SerialNumber,DeviceLocator,Capacity
$installed_memory | Format-Table -AutoSize
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {
$unique_numbers = $installed_memory.Partnumber.Trim() | Sort-Object -Unique
foreach ($number in $unique_numbers) {
$query = [System.Web.HttpUtility]::UrlEncode('"' + $number + '"')
Start-Process chrome.exe "https://www.google.com/search?q=$query"
}
}
Powershell has a handy convenience feature: When you have an array of objects, you can query nested properties from all of them in one go.
For example, if there are 4 Win32_PhysicalMemory objects in $installed_memory, then
$installed_memory.Partnumber.Trim()
gives you 4 readily trimmed part numbers in a single step.
I have a basic script, which will shutdown Windows services and generate a report about their shutdown processes. I also want to include two more columns into my output variable ($table), which will be timestamp values i.e. when shutdown tasks were launched and when they finished. I have no idea how to implement this into my report.
$processlist = #('SQLTELEMETRY$TESTDB', 'MSSQL$TESTDB', 'SQLWRITER')
$get = ''
$table = #{ }
$failed = 0
foreach ($proc in $processlist) {
stop-service -name $proc -force
}
#start-sleep -s 120
foreach ($proc in $processlist) {
$get = get-service $proc -Erroraction ignore
if ($get.Status -eq 'Running') {
$table += #{$proc = 'Running' }
}
else {
$table += #{$proc = 'Stopped' }
}
}
foreach ($value in $table.GetEnumerator()) {
if ($value.Value -eq 'Running') {
$failed += 1
}
}
if ($failed -gt 0) {
$err = 'FAILED'
}
else {
$err = 'SUCCESS'
}
$table.GetEnumerator() | Select-Object -Property Name, Value | export-csv appreport.csv -delimiter ";" -force -notypeinformation
(HTML part here...)
Instead of adding stuff into a Hashtable, I think it would be a lot easier to build an array of objects and write that as CSV file.
Something like this:
$serviceList = 'SQLTELEMETRY$TESTDB', 'MSSQL$TESTDB', 'SQLWRITER'
$maxAttempts = 10
# $result will become an array of PsCustomObjects you can easily pipe to Export-Csv
$result = foreach ($service in $serviceList) {
$shutStart = Get-Date
$svc = Get-Service -Name $service -ErrorAction SilentlyContinue
if ($svc) {
for ($attempt = 0; $attempt -lt $maxAttempts; $attempt++) {
$shutResult = 'Failed'
Start-Sleep -Milliseconds 100
$svc | Stop-Service -Force -ErrorAction SilentlyContinue
# test if the service has stopped. If so exit the loop
if (($svc | Get-Service).Status -eq 'Stopped') {
$shutResult = 'Success'
break
}
}
[PsCustomObject]#{
'ServiceName' = $svc.Name
'ServiceDisplayName' = $svc.DisplayName
'ShutDownStart' = $shutStart
'ShutDownEnd' = Get-Date
'Result' = $shutResult
}
}
else {
[PsCustomObject]#{
'ServiceName' = $service
'ServiceDisplayName' = ''
'ShutDownStart' = $shutStart
'ShutDownEnd' = Get-Date
'Result' = "Failed: Service '$service' could not be found."
}
}
}
# output on screen
$result
# output to CSV
$result | Export-Csv 'D:\appreport.csv' -Delimiter ";" -Force -NoTypeInformation
The output on screen will look like this:
ServiceName : SQLTELEMETRY$TESTDB
ServiceDisplayName :
ShutDownStart : 22-8-2019 16:47:40
ShutDownEnd : 22-8-2019 16:47:40
Result : Failed: Service 'SQLTELEMETRY$TESTDB' could not be found.
ServiceName : MSSQL$TESTDB
ServiceDisplayName :
ShutDownStart : 22-8-2019 16:47:40
ShutDownEnd : 22-8-2019 16:47:40
Result : Failed: Service 'MSSQL$TESTDB' could not be found.
ServiceName : SQLWRITER
ServiceDisplayName : SQL Server VSS Writer
ShutDownStart : 22-8-2019 16:47:38
ShutDownEnd : 22-8-2019 16:47:39
Result : Success
Hope that helps
I don't really know when you want to capture the time stamp for the services, but I suggest you take advantage of the below property and add it in the loop where you think its suitable.
(Get-Process -Name $proc).StartTime
Also you can use the below properties :
UserProcessorTime
TotalProcessTime
ExitTime
I hope this will help you to capture to time.
This only prints the last server in the list, I'm looking to get all servers and print to screen
$machines = (Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach($machine in $machines){
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if($machinelist.InMaintenanceMode -eq $true){
$status = "$machine is in maintenance mode"
}else {
$status = "$machine is not in maintenance mode"
}
}
Write-Host $status
Here is a more PowerShell-like approach (not tested):
Get-BrokerMachine -AdminAddress $adminaddress -DesktopGroupName $deliverygroup | ForEach-Object {
$machineName = $_.DNSName
[PSCustomObject] #{
"MachineName" = $machineName
"MaintenanceMode" = (Get-BrokerMachine -HostedMachineName $machine).InMaintenanceMode
}
} | Export-Csv "C:\whatever\results.csv" -NoTypeInformation
$Status is constantly being overwritten by the current machine in your list.
You're looking for:
$Status+=
As opposed to:
$Status=
You'll also want to explicitly state that $Status will be an array at the beginning like so:
$Status=#()
Or when you create the variable and omit the line at the beginning.
[array]$Status +=
Otherwise, you'll get results that run together as it will be treated as a [String]
another funky mode :
function get-BrokerMachineMode
{
param (
[Parameter(Mandatory = $true)]
[string[]]$machines
)
begin
{
$ErrorActionPreference = 'Stop'
Add-Type -Language CSharp #"
public class BrokenBroker {
qpublic System.String MachineName;
public System.String MaintenanceMode;
public BrokenBroker (string MachineName, string MaintenanceMode)
{
this.MachineName = MachineName;
this.MaintenanceMode = IsInMaintenanceMode;
}
}
"#
$status = #()
Write-Verbose "Created objects..."
}
process
{
try
{
$machines = (Get-BrokerMachine -AdminAddress $adminaddress `
-DesktopGroupName $deliverygroup | Select-Object DNSname).DNSname
foreach ($machine in $machines)
{
Write-Verbose "Checking machine: $machine"
$machinelist = Get-BrokerMachine -HostedMachineName $machine
if ($machinelist.InMaintenanceMode -eq $true)
{
$status += New-Object BrokenBroker($machine, $true)
}
else
{
$status += New-Object BrokenBroker($machine, $false)
}
}
}
catch
{
Write-Error $error[0].Exception.Message
}
$status
}
end
{
Write-Verbose "Done"
}
}
this is a function you just must to load then you can launch it just by using this command:
$computers = get-content = {PATH TO TXT FILE}
$list = get-BrokerMachineMode -machines $computers -Verbose
I'm trying to create a script that runs on my clients computers and checks for a pending reboot. If there is a pending reboot it pops up a message that asks the user to reboot or defer. This works great when I try it on my local computer. However, once I put it into System Center and deploy it, it does not run and I get the error "Warning: You cannot call a method on a null-valued expression" Any ideas? Here is the script
[CmdletBinding()]
param(
[Parameter(Position=0,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("CN","Computer")]
[String[]]$ComputerName="$env:COMPUTERNAME",
[String]$ErrorLog
)
Begin
{
# Adjusting ErrorActionPreference to stop on all errors, since using [Microsoft.Win32.RegistryKey]
# does not have a native ErrorAction Parameter, this may need to be changed if used within another
# function.
$TempErrAct = $ErrorActionPreference
$ErrorActionPreference = "Stop"
}#End Begin Script Block
Process
{
Foreach ($Computer in $ComputerName)
{
Try
{
# Setting pending values to false to cut down on the number of else statements
$PendFileRename,$Pending,$SCCM = $false,$false,$false
# Setting CBSRebootPend to null since not all versions of Windows has this value
$CBSRebootPend = $NULL
# Querying WMI for build version
$WMI_OS = Get-WmiObject -Class Win32_OperatingSystem -Property BuildNumber, CSName -ComputerName $Computer
# Making registry connection to the local/remote computer
$RegCon = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]"LocalMachine",$Computer)
# If Vista/2008 & Above query the CBS Reg Key
If ($WMI_OS.BuildNumber -ge 6001)
{
$RegSubKeysCBS = $RegCon.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\").GetSubKeyNames()
$CBSRebootPend = $RegSubKeysCBS -contains "RebootPending"
}#End If ($WMI_OS.BuildNumber -ge 6001)
# Query WUAU from the registry
$RegWUAU = $RegCon.OpenSubKey("SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\")
$RegWUAURebootReq = $RegWUAU.GetSubKeyNames()
$WUAURebootReq = $RegWUAURebootReq -contains "RebootRequired"
# Closing registry connection
$RegCon.Close()
# Determine SCCM 2012 Client Reboot Pending Status
# To avoid nested 'if' statements and unneeded WMI calls to determine if the CCM_ClientUtilities class exist, setting EA = 0
$CCMClientSDK = $null
$CCMSplat = #{
NameSpace='ROOT\ccm\ClientSDK'
Class='CCM_ClientUtilities'
Name='DetermineIfRebootPending'
ComputerName=$Computer
ErrorAction='SilentlyContinue'
}
$CCMClientSDK = Invoke-WmiMethod #CCMSplat
If ($CCMClientSDK)
{
If ($CCMClientSDK.ReturnValue -ne 0)
{
Write-Warning "Error: DetermineIfRebootPending returned error code $($CCMClientSDK.ReturnValue)"
}#End If ($CCMClientSDK -and $CCMClientSDK.ReturnValue -ne 0)
If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending)
{
$SCCM = $true
}#End If ($CCMClientSDK.IsHardRebootPending -or $CCMClientSDK.RebootPending)
}#End If ($CCMClientSDK)
Else
{
$SCCM = $null
}
# If any of the variables are true, set $Pending variable to $true
If ($CBSRebootPend -or $WUAURebootReq -or $SCCM)
{
$Pending = $true
}#End If ($CBS -or $WUAU)
If ($Pending = $CBSRebootPend -or $WUAURebootReq -or $SCCM)
{
$a = new-object -comobject wscript.shell
$b = $a.popup(“A Reboot is Pending, Press ""OK"" to reboot now or ""Cancel"" to reboot later.“,240,”CRISTA IT”,1)
if($b -eq 1) {
Restart-Computer
}
if($b -eq 2) {
#cancle was selected in the box. So should exit
exit(1)
}
}
}#End Try
Catch
{
Write-Warning "$Computer`: $_"
# If $ErrorLog, log the file to a user specified location/path
If ($ErrorLog)
{
Out-File -InputObject "$Computer`,$_" -FilePath $ErrorLog -Append
}#End If ($ErrorLog)
}#End Catch
}#End Foreach ($Computer in $ComputerName)
}#End Process
End
{
# Resetting ErrorActionPref
$ErrorActionPreference = $TempErrAct
}#End End
PLEASE NOTE: I did not create this script fully but used different free scripts online to create this.
What is the correct way of determining if a process is running, for example FireFox, and stopping it?
I did some looking around and the best way I found was this:
if((get-process "firefox" -ea SilentlyContinue) -eq $Null){
echo "Not Running"
}
else{
echo "Running"
Stop-Process -processname "firefox"
}
Is this the ideal way of doing it? If not, what the correct way of doing so?
The way you're doing it you're querying for the process twice. Also Lynn raises a good point about being nice first. I'd probably try something like the following:
# get Firefox process
$firefox = Get-Process firefox -ErrorAction SilentlyContinue
if ($firefox) {
# try gracefully first
$firefox.CloseMainWindow()
# kill after five seconds
Sleep 5
if (!$firefox.HasExited) {
$firefox | Stop-Process -Force
}
}
Remove-Variable firefox
If you don't need to display exact result "running" / "not runnuning", you could simply:
ps notepad -ErrorAction SilentlyContinue | kill -PassThru
If the process was not running, you'll get no results. If it was running, you'll receive get-process output, and the process will be stopped.
#jmp242 - the generic System.Object type does not contain the CloseMainWindow method, but statically casting the System.Diagnostics.Process type when collecting the ProcessList variable works for me. Updated code (from this answer) with this casting (and looping changed to use ForEach-Object) is below.
function Stop-Processes {
param(
[parameter(Mandatory=$true)] $processName,
$timeout = 5
)
[System.Diagnostics.Process[]]$processList = Get-Process $processName -ErrorAction SilentlyContinue
ForEach ($Process in $processList) {
# Try gracefully first
$Process.CloseMainWindow() | Out-Null
}
# Check the 'HasExited' property for each process
for ($i = 0 ; $i -le $timeout; $i++) {
$AllHaveExited = $True
$processList | ForEach-Object {
If (-NOT $_.HasExited) {
$AllHaveExited = $False
}
}
If ($AllHaveExited -eq $true){
Return
}
Start-Sleep 1
}
# If graceful close has failed, loop through 'Stop-Process'
$processList | ForEach-Object {
If (Get-Process -ID $_.ID -ErrorAction SilentlyContinue) {
Stop-Process -Id $_.ID -Force -Verbose
}
}
}
To start with process-killing, here python, my 2 cents:
Get-Process python3.9|Stop-Process
Thanks #Joey. It's what I am looking for.
I just bring some improvements:
to take into account multiple processes
to avoid reaching the timeout when all processes have terminated
to package the whole in a function
function Stop-Processes {
param(
[parameter(Mandatory=$true)] $processName,
$timeout = 5
)
$processList = Get-Process $processName -ErrorAction SilentlyContinue
if ($processList) {
# Try gracefully first
$processList.CloseMainWindow() | Out-Null
# Wait until all processes have terminated or until timeout
for ($i = 0 ; $i -le $timeout; $i ++){
$AllHaveExited = $True
$processList | % {
$process = $_
If (!$process.HasExited){
$AllHaveExited = $False
}
}
If ($AllHaveExited){
Return
}
sleep 1
}
# Else: kill
$processList | Stop-Process -Force
}
}