Powershell script to compare processes from text file - powershell

I need to do the following:
Get a list of all the current processes on the system and save them to a text file called “before.txt”
Launch a new browser window (Chrome, Edge, Firefox – your choice)
Get a new list of all the current processes on the system and save them to a text file called “after.txt”
Using the compare-object cmdlet in PowerShell, find the new process ID for the browser you just opened
Once the process ID has been identified, kill the process using that process ID
I'm having difficulty completing the last task of killing the identified process from the text file. Any help is greatly appreciated. Thank you!
########################## Current Script#########################
Get-Process | select-object -Expand Name > before.txt
Invoke-Item "C:\Program Files\Internet Explorer\iexplore.exe"
Start-Sleep -s 3
Get-Process | select-object -Expand Name > after.txt
Compare-Object (get-content before.txt) (Get-Content after.txt) > final.text
$list = Get-Content final.text; Get-Process $list | kill -force -Raw

The simple fix to your code is to change the compare-object line. Try running that line without the redirect and you'll see it's not a simple list of names. Try changing that line in your script to the following:
Compare-Object (get-content before.txt) (Get-Content after.txt) | select -ExpandProperty InputObject > final.text
However, in PowerShell you don't need to use all these temporary files. You can have arrays of items. A more PowerShell answer would be:
$before = Get-Process
Invoke-Item ...
$after = Get-Process
$list = Compare-Object $before $after
$list |% { stop-process $_.InputObject.id }

Related

Is Start-Process -Passthru unable to grab the process ID?

I have a PowerShell script able to open and close .txt and .exe via a returned PID from Start-process $type -Passthru. However, if I try to do this same thing with .pngs I get this error:
Start-Process : This command cannot be run completely because the system cannot find all the information required.
I am trying to get a returned PID so I can close the default photo editor without having to explicitly call it by name. Code sample:
$object = Get-ChildItem | Where-Object {$_.LastWriteTime -gt (Get-Date).AddDays(-730)} | Select-Object -Property Name
[System.Collections.ArrayList] $filearray = $object
$removed = $filearray[-1].Name
$open_photo = Start-Process -FilePath $removed -passthru
$open_photo.Id

Adding each item from for loop to csv file

I am trying to use power shell to determine whether a server has a particular patch installed based on the KB and if not append the name to a csv. my input file has system names so I want to export that system name if it does not find the patch installed.
here is what i have so far. The export to csv part does not seem to work.
forEach-Object{
try{
$status = wmic /node:#sys.csv qfe list full /format:table | findstr /i $kb_number
if(!$status){
$output_file = New-Item C:\temp\$kb_number.csv -ItemType File
export-csv $output_file -append -Force
}
else{
write-output $status
}
}
catch{
$error_message = $_.Exception.Message
#write-output "the error message is" $error_message
write-output "Could not find any system with this patch installed."
}
}
Why your code might be failing
We don't see where you're setting the values of #sys.csv or $kb_number in the code you shared. Either of those could be throwing you off.
But the real issue is Export-Csv. For one, you're making a new CSV with every iteration of the loop. And for two, you have to pass in some item for the cmdlet to export as a CSV. Right now, you're only providing these values.
$output_file = New-Item C:\temp\$kb_number.csv -ItemType File
Export-csv -Path $output_file -append -Force
Export-Csv requires an input object. You're not giving it one now.
What do you want to export? If you just want a list of computers without a patch, do this instead.
if(-not(Test-path C:\temp\$kb_number.csv)){
#if file doesn't exist, make it
$output_file = New-Item C:\temp\$kb_number.txt -ItemType File
}
#adds computer name if it doesn't have the patch
Add-Content -Path $output_file -Value $computer
General Suggestions
Instead of using ForEach-Object, you might find it's easier to debug if you use a ForEach loop like this.
$computers = Get-Content C:\pathTo\Your\ComputerList.txt
ForEach($computer in $computers){
}
One additional source of trouble is that your code is using older dos commands in WMIC and then tries to use PowerShell to store the records. You don't need to do this and can make it easier on yourself if you swap out the calls to wmic for Get-WmiObject or Get-CimInstance, the PowerShell native versions of the commands.
To do that, change this line:
wmic /node:#sys.csv qfe list full /format:table | findstr /i $kb_number
translates into
$kb_number = "KB4576484"
Get-CimInstance Win32_QuickFixEngineering -Filter "HotfixID = '$kb_number'" -ComputerName $computer
Source Description HotFixID InstalledBy InstalledOn
------ ----------- -------- ----------- -----------
Update KB4576484 NT AUTHORITY\SYSTEM 9/14/2020 12:00:00 AM
You can store the output of that in a variable and then call Export-Csv on it and that should work.
When in doubt, remove the filter part and just get it working to export all patches to a csv. Then add complexity by adding back the filtering statements.

Kill a task based on its windows title

I'm looking to kill a process using it PID when a particular file is open on my computer, so that it doesn't interfere with another script
I have an excel sheet that is referenced by other parts of my system, it is fed information from many different sources I want to automate my workflow to work more smoothly.
Get-Process |where {$_.mainWindowTItle -eq "RASP transfer.xlsx - Excel"} |Write-Output
I'm stuck on how to extract the PID and use it as a variable in a task Stop-Process -ID PID
maybe I'm missing something help
in the end it should look something like this
Get-Process |
Where {$_.mainWindowTItle -eq "RASP transfer.xlsx - Excel"} |
Write-Output $pid=id |
Stop-Process -ID $PID
edit: 6/6/19
Currently I'm able to identify the PID of the specific workbook
by running
Get-Process |where {$_.mainWindowTItle -eq "RASP transfer.xlsx - Excel"} |Write-Output
which I can identify here in the taskmanager
However when the files are run with scripts the excel file is hidden and can't be searched with the windows title....
One of the nice features of Powershell is that you can pass an object on the pipeline. You don't even need the Process ID:
Get-Process | Where {$_.mainWindowTItle -eq "RASP transfer.xlsx - Excel"} | Stop-Process
However it's important to know what type of object you're passing along at each stage in the pipeline. The input to Stop-Process in the line above is a Process object, that has an ID property.
If you wanted to get the Process ID you could assign it to a variable.
$PID = (Get-Process | Where {$_.mainWindowTItle -eq "RASP transfer.xlsx - Excel"}).ID
... which will usually only get a single Process ID because Excel doesn't allow opening the same spreadsheet in two windows. But you can't rely on the window title to be unique because it would be the same if you opened two copies of the same spreadsheet from different folders.
If you wanted to close all Excel windows, this code would get all the Excel windows' processes into an Object[] array:
$ExcelProcs = Get-Process | Where {$_.mainWindowTItle -like "* - Excel"}
Stop-Process is smart enough to use the array of [Object] as input and stop all their processes:
$ExcelProcs | Stop-Process
Why not just re- enable the visibility before you check for the open sheet with:
$x1.Visible = $true
$PID = (Get-Process | Where {$_.mainWindowTItle -eq "RASP transfer.xlsx - Excel"}).ID

Adding user input to a Select-String command

I'm currently trying to create a select-string command to determine if users in my company are still showing up in certain directories. My current command is a follows:
Select-String -path "C:\filepath\*.csv" -Pattern "<string>" |
Format-Table -Property LineNumber,Line,Path -Wrap |
Out-File "C:\outfile.txt"
The current command means I'd need to alter the content of the Pattern parameter each time before running the script. What I'd like to do instead is to request my input for the string when I run the script. Is there a while loop I can add to this command to request user input?
Thanks
Add this to the head of your script:
Param(
[Parameter(Mandatory = $True)]
[System.String]
$Pattern
)
Select-String -Path C:\filepath\*.csv -Pattern $Pattern |
Tee-Object -FilePath C:\outfile.txt |
Format-Table -Property LineNumber, Line, Path -Wrap
When you call the script, the attribute makes sure you give an argument to -Pattern. You can add further attributes if you want this to not be null ([ValidateNotNullOrEmpty()]), etc.
As an aside, never pipe from formatters.

Powershell Find and Replace Loop, OutOfMemoryException

I have a working powershell script to find and and replace a few different strings with a new string in thousands of files, without changing the modified date on the files. In any given file there could be hundreds of instances of said strings to replace. The files themselves aren't very large and probably range from 1-50MB (a quick glance at the directory I am testing with shows the largest as ~33MB).
I'm running the script inside a Server 2012 R2 VM with 4 vCPUs and 4GB of RAM. I have set the MaxMemoryPerShellMB value for Powershell to 3GB. As mentioned previously, the script works, but after 2-4 hours powershell will start throwing OutOfMemoryExceptions and crash. The script is 'V2 friendly' and I haven't adopted it to V3+ but I doubt that matters too much.
My question is whether or not the script can be improved to prevent/eliminate the memory exceptions I am running into at the moment. I don't mind if it runs slower, as long as it can get the job done without having to check back every couple of hours and restart it.
$i=0
$all = Get-ChildItem -Recurse -Include *.txt
$scriptfiles = Select-String -Pattern string1,string2,string3 $all
$output = "C:\Temp\scriptoutput.txt"
foreach ($file in $scriptFiles)
{
$filecreate=(Get-ChildItem $file.Path).creationtime
$fileaccess=(Get-ChildItem $file.Path).lastaccesstime
$filewrite=(Get-ChildItem $file.Path).lastwritetime
"$file.Path,Created: $filecreate,Accessed: $fileaccess,Modified: $filewrite" | out-file -FilePath $output -Append
(Get-Content $file.Path) | ForEach-Object {$_ -replace "string1", "newstring" `
-replace "string2", "newstring" `
-replace "string3", "newstring"
} | Set-Content $file.Path
(Get-ChildItem $file.Path).creationtime=$filecreate
(Get-ChildItem $file.Path).lastaccesstime=$fileaccess
(Get-ChildItem $file.Path).lastwritetime=$filewrite
$filecreate=(Get-ChildItem $file.Path).creationtime
$fileaccess=(Get-ChildItem $file.Path).lastaccesstime
$filewrite=(Get-ChildItem $file.Path).lastwritetime
"$file.Path,UPDATED Created: $filecreate,UPDATED Accessed: $fileaccess,UPDATED Modified: $filewrite" | out-file -FilePath $output -Append
$i++}
Any comments, criticisms, and suggestions welcomed.
Thanks
Biggest issue I can see is that you are repeatedly getting the file for every property you are querying. Replace that with one call per loop pass and save it to be used during the pass. Also Out-File is one of the slower methods of outputting data to file.
$output = "C:\Temp\scriptoutput.txt"
$scriptfiles = Get-ChildItem -Recurse -Include *.txt |
Select-String -Pattern string1,string2,string3 |
Select-Object -ExpandProperty Path
$scriptfiles | ForEach-Object{
$file = Get-Item $_
# Save currrent file times
$filecreate=$file.creationtime
$fileaccess=$file.lastaccesstime
$filewrite=$file.lastwritetime
"$file,Created: $filecreate,Accessed: $fileaccess,Modified: $filewrite"
# Update content.
(Get-Content $file) -replace "string1", "newstring" `
-replace "string2", "newstring" `
-replace "string3", "newstring" | Set-Content $file
# Write all the original times back.
$file.creationtime=$filecreate
$file.lastaccesstime=$fileaccess
$file.lastwritetime=$filewrite
# Verify the changes... Should not be required but it is what you were doing.
$filecreate=$file.creationtime
$fileaccess=$file.lastaccesstime
$filewrite=$file.lastwritetime
"$file,UPDATED Created: $filecreate,UPDATED Accessed: $fileaccess,UPDATED Modified: $filewrite"
} | Set-Content $output
Not tested but should be fine.
Depending on what you replacements are actually like you could probably save some time there as well. Test first before running in production obviously.
I remove the counter you had since it appeared nowhere in the code.
Your logging could easily be csv based since you have all the object ready to go but I just want to be sure we are one the right track before we go to far.