I have the below bit of code, it is designed to simply ask for a Windows ProcessName, and then return a list of all instances of that process that are running.
$processName = Read-Host 'Please Enter the Process Name, as shown in Task Manager (not inc *32)'
if (!$processName)
{
"No Process Given"
}
else
{
"You Entered "+$processName
$filter = "name like '%"+$processName+"'"
$result = Get-WmiObject win32_process -Filter $filter | select CommandLine
$counter=1
foreach($process in $result )
{
write-host "$counter) $process"
$counter++
}
}
It works fine up until the point where it outputs the list.
If I do
echo $process
then I get what I am after e.g.
"C:\folder\AppName.exe"
"C:\folder\AppName.exe instance1"
If however I try to concatenate the $counter in front of it I get:
1) #{CommandLine="C:\folder\AppName.exe" }
2) #{CommandLine="C:\folder\AppName.exe instance1" }
I've tried write-host, write-output, echo, various combinations of "", +, but I can't get rid of the #{CommandLine= xxx } when I try to combine it with another variable
Is there a way to get what I am after? e.g.:
1) "C:\folder\AppName.exe"
2) "C:\folder\AppName.exe instance1"
try write-host "$counter) $($process.commandline)"
OR modify your selection :
$result = Get-WmiObject win32_process -Filter $filter | select -expandproperty CommandLine
explanation : without expandproperty you get a psobject with expandproperty you have a string
Related
Abstract
So I work for a company that has roughly 10k computer assets on my domain. My issue is the time it takes to query if a user exists on a computer to see if they've ever logged into said computer. We need this functionality for audits in case they've done something they shouldn't have.
I have two methods in mind I've researched to complete this task, and a third alternative solution I have not thought of;
-Method A: Querying every computer for the "C:\Users<USER>" to see if LocalPath exists
-Method B: Checking every computer registry for the "HKU:<SID>" to see if the SID exists
-Method C: You are all smarter than me and have a better way? XD
Method A Function
$AllCompFound = #()
$AllADComputer = Get-ADComputer -Properties Name -SearchBase "WhatsItToYa" -filter 'Name -like "*"' | Select-Object Name
ForEach($Computer in $AllADComputers) {
$CName = $Computer.Name
if (Get-CimInstance -ComputerName "$CName" -ClassName Win32_Profile | ? {"C:\Users\'$EDIPI'" -contains $_.LocalPath}) {
$AllCompFound += $CName
} else {
#DOOTHERSTUFF
}
}
NOTE: I have another function that prompts me to enter a username to check for. Where I work they are numbers so case sensitivity is not an issue. My issue with this function is I believe it is the 'if' statement returns true every time because it ran rather than because it matched the username.
Method B Function
$AllCompFound = #()
$AllADComputer = Get-ADComputer -Properties Name -SearchBase "WhatsItToYa" -filter 'Name -like "*"' | Select-Object Name
$hive = [Microsoft:Win32.RegistryHive]::Users
ForEach($Computer in $AllADComputers) {
try {
$base = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($hive, $Computer.Name)
$key = &base.OpenSubKey($strSID)
if ($!key) {
#DOSTUFF
} else {
$AllCompFound += $Computer.Name
#DOOTHERSTUFF
}
} catch {
#IDONTTHROWBECAUSEIWANTITTOCONTINUE
} finally {
if($key) {
$key.Close()
}
if ($base) {
$base.Close()
}
}
}
NOTE: I have another function that converts the username into a SID prior to this function. It works.
Where my eyes start to glaze over is using Invoke-Command and actually return a value back, and whether or not to run all of these queries as their own PS-Session or not. My Method A returns false positives and my Method B seems to hang up on some computers.
Neither of these methods are really fast enough to get through 10k results, I've been using smaller pools of computers in order to get test these results when requested. I'm by no means an expert, but I think I have a good understanding, so any help is appreciated!
First, use WMI Win32_UserProfile, not C:\Users or registry.
Second, use reports from pc to some database, not from server to pc. This is much better usually.
About GPO: If you get access, you can Add\Remove scheduled task for such reports through GPP (not GPO) from time to time.
Third: Use PoshRSJob to make parallel queries.
Get-WmiObject -Class 'Win32_USerProfile' |
Select #(
'SID',
#{
Name = 'LastUseTime';
Expression = {$_.ConvertToDateTime($_.LastUseTime)}}
#{
Name = 'NTAccount';
Expression = { [System.Security.Principal.SecurityIdentifier]::new($_.SID).Translate([System.Security.Principal.NTAccount])}}
)
Be careful with translating to NTAccount: if SID does not translates, it will cause error, so, maybe, it's better not to collect NTAccount from user space.
If you have no other variants, parallel jobs using PoshRSJob
Example for paralleling ( maybe there are some typos )
$ToDo = [System.Collections.Concurrent.ConcurrentQueue[string]]::new() # This is Queue (list) of computers that SHOULD be processed
<# Some loop through your computers #>
<#...#> $ToDo.Enqueue($computerName)
<#LoopEnd#>
$result = [System.Collections.Concurrent.ConcurrentBag[Object]]::new() # This is Bag (list) of processing results
# This function has ComputerName on input, and outputs some single value (object) as a result of processing this computer
Function Get-MySpecialComputerStats
{
Param(
[String]$ComputerName
)
<#Some magic#>
# Here we make KSCustomObject form Hashtable. This is result object
return [PSCustomObject]#{
ComputerName = $ComputerName;
Result = 'OK'
SomeAdditionalInfo1 = 'whateverYouWant'
SomeAdditionalInfo2 = 42 # Because 42
}
}
# This is script that runs on background. It can not output anything.
# It takes 2 args: 1st is Input queue, 2nd is output queue
$JobScript = [scriptblock]{
$inQueue = [System.Collections.Concurrent.ConcurrentQueue[string]]$args[0]
$outBag = [System.Collections.Concurrent.ConcurrentBag[Object]]$args[1]
$compName = $null
# Logging inside, if you need it
$log = [System.Text.StringBuilder]::new()
# we work until inQueue is empty ( then TryDequeue will return false )
while($inQueue.TryDequeue([ref] $compName) -eq $true)
{
$r= $null
try
{
$r = Get-MySpecialComputerStats -ComputerName $compName -EA Stop
[void]$log.AppendLine("[_]: $($compName) : OK!")
[void]$outBag.Add($r) # We append result to outBag
}
catch
{
[void]$log.AppendLine("[E]: $($compName) : $($_.Exception.Message)")
}
}
# we return log.
return $log.ToString()
}
# Some progress counters
$i_max = $ToDo.Count
$i_cur = $i_max
# We start 20 jobs. Dont forget to say about our functions be available inside job
$jobs = #(1..20) <# Run 20 threads #> | % { Start-RSJob -ScriptBlock $JobScript -ArgumentList #($ToDo, $result) -FunctionsToImport 'Get-MySpecialComputerStats' }
# And once per 3 seconds we check, how much entries left in Queue ($todo)
while ($i_cur -gt 0)
{
Write-Progress -Activity 'Working' -Status "$($i_cur) left of $($i_max) computers" -PercentComplete (100 - ($i_cur / $i_max * 100))
Start-Sleep -Seconds 3
$i_cur = $ToDo.Count
}
# When there is zero, we shall wait for jobs to complete last items and return logs, and we collect logs
$logs = $jobs | % { Wait-RSJob -Job $_ } | % { Receive-RSJob -Job $_ }
# Logs is LOGS, not result
# Result is in the result variable.
$result | Export-Clixml -Path 'P:/ath/to/file.clixml' # Exporting result to CliXML file, or whatever you want
Please be careful: there is no output inside $JobScript done, so it must be perfectly done, and function Get-MySpecialComputerStats must be tested on unusual ways to return value that can be interpreted.
I have a simple Powershell script that I want to accept string values via Pipeline/Argument or plain just run the script.
#myscript
[CmdletBinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName,ValueFromPipeline)][string[]]$dnshostname
)
Begin {
If(-Not $dnshostname){
Write-Host "I AM HERE"
$raw = Get-ADComputer -Filter * -Property *
$raw | ForEach {
If (Test-Connection -Delay 15 -ComputerName $_.IPv4Address -Count 1 -ErrorAction SilentlyContinue){
$dnshostname += $_.DNSHostname
}
}
}
}
Process {
...
...
}
When I run this script like this: "myserver" | ./myscript.ps1
I get the "I AM HERE" message which means the $dnshostname variable is empty, but wasn't it fed from the pipeline?
If I run the same script as: ./myscript.ps1 -dnshostname "myserver", I do not get the "I AM HERE" message which mean it correctly determined that the $dnshostname variable is not empty.
I am sure I am missing something very fundamental here. May i get some guidance pretty please as to why the value passed via the pipeline triggers my IF statement?
Thank you!
I am running the below command in one of my powershell function to get the list of services from remote computer
Get-WmiObject -Class Win32_Service -filter "name like '$envlist%'" -Impersonation 3 -Credential abcdomain\PXXXX -ComputerName $server | Format-List -Property Name
The above command gives me the below output and I want to store it in variable and list it in proper format
Name : ABC_xx02_TT_xcedf_1.0.00.0101
Name : ABC_xx02_TT_nghk_2.1.0.99999
Name : ABC_xx02_TT_nmk_3_1.0.3.7890
Name : ABC_xx02_TT_pnp_4.0.0.123
I am expecting the output in below way:(the below services could be more so need to use something like counter).Once I select any choice from below I want to store it in variable for e.g if I select "3" then it should store in variable $serivcename = ABC_LA02_TT_nmk_3_1.0.3.7890
1. Press 1 to select ABC_xx02_TT_xcedf_1.0.00.0101
2. Press 2 select ABC_xx02_TT_nghk_2.1.0.99999
3. Press 3 to select ABC_xx02_TT_nmk_3_1.0.3.7890
4. Press 4 to select ABC_xx02_TT_pnp_4.0.0.123
Please make a selection:
I have no idea of course what could be in '$envlist%', but apparently that gives you the result you need.
Right now, your code uses Format-List, but that does not display a usable console menu.
I would do it like this:
$collection = (Get-WmiObject -Class Win32_Service -Filter "name like '$envlist%'" -Impersonation 3 -Credential abcdomain\PXXXX -ComputerName $server).Name
while ($true) {
Clear-Host
for ($i = 1; $i -le $collection.Count; $i++) {
# build your menu
"{0}. Press {0} to select {1}" -f $i, $collection[$i - 1]
}
$answer = Read-Host "Please make a selection. Press Q to quit"
# test if the (string) answer is in range, or if the user wants to quit
# if so, break the while loop
if ($answer -match "[1-$($collection.Count)Q]") { break }
# when you reach this point, the user made an invalid choice
Write-Host "Invalid input, please try again" -ForegroundColor Red
Start-Sleep -Seconds 3
}
# do whatever you need with the selected option
if ($answer -ne 'Q') {
Write-Host "User selected '{0}'" -f $collection[[int]$answer - 1]
# DoStuff $collection[[int]$answer - 1]
}
Try this:
$i = 0
Get-WmiObject -Class Win32_Service -filter "name like '$envlist%'" -Impersonation 3 -Credential abcdomain\PXXXX -ComputerName $server | Foreach {
$i++
Write-host "$($i). Press $($i) to select $($_.name.split('=')[1])"
}
$input = read-host "Please make a selection"
Now selection will be stored in $input.
I have a problem with the Get-Process command in Powershell, when i use it inside a Job.
I would like to get a process by PID, so i am doing the below:
$MyProcess = Get-Process | Where-Object { $_.Id -eq $parentProcessID }
The above, when it is called as a command from a Powershell script, returns me the process expected.
If I use the exact same command inside a Start-Job{} block then it gives me null, even for a process that is running. For example:
Start-Job {
$parentProcessID = $args
$MyProcess = Get-Process | Where-Object { $_.Id -eq $parentProcessID }
if($MyProcess -eq $null)
{
echo "Nothing returned"
}
} -ArgumentList "$parentProcessID"
Is there anything i am missing here? Has anyone run into similar situation before?
Any insights appreciated.
Thanks.
$args is an array, if you still want to use make sure to pick its first element:
$parentProcessID = $args[0]
Also, Get-Process has an Id parameter, there's no need to use the Where-Object cmdlet:
Get-Process -Id $parentProcessID
Another avantage of the Id parameter is that it takes an array of Id's so it would have work if you passes to it the value of $args as is.
You can also use names parameters for the scriptblock instaed of using $args:
Start-Job {
param([int[]]$procid)
$MyProcess = Get-Process -Id $procid
(...)
Basically I'd like to use Powershell to dump a list of all available classes in the root\cimv2 namespace. I have a vbscript which accomplishes the task:
Set objWMIService = _
GetObject("winmgmts:{impersonationLevel=impersonate}root\cimv2")
Set colClasses = objWMIService.SubClassesOf
For Each objClass In colClasses
If Left(objClass.Path_.Class,6) = "Win32_" Then
WScript.Echo objClass.Path_.Class
End If
Next
I've been able to get powershell to retrieve the list, but I can't seem to figure out how to get it to Write-Host the names. This is where I'm at now:
$WMIService = Get-WmiObject -Namespace root\cimv2 -List
$aClasses = $WMIService.SubClassesOf
foreach ($Class in $aClasses) {
Write-Host $Class.Path_.Class
}
Powershell dumps a long list of nothing, so I know it's enumerated something. I've tried all sorts of $Class.x and haven't hit the magic one yet. Does anyone know?
Something similar to:
$WMIService = Get-WmiObject -Namespace root\cimv2 -List
$WMIService | where { $_.Name -like "Win32_*" } | foreach { $_.Name }
Will get you what you're looking for, I think.