Where-Object clause fails when run as a workflow in PowerShell - powershell

I'm trying to extract static routes from all AD computer using the Get-NetRoute cmdlet. It works well when run outside a workflow, but as soon as I try to run the same code within a workflow it fails with the following exception:
System.Management.Automation.ParameterBindingException: Parameter set cannot be resolved using the specified named parameters.
I could trace this back to the Where-Object "?" filter. The "? {}" section in commented line of code makes it fail. Without that filter it works perfectly.
Code is here:
cd C:\Users\Public\Documents
workflow Get-StaticRoutes {
# Change the error action to 'stop' to get try/catch working
# Get-NetRoute -Protocol NetMgmt -AddressFamily IPv4 | ? { $_.DestinationPrefix -ne "0.0.0.0/0" } | % {
Get-NetRoute -Protocol NetMgmt -AddressFamily IPv4 | % {
[PSCustomObject] #{
ComputerName = $env:COMPUTERNAME
InterfaceName = $_.InterfaceAlias
InterfaceIndex = $_.InterfaceIndex
DestinationPrefix = $_.DestinationPrefix
NextHop = $_.NextHop
Comment = ""
}
}
}
# Get all computers from AD
$computers = Get-ADComputer -Filter * | % { $_.Name }
# Retrieve IP config
Get-StaticRoutes -PSComputerName $computers | Export-Csv ".\StaticRoutes.csv" -NoTypeInformation
I could filter after the workflow to fix this problem, but I would like to understand what I do wrong as this ParameterBindingException is rather obscure.
Thanks,
Olivier.

To run commands or expressions in a workflow that are valid in Windows PowerShell, but not valid in workflows, run the commands in an inlineScript activity. You can use also an inlineScript activity to run Windows PowerShell scripts (.ps1 files) in a workflow.
Try this ( not tested )
workflow Get-StaticRoutes
{
inlinescript { Get-NetRoute -Protocol NetMgmt -AddressFamily IPv4 |
? { $_.DestinationPrefix -ne "0.0.0.0/0" } |
% {
[PSCustomObject] #{
ComputerName = $env:COMPUTERNAME
InterfaceName = $_.InterfaceAlias
InterfaceIndex = $_.InterfaceIndex
DestinationPrefix = $_.DestinationPrefix
NextHop = $_.NextHop
Comment = ""
}
}
}
}
side note:
$env:computername outside the inlinescipt activity resolve to local
computer name. Inside the inlinescipt activity resolve to the remote
computer name.
the object returned by a workflow is a serialized object and not the object created in the inlinescript activity or workflow process ( this means, in simple terms, that you can't have object method but only the properties of the object )

Remember that with Workflows you need to use named parameters.
When you run something like this:
$a = $b | ?{$_.Name -eq "John"}
You're really running this:
$a = $b | where-object -FilterScript {$_.Name -eq "John"}
The latter works fine in a workflow without using those pesky inlinescripts.

Related

Powershell - Process List with arguments

I am trying to find a way to list processes with;
PID
Process Name
CPU Usage
Execution Path
Port Number (TCP and UDP)
Description
and export it in a csv file.
Is it possible? If not, can this list be configured to search the process name as "like process_name" ?
For example, list multiple process names (as I specify) with other arguments.
So far, I've found this one but it doesn't include the port numbers;
Get-Process | select id, processname,cpu,path,description | where {$_.path -like "*postgre*"} | Export-Csv -Path C:\temp\process.csv -Delimiter ',' -NoTypeInformation
Thanks.
Here's a new function I whipped together called Get-ProcessPlus. It supports calling with process name(s) or process id(s) or without any parameters (will return all processes).
Example of output:
ProcessName : NVIDIA Web Helper
Id : 10184
Description : NVIDIA Web Helper Service
Path : C:\Program Files (x86)\NVIDIA Corporation\NvNode\NVIDIA Web Helper.exe
CPU usage (s) : 0,59375
TCP Addresses : 127.0.0.1
TCP Ports : 13549
UDP Addresses : 127.0.0.1
UDP Ports : 10010
Either run this code in ISE or VSCode, save it and dot source it (eg. . c:\path\to\Get-ProcessPlus.ps1 or maybe add it to your profile.
Then just call it with Get-ProcessPlus. Using a parameter name is optional, just providing one or more process ids or names will work, eg. Get-ProcessPlus chrome,firefox or Get-ProcessPlus 1044,894,432.
Finally, here's the code:
function Get-ProcessPlus {
[CmdletBinding(DefaultParameterSetName = 'Default')]
param (
[Parameter(ParameterSetName='ProcessName',Position = 0)]
[string[]]
$Name,
[Parameter(ParameterSetName='PID',Position = 0)]
[int[]]
$Id
)
# Check which parameter set is in use and get our processes
switch ($PSCmdlet.ParameterSetName) {
'ProcessName' {
$AllProcesses = Get-Process -Name $Name
break
}
'PID' {
$AllProcesses = Get-Process -Id $Id
break
}
default { $AllProcesses = Get-Process }
}
foreach ($Process in $AllProcesses) {
# Retrieve TCP and UDP Connection information for the current process (if any)
$UDPConnections = Get-NetUDPEndpoint -OwningProcess $Process.Id -ErrorAction Ignore |
Select-Object LocalAddress,LocalPort
$TCPConnections = Get-NetTCPConnection -OwningProcess $Process.Id -State Listen -ErrorAction Ignore |
Select-Object LocalAddress,LocalPort
$TCPPorts = $TCPConnections.LocalPort | Where-Object { $null -ne $_} | Select-Object -Unique
$UDPPorts = $UDPConnections.LocalPort | Where-Object { $null -ne $_} | Select-Object -Unique
$TCPAddresses = $TCPConnections.LocalAddress | Select-Object -Unique
$UDPAddresses = $UDPConnections.LocalAddress | Select-Object -Unique
# Collect and output all information about the current process
[PSCustomObject] #{
'ProcessName' = $Process.ProcessName
'Id' = $Process.Id
'Description' = $Process.Description
'Path' = $Process.Path
'CPU usage (s)' = $Process.CPU
'TCP Addresses' = $TCPAddresses
'TCP Ports' = $TCPPorts
'UDP Addresses' = $UDPAddresses
'UDP Ports' = $UDPPorts
}
}
}

PowerShell :: How to filter worker processes list by User Name

Basically as per screen-shot there are multiple worker processes are running on machine in IIS but we need w3wp which is running under Sitecore User Username.
We tried below PS script but getting blank value in User Name column
$processlist = get-process | where {$_.cpu -gt 5 -and $_.Name -eq 'w3wp'} |select Name, #{l="User name";e={$_.getowner().user}} | ft -AutoSize
foreach($proc in $processlist){
if($proc -eq "Sitecore User" ){
C:\Users\Administrator\Documents\someexe.exe $proc.Id "C:\Users\Administrator\Documents\output.dmp"
}
}
and finally we need to perform some action on process Id.
I suggest the following PoSh-Script that should give you all the necessary info and more:
# Ensure to import the WebAdministration module
Import-Module WebAdministration
# Declare the variables
$server = "localhost"
$search = "*"
$wmiQuery=[wmisearcher]"SELECT * FROM __Namespace where NAME like 'WebAdministration' or NAME like 'MicrosoftIISv2'"
$wmiQuery.Scope.Path = "\\" + $server + "\root"
$WebNamespace = $wmiQuery.Get()
# Checking if the the server has IIS installed
if($WebNamespace -like '*WebAdministration*')
{
"IIS found on $server"
$WPlist=Get-WmiObject -NameSpace 'root\WebAdministration' -class 'WorkerProcess' -ComputerName 'LocalHost'
# Loop through the list of active IIS Worker Processes w3wp.exe and fetch the PID, AppPool Name and the startTime
forEach ($WP in $WPlist)
{
if ($WP.apppoolname -like$search)
{
write-host "Found:""PID:"$WP.processid "AppPool_Name:"$WP.apppoolname
(get-process -ID $WP.processid|select starttime)
}
}
}
Else
{
write-host"WARNING: IIS not detected."
}
Ref: https://blogs.msdn.microsoft.com/webtopics/2015/11/28/query-the-active-worker-process-information-in-iis-7-x-using-powershell/

Property has a value but cannot select it

I have a function that checks the registry for an uninstall key called Get-InstalledApps
Function Get-InstalledApps {
param (
[Parameter(ValueFromPipeline=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME,
[string]$NameRegex = ''
)
foreach ($comp in $ComputerName) {
$keys = '','\Wow6432Node'
foreach ($key in $keys) {
try {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $comp)
$apps = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall").GetSubKeyNames()
} catch {
continue
}
foreach ($app in $apps) {
$program = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall\$app")
$name = $program.GetValue('DisplayName')
if ($name -and $name -match $NameRegex) {
[pscustomobject]#{
ComputerName = $comp
DisplayName = $name
DisplayVersion = $program.GetValue('DisplayVersion')
Publisher = $program.GetValue('Publisher')
InstallDate = $program.GetValue('InstallDate')
UninstallString = $program.GetValue('UninstallString')
Bits = $(if ($key -eq '\Wow6432Node') {'64'} else {'32'})
Path = $program.name
}
}
}
}
}
}
and then I grab the DisplayName/Version for what I need. My current problem is that it only seems to work on certain machines. Example:
Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"}
Name Value
---- -----
InstallDate
ComputerName Computer
DisplayName Citrix Receiver 4.7
Bits 64
UninstallString C:\ProgramData\Citrix\Citrix Receiver 4.7\TrolleyExpress.exe /uninstall /cleanup
Path HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\CitrixOnlinePluginPackWeb
Publisher Citrix Systems, Inc.
DisplayVersion 14.7.0.13011
So this is great, I get what I want. Now I normally just pipe in | Select-Object Displayname -ExpandProperty Displayname and it would return "Citrix Receiver 4.7" just like I want. My problem is that on certain machines I'm getting this:
Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"} | Select-Object DisplayName
DisplayName
-----------
And that's it. Why is there no value listed? If I try to expandproperty I get an error because it says nothing is there, but clearly there is something there or the Where-Object would not have found it in my search. Again, in a lot cases this code works just fine and I get the value I want but on a lot of machines I'm getting what you see above.
Edited in from comments:
I run this on a user's machine and I get the results I posted. If I run it on my machine I'll get the value "Citrix Receiver 4.7" every time. Also, on my machine I don't get the Name and Value columns. Only about 1/4 of the machines I ran this code on actually gave me the value I expected. Windows 7 vs Windows 10 thing?
It looks to me like your function returns a [hashtable], but you're using it like it's an object with properties.
That happens to work fine with Where-Object because the .Member syntax works for accessing [hashtable] values, but it's not going to work with Select-Object because it's operating on actual properties.
So what can you do?
If you want to keep it as a [hashtable], and insist on doing it in a pipeline, you can use ForEach-Object:
Get-InstalledApps |
Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
ForEach-Object -Process { $_.DisplayName }
or
Get-InstalledApps |
Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
ForEach-Object -MemberName Item -ArgumentList DisplayName
Another thing you can do is change your function to return an object.
This is really easy to do with a [hashtable]; so say your function is about to return $hash, instead return:
New-Object -TypeName PSObject -Property $hash
Now you can use the normal suite of cmdlets and have them work as expected.
Edit: after seeing your code, it looks like you are converting your hashtable to an object already, but your output says otherwise. It wouldn't display as Name and Value columns if that were the case, so I still think something is wrong and the output is a [hashtable].
Edit 2: with info from comments about the platform differences, this seems to be happening because the object conversion is being done with the [pscustomobject] type accelerator which was added in PowerShell v3. Since the problematic machine is running Windows 7, it may be running v2 (which is what Win 7 shipped with).
Recommendations:
Get rid of Windows 7.
If you can't do that, upgrade PowerShell (Windows Management Framework) on that machine.
Either way, use New-Object as posted above.

Functions tabular output changing on some remote computers

I have this function I'm using a foreach statement block to run against a number of machines:
function Get-InstalledApps ($appStr) {
$appWC = "*$appStr*"
if ([IntPtr]::Size -eq 4) {
$regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
}
else {
$regpath = #(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
}
$getapps = Get-ItemProperty $regpath | .{process{if($_.DisplayName -and $_.UninstallString) { $_ } }}
Foreach ($app in $getapps | where {$_.DisplayName -like $appWC}) {
[pscustomobject]#{Computer = ($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN)
AppName = ($app.displayname)
Publisher = ($app.Publisher)
DisplayVersion = ($app.DisplayVersion)
InstallDate = ($app.InstallDate)
UninstallString = ($App.UninstallString)}
}
}
Locally, it looks like this:
PS C:\windows\system32> Get-InstalledApps ibm | ft
Computer AppName Publisher DisplayVersion InstallDate UninstallString
-------- ------- --------- -------------- ----------- ---------------
Computer.domain.COM IBM Tivoli Storage Manager Client IBM 06.04.0001 20140807 MsiExec.exe /I{FF99015E-71B4-41AB-8985-67D99383A72A}
But when run remotely on some computers
(i.e:)
Invoke-Command -ComputerName $computer -ScriptBlock
${function:Get-InstalledApps} -ArgumentList $appStr
I get the above, however on others I get this:
Name Value
---- -----
UninstallString MsiExec.exe /I{68C09095-AC00-4541-B46B-0835F2BDB0CE}
Computer comp1.domain.com
Publisher IBM
InstallDate 20150122
DisplayVersion 07.01.0000
AppName IBM Tivoli Storage Manager Client
UninstallString MsiExec.exe /X{1316AC9A-7A5D-4866-B41F-4B3CF03CE52A}
Computer comp2.domain.com
Publisher IBM Corp.
InstallDate 20170226
DisplayVersion 9.2.7.53
AppName IBM BigFix Client
Without having a chance to verify PowerShell versions of some of the computers yet, I'm guessing the 2nd set of results may be as a result of being run against computers running < version 3.0.
Any way to force the output to display as a table (1st example output) on all computers?
I'm guessing the 2nd set of results may be as a result of being run against computers running < version 3.0.
If you are running that on systems that are not at least version 3 then your [pscustomobject] cast would fail since that was introduced in v3. I would have expected that to just trigger an error but instead it appears to be returning the hashtable. A compatible solution would be to use new-object instead.
New-Object -TypeName PSCustomObject -Property #{
Computer = ($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN)
AppName = ($app.displayname)
Publisher = ($app.Publisher)
DisplayVersion = ($app.DisplayVersion)
InstallDate = ($app.InstallDate)
UninstallString = ($App.UninstallString)
}
Thanks Matt.
That worked, which is my preferred method.
if the app wasn't installed or the host was offline, a couple of variations of IF statements didn't seem to pick up the output at another point in the script (only displayed if it was installed) and returned as a blank line, however this seemed to be picked up by the statement blocks:
function Get-InstalledApps ($appStr) {
$appWC = "*$appStr*"
if ([IntPtr]::Size -eq 4) {
$regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
}
else {
$regpath = #(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
}
$getapps = Get-ItemProperty $regpath | .{process{if($_.DisplayName -and $_.UninstallString) { $_ } }}
$getapps | where {$_.DisplayName -like $appWC} | Select #{n='Computer';e={$env:COMPUTERNAME + "." + $env:USERDNSDOMAIN}},Displayname,Publisher,DisplayVersion,InstallDate,UninstallString
}

PowerShell WMI query fails to return username in logon script

I'm trying to get the username of domain users in a PowerShell logon script. Any number of different users may log into the computers in question.
A local user account (let's call it 'syscheck') is configured on Win7/Win8 domain clients for the purpose of running a PS script (PS 2.0/3.0); the script resides locally and is launched by Task Scheduler on user logon. The script needs to obtain the username of the domain user that is logging in.
I've attempted to do this with WMI:
Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty UserName
but this does not return anything when the script runs.
If I try this:
$env:USERNAME
The username of the 'syscheck' local account is returned.
Is the domain username not yet available when the script is running on logon?
Perhaps there a way to do this with .NET? Other options?
***** UPDATE August 8 *****
I've tested with the solution provided (thanks Alexander!) but still can NOT retrieve the username of the logged-in user. I believe this is because, as mentioned above, this is a logon script launched by Task Scheduler. The principal for the Task that launches the script is a local account. For some reason, all methods of trying to get the domain username fail.
Here is latest attempt:
First, this is how I call the function:
$indx = 0
do {
$username = GetDomUser
if (($indx -eq 25) -or ($username.Length -ne 0)) {
Write-Output $username
Break
}
else {
Start-Sleep -Seconds 12
}
$indx++
}
while ($indx -lt 25) # 5 minutes is PLENTY of time for boot...
Now, here's the function:
Function GetDomUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"MYDOMAIN",Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } | Select-Object -ExpandProperty Antecedent)
Return ([regex]::Match([string]$antecedent[0],"$pattern(.*$)").Value).Split('=')[1] -replace '"', ""
}
Of course, this works perfectly from the console once the machine has booted.
Is it possible to refresh whatever store the Win32_LoggedOnUser Class gets its data from?
Other options?
Here are previous methods I've tried - all return the username of the principal of the Task that launches the script (or an empty string, which is what D returns).
$usernameA = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
$usernameB = $(whoami)
$usernameC = $($env:USERNAME)
$usernameD = $(Get-WmiObject Win32_ComputerSystem -ComputerName $compname | Select-Object -ExpandProperty UserName)
$usernameE = $([Environment]::UserName)
Here's what you could do to find out what's going on:
$iLOGON32_LOGON_INTERACTIVE = 2
$cLogonSessions = Get-WmiObject -Class "Win32_LogonSession" `
| Where-Object { $_.LogonType -eq $iLOGON32_LOGON_INTERACTIVE }
if ($cLogonSessions -ne $null) {
$cInteractiveLogons = #()
foreach ($oLogonSession in $cLogonSessions) {
$sWmiQuery = ('ASSOCIATORS OF {{Win32_LogonSession.LogonId="{0}"}} ' `
+ 'WHERE AssocClass=Win32_LoggedOnUser') -f $oLogonSession.LogonId
$cInteractiveLogons += Get-WMIObject -Query $sWmiQuery `
| Select-Object -ExpandProperty "Caption"
}
} else {
$ex = New-Object -TypeName System.NullReferenceException(`
'$cInteractiveLogons is null.')
throw $ex
}
$cInteractiveLogons | Select-Object -Unique
When $cInterativeLogons is null exception is thrown, it means that no-one is logged on interactively (yet) in which case you can wait and re-check later.
Note that this code is not reliable because LOGON32_LOGON_INTERACTIVE wasn't limited to local console logons in XP and earlier versions.
As for actual solution, I'd recommend using some kind of explicit notifications. You could for example make use of events. Subscribe for an event and then emit the event from the user's regular logon script.
The problem was not with the WMI code but rather the state of the machine it was being run on. It turns out that when users are VPNed into their machines (almost always thanks to a VPN client's automated reconnect feature), or have some third-party utility installed (e.g. certain cloud backup services), there are multiple Logons and "the" logged on user is ambiguous.
For now this is working pretty well:
Function GetDomainUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"' + $($env:USERDOMAIN) + '"' + ',Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } |
Select-Object -ExpandProperty Antecedent | Select-Object -Unique)
Return $(([regex]::Match([string]$antecedent,$($pattern + '(".+")')).Value).Split('=')[1] -replace '"','')
}
But I had to write addition code to work around cases when the LoggedOnUser cannot be discovered (multiple logons exist), or when no one is logged in.