What I'm trying to do
I have a configurable Powershell 5.1 script with the following variable:
[bool]$SourceFilter
Based on the value of this boolean, I may or may not trigger a Where-Object clause in the middle of a pipeline, which is filtering a large and complex Array of objects:
$objectArray <# | Where-Object {$_.Attributes.Value -NotLlike "*this*"} #> | Sort-Object -Property {$_.Attributes.Name}
How do I encode the Where-Object clause to only trigger if $SourceFilter = $true?
What I've tried
I've tried encoding the clause as a variable and then using Invoke-Expression to rationalise it into the pipeline, but can't seem to get this working:
$script = '| Where-Object {$_.Attributes.pointsource -NotLike "*AF*"}'
$output = if($SourceFilter)
{Invoke-Expression "$objectArray $script" | Sort-Object -Property {$_.Attributes.Name}}
else
{$objectArray | Sort-Object -Property {$_.Attributes.Name}}
This approach gives me an error which states that the $variable is not recognised as the name of a cmdlet, script or program.
You can have a scriptblock variable set to either your condition or true.
$sb1 = {$true}
$sb2 = {$_ -like 'a*'}
echo hi | where $sb1
hi
echo hi | where $sb2
Related
I would like to design my script with variables and I have the problem that as soon as I use the variable, my script no longer works.
In the script part, where I specify the PC name structure, I get the error that my variable is not defined.
In the script part where I want to design the replace with a variable, I get the error: No overload can be found for "replace" and the following number of arguments: "1".
The problem occurs once with the $PCSyntax variable and with the $replace variable.
If I run the affected parts of the script without the variables, then it works.
Script parts without the variables:
Invoke-Command -ComputerName $AD_Server_Name -Credential $Administrator -ScriptBlock {
$AD_Clients = Get-ADComputer -Filter 'Name -like "PC-*"' | select-object -Property Name| Export-Csv (Join-Path $using:Export_Pfad_Extern -ChildPath "Active_Directory_Clients.csv") -NoTypeInformation
}
$WSUS_Clients = Get-WsusComputer -NameIncludes $using:NameSyntax |select FullDomainName| Out-String | % {$_.replace(".domain.local","")} | Out-File -FilePath \\$using:WSUS_Server_Name\C$\users\Administrator\WSUS_Client_list.csv
Script parts with the variables:
$global:PCSyntax = "PC-*"
Get-ADComputer -Filter 'Name -like $using:PCSyntax' | select-object -Property Name| Export-Csv (Join-Path $using:Export_Pfad_Extern -ChildPath "Active_Directory_Clients.csv") -NoTypeInformation
$global:Replace = ".domain.local",""
$WSUS_Clients = Get-WsusComputer -NameIncludes $using:NameSyntax |select FullDomainName| Out-String | % {$_.replace($using:Replace)} | Out-File -FilePath \\$using:WSUS_Server_Name\C$\users\Administrator\WSUS_Client_list.csv
I'm trying to use the output of Get-LoggedOnUser.ps1 (which outputs a PSCustomObject) to filter a list of usernames.
Goal: I want to remove from my list any currently logged on users.
However, I cannot figure what I'm doing isn't working, even debugging in the ISE. You can see I've even tried to make the object's field names match eachother.
$currentlyLoggedInUsernames = .\Get-LoggedOnUser.ps1
$currentlyLoggedInUsernames = .\Get-LoggedOnUser.ps1 | Select-Object UserName, LogonTime | sort UserName, LogonTime
$currentlyLoggedInUsers = $currentlyLoggedInUsernames | Select-Object UserName | Select-Object #{Name = "Name"; Expression = {$_.UserName}}
$useraccounts = Get-ChildItem -path \\$env:COMPUTERNAME\c$\users\ -Exclude 'public', 'Administrator' | Where-Object Name -notcontains $env:USERNAME
### Trouble below ###
$useraccounts = $useraccounts | Where-Object Name -NotContains $currentlyLoggedInUsers #BUT OTHER CURRENTLY LOGGED IN USERS REMAIN AFTER THIS LINE IS RUN.
$useraccounts #Why doesn't the above line actually remove the other logged in users from the list?
-notcontains is to be used when you want to check a collection against a value. In your case, you are checking a value against a collection (the opposite). You need to use -notiné
Example
# To check if a value is in a collection
"A" -in #('A','B','C')
# To check if a collection contains a value.
#('A','B','C') -contains 'A'
Here is your sample code, simplified and revised.
$currentlyLoggedInUsers = .\Get-LoggedOnUser.ps1
$useraccounts = Get-ChildItem -path \\$env:COMPUTERNAME\c$\users\ -Exclude 'public', 'Administrator' | Where-Object Name -notcontains $env:USERNAME
$NotLoggedInUsers = $useraccounts | Where-Object Name -notin $currentlyLoggedInUsers.Username
References
About_Comparison_Operators
I have this script that changes services per a csv file input
Import-CSV .\SSAS_services.csv |
ForEach-Object{
Get-Service $_.Service -ComputerName $_.Server -PipelineVariable svc|
Set-Service -Status $_.Task -StartupType $_.'Startup Type' -PassThru
} |
Select-Object MachineName, Name, Status, StartType, #{n='OldStatus';e={$svc.Status}}, #{n='OldStartType';e={$svc.StartType}} |
tee-object -FilePath '.\ChangeServices_LOG.txt' #-Append
Server,Service,Startup Type,Task
DCVPIM108,SQL Server Analysis Services (MSSQLSERVER),automatic,start
server2,"SQL Server Analysis Services (MSSQLSERVER), SQL Server Analysis Services (MSSQLSERVER) CEIP",Manual,stop
it works great, except for my -PipelineVariable svcis not working as intended. if a service was "stopped" and "Manual" before being changed to "running" and "automatic", it doesnt get the old values "stopped" and "Manual" for OldStatus and OldStartType
MachineName : DCVPIM108
Name : MSSQLServerOLAPService
Status : Running
StartType : Automatic
OldStatus : Running
OldStartType : Automatic
why is that?
The -PipelineVariable / -pv common parameter only works:
within a single pipeline.
in script blocks in later segments of the same pipeline.
Since you're using it in a pipeline that is nested inside the ForEach-Object script block, the commands in the outer pipeline cannot use it.
However, I suggest restructuring your command so that you don't need a pipeline variable for Get-Service anymore.
Instead,
-PipelineVariable $csvRow is used with Import-Csv, so that you can more easily refer to it even in nested pipelines (the alternative would be to define the variable explicitly at the start of the ForEach-Object script block as $csvRow = $_).
$svc is then declared as an -OutVariable, so that the original service state is captured before Set-Service is called to change it.
Getting a service, setting its startup type, and enriching the CSV-row object with additional information now all happen inside the ForEach-Object script block.
Import-CSV .\SSAS_services.csv -PipelineVariable csvRow | ForEach-Object {
Get-Service -Name $csvRow.Service -ComputerName $csvRow.Server -OutVariable svc |
Set-Service -Status $csvRow.Task -StartupType $csvRow.'Startup Type'
$csvRow | Select-Object MachineName, Name, Status, StartType,
#{n='OldStatus';e={$svc.Status}},
#{n='OldStartType';e={$svc.StartType}}
} | Tee-object -FilePath '.\ChangeServices_LOG.txt'
I guess what you want is to pass same object down the multiple pipes. I haven't use -PipeLineVariable much, but looks like it just creating a nicer alias for $_ . If you need to push something specific down the pipeline I guess you need to use write-ouput with custom object or hashtable. Below is a dummy sample, pushing down and modifying a hastable:
$services = "xagt" , "xbgm" , "XblGameSave"
$list = new-object System.Collections.ArrayList
$serv | foreach {
$svc = Get-Service $_ ; Write-Output #{Name = $svc.Name; Stat=$svc.Status}
} | foreach {$_.SomeNewItem = "new stuff"; $list.Add($_)}
But in your case one pipeline might be sufficient. Try something like that:
Import-CSV .\SSAS_services.csv | foreach {
$old = Get-Service $_.Service;
Set-Service -Name $_.Service -Status Running
$new = Get-Service $_.Service;
$data = $_.MachineName, $_.Service, $old.Status, $new.Status -join ","
Write-Host $data
$data >> Log.txt
}
I'm running this powershell command from a perl script and parsing the output.
powershell "Get-WinEvent -EA SilentlyContinue -FilterHashtable #{Logname='System';ID=7001,10,12,13,41,42,1129,5060,5719,6008,7045}| SELECT-Object ID,TimeCreated,MACHINENAME,MESSAGE|ConvertTo-Csv -NoTypeInformation | %{ $_ -replace """`r`n""",',' } | select -Skip 1"
Is there a way to change the format of the TimeGenerated field in the oputput to 2014-08-5 16:09:54 from 8/5/2014 4:09:54 PM
You can create values from hashtables at the Select portion of the pipe. This should do what you want:
powershell "Get-WinEvent -EA SilentlyContinue -FilterHashtable #{Logname='System';ID=7001,10,12,13,41,42,1129,5060,5719,6008,7045}| SELECT-Object ID,#{label='TimeCreated';expression={$_.TimeCreated.ToString("yyyy-M-d HH:mm:ss")}},MACHINENAME,MESSAGE|ConvertTo-Csv -NoTypeInformation | %{ $_ -replace """`r`n""",',' } | select -Skip 1"
I replaced TimeCreated with #{label=TimeCreated;expression={$_.TimeCreated.ToString("yyyy-M-d HH:mm:ss")}}. Let me break that down for you.
label=TimeCreated is what the property name will be going further down the pipe. I simply reused the same name.
expression={ScriptBlock} tells the system what the value for that property will be for each record.
As for the actual scriptblock, in this case we were already working with a [DateTime] object so I used its ToString() method, and specified a format of your design to output it as. That changes it, so it is now a [String] instead of a [DateTime] object, but seeing as you are just converting the whole thing to a CSV a string should do just fine.
Edit: You can add a switch into the scriptblock of the hashtable described above, it just gets long and can be hard to follow. I would do something like:
powershell "Get-WinEvent -EA SilentlyContinue -FilterHashtable #{Logname='System';ID=7001,10,12,13,41,42,1129,5060,5719,6008,7045}| SELECT-Object ID,#{l='ID Description';e={Switch($_.ID){
"7001" {"Text1"}
"10" {"Text2"}
"12" {"Text3"}
"13" {"Text4"}
"41" {"Text5"}
"42" {"Text6"}
"1129" {"Text7"}
"5060" {"Text8"}
"5719" {"Text9"}
"6008" {"Text10"}
"7045" {"Text11"}
}
}},#{label='TimeCreated';expression={$_.TimeCreated.ToString("yyyy-M-d HH:mm:ss")}},MACHINENAME,MESSAGE|ConvertTo-Csv -NoTypeInformation | %{ $_ -replace """`r`n""",',' } | select -Skip 1"
l= is short for label= and e= is short for expression=
Edit2: More switch info... You could do things based on multiple fields, you would want to do Switch($_) and then on each line put your conditions in a scriptblock, so something like:
Switch($_){
{$_.ID -eq "7001" -and $_.Message -match "catastrophic"}{"The dog ate my NetBIOS"}
{$_.ID -eq "7001" -and $_.Message -match "Lex Luthor"}{"Superman stole my WiFi"}
{<more conditions>}{<and their resultant values>}
}
You can specify an expression in the Select-Object command to create a calculated property. Here, I called this new property "Time" and used the ToString() method with the InvariantCulture to make sure the output is consistent on different computers.
Get-WinEvent -EA SilentlyContinue -FilterHashtable #{Logname='System';ID=7001,10,12,13,41,42,1129,5060,5719,6008,7045} | `
SELECT-Object -Property ID,#{Name="Time"; Expression = {$_.TimeCreated.Tostring("yyyy-MM-d HH:mm:ss", [CultureInfo]::InvariantCulture)}},MACHINENAME,MESSAGE | `
ConvertTo-Csv -NoTypeInformation | %{ $_ -replace """`r`n""",',' } | select -first 5
$output = $data | Where-Object {$_.Name -eq "$serverName"} | Select-Object -Property Description1,Version | Where-Object {$_.Description1 -eq "Power controller Firmware"} | Select-Object -Property Version
Write-Host $output
Gives me the following output:
#{Version=3.4}
So $data is an array and I select what I want form it and assign it to a variable to eventually be inputted into a excel file but no matter what I seem to try I cant just select "3.4" Instead it selects like the above (#{Version=3.4}). Doesn't anybody know how to just select the "3.4" within my command?
Just replace last line with:
foreach( $out in $output )
{
Write-Host $out.Version
}
In fact your $output variable contains an array so you need to go through it with a foreach loop.
Then you can Write-Host or do anything with the Version property.
As stated by #okaram, if you want to make the same kind of looping but after a pipe you can do it this way:
$output | ForEach-Object {Write-Host $_.Version}
or
$output | %{Write-Host $_.Version}
Your last expression of
Select-Object -Property Version
Keeps the entire object in the pipeline, but filters down the properties to only Version. However, the -ExpandProperty will put the property value itself in the pipeline.
Select-Object -ExpandProperty Version
That should return the "3.4" result you expect.
Please try the following code:
$data | Where-Object {$_.Name -eq "$serverName"} | Select-Object -Property
Description1, Version | Where-Object {$_.Description1 -eq "Power controller
Firmware"} | write-Host $_.Version