I'm programming a Powershell GUI script that shows a list of all the processes names running on the system using a combobox.
I would like to show process name followed by the proccess ID in parenthesis.
i.e.
Internet explorer (4)
Chrome (100)
Skype (33)
This is the code I've got. So far I can just show the process name, I don't know how to show the process id as well, any advise?
$MainForm_Load={
#TODO: Initialize Form Controls here
$processes = Get-Process
foreach ($process in $processes)
{
Load-ComboBox $combobox1 $process.ProcessName -Append
}
}
You can use Format String (-f)
$MainForm_Load={
#TODO: Initialize Form Controls here
$processes = Get-Process
foreach ($process in $processes)
{
$CurrentProcess = "{0} ({1})" -f $process.Name,$process.id
Load-ComboBox $combobox1 $CurrentProcess -Append
}
Regarding your Comment Question,
first it should be: get-process $combobox1.selecteditem.text but it still not going to work because for example the name "Chrome (100)" is not a valid process name, so you need to split it first to remove the process id (100),
you can try this
$ProcessName = ($combobox1.selecteditem.text) -replace "\(.*\)"
then use
$textbox1.text = (get-process $ProcessName) | Out-String
Simple solution:
get-process | %{$combobox.items.add("$($_.name)($($_.id))")}
Related
Occasionally I forget to log off from a server or am disconnected through an error and I don't remember the name of the server. And my domain account starts getting periodically locked out, so I have to access logs on DC to find out which server(s) keep locking my account and log off from it/them. So I wanted to write to script in powershell that would log me off from all servers in a domain (with the exception of the server where I run the script on of course) without me needing to search which to log off from. This is what I have:
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
$SessionIDs | ForEach-Object
{
Write-Host "Logging off session [$($_)]..."
logoff $_
}
}
}
foreach ($Server in $Servers)
{
if ($Server -isnot $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock {$ScriptBlock}
}
}
But when I launch the script, nothing happens. The script doesn't return any errors but doesn't log me off from any server, nor does it write any of the messages from Write-Host cmdlet, obviously. I noticed the $SessionIDs variable definition only returns ID of the first session. Usually this shouldn't be a problem, since it's unlikely I will have more than one session on a server, but I'd like to have this insurance. Can anyone tell me what's wrong in the script?
I notice a few things...
"First, I don't think quser | Where-Object {$_ -match $env:USERNAME} will ever return anything. The output of quser will not contain the hostname."
Try this for getting logon sessions:
$Sessions = (query user /server:$Env:ComputerName) -split "\n" -replace '\s\s+', ';' |
ConvertFrom-Csv -Delimiter ';'
Next, when you reference the $Server variable on the remote machine in your script block, it is out of scope. You would need to use $Using:Server in the script block.
Lastly, the -isnot operator doesn't compare value, it compares type. So in your last foreach, the if statement evaluates to "if type string is not type string" and will not run. Try -ne or -notlike instead.
Working with objects is much easier if you can just parse the output of QUser.exe. Given your scenario, here's my take on it:
$servers = (Get-ADComputer -Filter '*').Name.Where{$_ -ne $env:COMPUTERNAME}
foreach ($server in $servers)
{
if (-not ($quser = ((QUser.exe /server:$server) -replace '\s{20,39}',',,' -replace '\s{2,}',',' 2>&1) | Where-Object -FilterScript { $_ -match $env:USERNAME })) {
Continue
}
Write-Verbose -Message "$($quser.Count) session(s) found on $server." -Verbose
($quser.Trim() | ConvertFrom-Csv -Header 'USERNAME','SESSIONNAME','ID','STATE','IDLE TIME','LOGON TIME').foreach{
Write-Verbose -Message "Logging user [$($_.UserName)] off." -Verbose
LogOff.exe $_.ID /server:$server
}
}
Filtering should always happen before hand meaning, filter out your computer name on your first call to Get-ADComputer. Since you're using QUser.exe and LogOff.exe to begin with, I'd recommend the use of it all the way through since LogOff accepts an ID value that QUser outputs.
Next, placing the call to quser inside your if statement does two things in this case.
Filters for all users matching $ENV:UserName
Returns $true if anything is found, and $false if not found.
So, switching the results using -not will turn $false into $true allowing the execution of the code block which will just continue to the next server.
This in turn doesn't bother with the rest of the code and continues onto the next computer if no matching names were found.
The use of $quser inside the if statement is so you can save the results to it if more than one name is found; (..) allows this as it turns the variable assignment into an expression having the output pass through onto the pipeline where it is either empty, or not.
Finally, referencing the $quser variable we can convert the strings into objects piping to ConvertFrom-Csv. Only step left to do is iterate through each row and passing it over to LogOff to perform the actual logoff.
If you've noticed, the headers are manually-specified because it is filtered out by the Where-Object cmdlet. This is a better approach seeing as there could be "more than one" RDP Session, now you're just left with those sessions matching the name which can be saved to $quser, so no extra filtering is needed down the line.
So I modified the script this way and it works, sort of. It logs off account from servers, which is the main goal. There are still some glitches, like the message it sends from the first Write-Host doesn't give server's name, the message from second one gives a different value than it should (it gives [1] value after -split instead of [2] for some reason; but those are not really that important things, even though I will try to make at least the first message right) and $SessionIDs still gives only the first value, but usually you shouldn't have more than one RDP session per server. I've seen more sessions of one user, but that is very rare. But I'd also like to fix this if possible. Nevertheless, the script basically does the most important thing. But if someone has a suggestion how to fix the glitches I mentioned I would be grateful.
$ErrorActionPreference = "Silentlycontinue"
$Servers = (Get-ADComputer -Filter *).Name
$ScriptBlock = {
$Sessions = quser | ?{$_ -match $env:USERNAME}
if (($Sessions).Count -ge 1)
{
$SessionIDs = , ($Sessions -split ' +')[2]
Write-Host "Found $(($SessionIDs).Count) user login(s) on $Server."
Foreach ($SessionID in $SessionIDs)
{
Write-Host "Logging off session $SessionID..."
logoff $SessionID
}
}
}
foreach ($Server in $Servers)
{
if ($Server -ne $env:COMPUTERNAME)
{
Invoke-Command -ComputerName $Server -ScriptBlock $ScriptBlock
}
}
I am trying to create a GUI for my script that will go and delete certain files that match a certain name that is older than a set time period in a directory and show a what if before deleting. Everything was going well until I tried to put the output into a Textbox. The output displays in the console fine but won't display in the textbox. I have narrowed it down to the command I am running, as if I simply remove it and run 'ping google.com' it outputs fine. Please find my code below:
$scanbutton.Location = '380,84'
$scanbutton.text = 'Scan Directory'
$scanbutton.height = 25
$scanbutton.Width = 100
$scanbutton.Add_Click({
$result.Text = get-childitem $folderBrowser.SelectedPath -include "cat*.png" -force -recurse | where-object { (-not $_.PSIsContainer) -and ($_.LastWriteTime -lt (get-date).AddDays(-0)) } | remove-item -whatif
#$result.Text = ping google.com
$Form.Controls.Add($result)
})
Anyone have any ideas why this is? I am still very new to all this so please be nice. Also how can I get the output to follow new lines like in the console? At the moment it just has it as one long string (when I do ping). Please let me know if you need anything else from me.
Thank you in advance.
IC
As commented, the Remove-Item cmdlet does not return anything you can capture as text in a textbox. Neither does the -WhatIf switch, which like Write-Host is designed to not return anything, but write directly onto the console.
In your case, you can create and write your own info in the TextBox. Something like:
$resultBox = [System.Windows.Forms.TextBox]::new()
# do the Location, Size and whatever it takes here
# make the textbox accept multiple lines of text
$resultBox.Multiline = $true
# add the control to the form after you have created it
# NOT inside the $scanbutton.Add_Click() event handler
$Form.Controls.Add($resultBox)
$scanbutton.Add_Click({
# get a list of files to remove (just the FullNames)
$filesToRemove = Get-ChildItem $folderBrowser.SelectedPath -Filter "cat*.png" -File -Force -Recurse |
Where-Object { ($_.LastWriteTime -lt (Get-Date).AddDays(-60).Date) } |
Select-Object -ExpandProperty FullName
# write the file FullNames in the textbox
$resultBox.Text = "Removing files:`r`n{0}" -f ($filesToRemove -join [environment]::NewLine)
$filesToRemove | Remove-Item -WhatIf
})
So I've successfully been able to retrieve a list of all the installed programs on my computer and have been able to store them into an array with the following code:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Format-Table –AutoSize
Now what I am trying to do is output a list of the only the names of the programs, which I have already done, but allow the user to type in the name of the program they'd like to view more information about and have some command go through, find the program, and output the properties for ONLY that program. Any tips?
You are not really specific what you are looking for but this would satisfy what little you provided. Display the names to the user. Continue prompting until they do not enter anything. For every match we display the pertinent results to the user and continue the prompt.
# Gather information
$productDetails = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate
# Display teaser information to the user
$productDetails | Select-Object DisplayName
do {
Write-Host "--McPrompter 5000--" -ForegroundColor Green
$response = Read-Host "Type a partial software title or <Enter> to quit"
# If there is text lets see if there is a match
If($response){
$results = $productDetails | Where-Object{$_.DisplayName -like "*$response*"}
If($results){
$results | Format-Table -AutoSize
} Else {
Write-Host "No match for $response. Please try again." -ForegroundColor Red
}
}
} until (!$response)
Note about that key
Understand that you will need to check the syswow64 key if the system is x64 to get the complete list. You should be able to find more information about that here or on the Google.
You can basically try simple things, by displaying the list of Softwares and have your logic after the selection.
$Softwares = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate
$Choice = #{}
$Number = 1
foreach ($Software in $Softwares.DisplayName)
{
$Choice.add($Number,$Software)
$Number = $Number + 1
}
$Choice | Format-Table
[Int]$MenuChoice = read-host "Please enter your choice"
Switch($MenuChoice)
{
1{
Write-Host "Selected Software is" $Choice.get_item($MenuChoice);
#Your Logic here
}
2{
#Your Logic here
}
default{"please select a valid Software"}
}
Hope this helps!!
How would I pass all processes with Get-Process matching a certain process name to another PowerShell script one by one ?
pseudocode
for each process in matchingprocesses:
myScript(process)
Planned usage Set-WorkingSetToMin script: https://www.powershellgallery.com/packages/PoshInternals/1.0/Content/Set-WorkingSetToMin.ps1
This works great as there is only one notepad++ process:
get-process notepad++ | Set-WorkingSetToMin
However for VS Code this only only gets the first code process and ignores the rest:
get-process code | Set-WorkingSetToMin
How do I pipe each process matching a certain name to powershell script ?
Alternative would be to modify PoshInternals script to accept multiple processes:
# Dont run Set-WorkingSet on sqlservr.exe, store.exe and similar processes
# Todo: Check process name and filter
# Example - get-process notepad | Set-WorkingSetToMin
Function Set-WorkingSetToMin {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline=$True, Mandatory=$true)]
[System.Diagnostics.Process] $Process
)
if ($Process -ne $Null)
{
$handle = $Process.Handle
$from = ($process.WorkingSet/1MB)
$to = [PoshInternals.Kernel32]::SetProcessWorkingSetSize($handle,-1,-1) | Out-Null
Write-Output "Trimming Working Set Values from: $from"
} #End of If
} # End of Function
ANSWER in one line without extra variables:
foreach ($process in Get-Process myprocessname) { Set-WorkingSetToMin ($process) }
You could add a where clause to pull out just the processes you want, then pipe that set to Set-WorkingSetToMin. An example is below, but adjust as needed to pull exactly what you're looking for.
get-process | where {$_.ProcessName -like "*code*"} | Set-WorkingSetToMin
Update:
I see what you're saying, the issue is not with the set of processes being sent, but how they're handled once they're there. To get around that, you could set a variable equal to the process set, then loop through them, calling the cmdlet each time. Something like this:
$processes = get-process | where {$_.ProcessName -like "code"} | Set-WorkingSetToMin
foreach ($process in $processes)
{
Set-WorkingSetToMin ($process)
}
ANSWER thanks to help from Landon: foreach ($process in Get-Process myprocessname) { Set-WorkingSetToMin ($process) }
This thread got me started very well, but now I need more help
I am trying to loop through my serverlist.txt file, and pass the results of Get-EventLog to Out-GridView and then on to a .csv file. I have this working, but I have to select all the records in the GridView window then click OK for each server.
So, I have the idea that I want to create a $sys variable outside the loop, go in, append the results to that variable for each server, and then exit the loop and pass $sys over to Grid-view.
My confusion comes regardinf variable declaration, type, appending and placement in the code...
I'm just learning PS now, so this may be a little basic for you :)
this code works...need to add in the variable idea in the right places:
#Drop the existing files
Remove-Item C:\system.csv
# SERVER LIST PROPERTIES
# Get computer list to check disk space. This is just a plain text file with the servers listed out.
$computers = Get-Content "C:\ServerList.txt";
#Declare $sys here ??
# QUERY COMPUTER SYSTEM EVENT LOG
foreach($computer in $computers)
{
if(Test-Connection $computer -Quiet -Count 1)
{
Try {
# $sys =
Get-EventLog -ComputerName $computer -LogName System -EntryType "Error","Warning" -After (Get-Date).Adddays(-7) `
| Select-Object -Property machineName, EntryType, EventID, Source, TimeGenerated, Message `
| Out-GridView -PassThru | Export-Csv C:\System.csv -NoTypeInformation -Append;
}
Catch
{
Write-Verbose "Error $($error[0]) encountered when attempting to get events from $computer"
}
}
else {
Write-Verbose "Failed to connect to $computer"
}
}
# $sys | Out-GridView....etc.
Thanks!
Kevin3NF
Just to close this out, I used suggestions from mutiple comments:
$sys = #() (outside the loop)
$sys += Get-EventLog (inside the loop)
$sys | Export-Csv (after the loop to send to .csv)
I even blogged the whole thing, including all the various iterations of learning I went through:
http://dallasdbas.com/getting-to-know-powershell-from-an-old-dba/
Thanks to all that helped. This gave me a framework I will continue to use on these servers as the needs arise.
Kevin3NF