Powershell: Using a variable for the fields in Select-Object - powershell

I'm building a script that is creating csv files. Each csv has a different set of fields. Everything is working great, but I want to control the column order output. I have a variable that contains the fields and the order I want them in.
I know that I can use Select-Object to control this. What I want to do is this:
$Fields = "ID,Field1,Field2,Field3,Field4"
$Obj | Select-Object $Fields | Export-CSV -Path $OutputPath
Instead of this:
$Obj | Select-Object "ID","Field1","Field2","Field3","Field4" | Export-CSV -Path $OutputPath
Since I have several different formats and want to just pass the field list as a parameter to the function doing the work. Is this possible?

Yes it is absolutely possible.
When you pass in "ID,"Field1","Field2","Field3","Field4" that is just an array of strings.
To show a literal example:
$Fields = "ID","Field1","Field2","Field3","Field4"
$Obj |Select-object $Fields |export-csv -path $OutputPath
How you assign the value of $Fields during each iteration is up to you.

Related

redundant results are saved when using Export-CSV

I have a working script for detecting Log4j but trying to export all the result to CSV.
The current script was able to export but is giving me multiple rows of exact result.
$hostnames = "PC1"
foreach ($hostname in $hostnames){
Write-Host "Starting..."
$List = Invoke-Command -ComputerName $hostname {Get-ChildItem 'C:\' -Recurse -Force -Include *.jar -ea 0 | foreach {select-string "JndiLookup.class" $_} | select -exp Path}
if ($List -ne $null)
{
$List1 = [system.String]::Join("`r`n", $List)
$file_location = "C:\Powershell\log4j-test.csv"
$global:name = #{N="Computer Name";E={$hostname}}
$global:lists = #{N="Affected Location";E={$List1}}
Write-Host "Done.."
$name, $lists | Export-CSV $file_location -noTypeInformation -Force -Append -Encoding UTF8
}
}
You're probably looking for something like this:
[pscustomobject] #{
'Computer Name' = $hostname
'Affected Location' = $List -join "`r`n"
} | Export-CSV $file_location -noTypeInformation -Force -Append -Encoding UTF8
Note that each single CSV row created in each iteration will (potentially) span multiple lines, due to using newlines ("`r`n") as separators in the Affected Location column.
As for what you tried:
Your hashtable literals (e.g. #{N="Computer Name";E={$hostname}}) use the syntax of a calculated property, yet you're not using a cmdlet - typically Select-Object - that is capable of creating calculated properties.
More generally, Export-Csv and ConvertTo-Csv in Windows PowerShell do not meaningfully support hashtables as input objects - you'll need to provide [pscustomobject] instances instead, which you can easily create by casting[1] a hashtable literal to [pscustomobject], as shown above
By contrast, in PowerShell (Core) 7+ you may now use hashtables directly as input, though you'll probably want to use [ordered] hashtables for predictable column ordering.
[1] Strictly speaking, the form [pscustomobject] #{ ... } isn't a cast, but built-in syntactic sugar for creating [pscustomobject] instances as literals. This is evidenced by the fact that the key-definition order in the hashtable literal is maintained in this case, whereas it isn't in stand-alone hashtable literals.

Powershell comparing 2 csv files

I'm trying to compare 2 files that both contain email addresses. For example text1.csv has 800 addresses and text2.csv has 200. Most of the email addresses in text2.csv are in text1.csv but not all of them. I need to export all the email addresses from text2.csv that are NOT in text1.csv. The property on both files is email
I tried something like this but I dont get the correct outcome:
Compare-Object -ReferenceObject (Get-Content -Path C:\scripts\test1.csv) -DifferenceObject (Get-Content -Path C:\scripts\test2.csv) | Where-Object{ $_.SideIndicator -eq "=>" }
I also tried this without result
$file2 | Where-Object { $_.email -NotIn $file1 }
By using Get-Content, you end up comparing whole lines from your input files.
In order to compare column values from CSV files, you must first convert your input files to objects, using Import-Csv, whose properties (reflecting the column values) you can then compare; in your case, the .email property:
Compare-Object -PassThru (Import-Csv C:\scripts\test1.csv).email `
(Import-Csv C:\scripts\test2.csv).email |
Where-Object{ $_.SideIndicator -eq '=>' }
Note:
Compare-Object's -PassThru switch directly passes the differing objects through, decorated with a .SideIndicator ETS property, instead of wrapping them in a [pscustomobject] wrapper whose .InputObject contains the respective original object.
Even though, with multiple data rows in the CSV, Import-Csv returns an array of objects, you can access .email directly on that array in order to get the property values of all elements in the array, which is a convenient feature known as member-access enumeration.
For brevity, I'm using positional arguments above (e.g., C:\scripts\test1.csv instead of -Path C:\scripts\test1.csv).
I'd probably do something like this:
$emails = (Import-Csv -Path 'X:\file1.csv').email
$result = Import-Csv -Path 'X:\file2.csv' |
Where-Object { $emails -notcontains $_.email }
# output to new file
$result | Export-Csv -Path 'X:\missingEmails.csv' -NoTypeInformation

How to add a string to the output from powershell

I use this to get a list of folders containing .h files.
**
$type = "*.h"
$HDIRS = dir .\$type -Recurse |
Select-Object Directory -Unique |
Format-Table -HideTableHeaders
** It gives me a list of folders.
Now I want "-I " before every foldername. Common string manipulation doesn't seem to work
You still have rich objects after the select so to manipulate the strings you have to reference the one property you've selected "Directory"
$type = "*.h"
$HDIRS = Dir .\$type -Recurse |
Select-Object Directory -Unique |
ForEach-Object{
$_.Directory = "-I" + $_.Directory
$_
} |
Format-Table -HideTableHeaders
This will result in $HDIRS looking like a list of folder paths like -IC:\temp\something\something...
However, the format output objects are generally not suitable for further consumption. It looks like you're interested in the strings you could simply make this a flat array of strings like:
$type = "*.h"
$HDIRS = Dir .\$type" -Recurse |
Select-Object -ExpandProperty Directory -Unique |
ForEach-Object{ "-I" + $_ }
The mantra goes filter left, format right.
Our data:
$type = '.\*.h'
Get-ChildItem -Path $type -Recurse
The manipulation (filter):
Select-Object { "-I $($_.Directory)" } -Unique
And the format:
Format-Table -HideTableHeaders
In many cases, PowerShell cmdlets allow you to pass scriptblocks (closures) to evaluate to values and that's what I'm doing above with the Select-Object call.

Excluding folders using Get-ChildItem -Exclude

I require to exclude user profiles from \\Computer\c$\Users folder when using Get-ChildItem.
I potentially need to exclude 100 profiles from various machines so have created a variable that contains all user profiles from an AD security group that I want excluding but when I run the below command it doesn't work but if I list the profiles in plain text it does work.
Get-ChildItem "\\$computer\c$\Users" -Exclude $ListOfUsers
But works if I run Get-ChildItem "\\$computer\c$\Users" -Exclude Bob1,Bob2,Bob3,Bob4,Bob5
My code to obtain the users from AD security groups are below and the format it outputs to is like the above with commas.
## collects users from groups and adds to exlusion list.
$1stline = Get-ADGroupMember GG_1st_Line_Team | Select SamAccountName
$2ndline = Get-ADGroupMember GG_2nd_Line_Team | Select SamAccountName
$3rdline = Get-ADGroupMember GG_3rd_Line_Team | Select SamAccountName
$FieldTech = Get-ADGroupMember "Field tech" | select SamAccountname
$Excluded = $1stline + $2ndline + $3rdline + $Fieldtech | Out-File "C:\temp\Members.txt"
(get-content c:\temp\Members.txt) -join "," | out-file C:\temp\Format.txt
$format = get-content c:\temp\format.txt; $format = $format -replace '\s',''; $format = $format -replace ',SamAccountName,--------------,',''; $format = $format.trimend(",,") | Out-File "C:\temp\newFormat.txt"
$excluded = Get-Content C:\Temp\newFormat.txt
This will create a text file with 100 names with no spaces, only commas to seperate the users. If I Write-Host the variable $newformat the commas are White along with the text but if I put the Profile names in plain text then the commas are grey...
The excessive use of Out-File is unnecessary and will cause you issues as it changes objects into strings. You then have to use commands like Split and Join to turn the strings back into the object (arrays here) you require.
Out-File should only be used when you actually need the output as a file.
With a few simple changes to your script it'll do what you want, and be a lot simpler too!
You can use ExpandProperty to only return the value of SamAccountName itself as a string rather than an object.
You can then simply create an array of those strings and use this to exclude those accounts/folders from Get-ChildItem,
$1stline = Get-ADGroupMember GG_1st_Line_Team | Select-Object -ExpandProperty SamAccountName
$2ndline = Get-ADGroupMember GG_2nd_Line_Team | Select-Object -ExpandProperty SamAccountName
$3rdline = Get-ADGroupMember GG_3rd_Line_Team | Select-Object -ExpandProperty SamAccountName
$FieldTech = Get-ADGroupMember "Field tech" | Select-Object -ExpandProperty SamAccountname
$excluded = $1stline,$2ndline,$3rdline,$Fieldtech
Get-ChildItem "\\$computer\c$\Users" -Exclude $excluded
You could then look into using a foreach loop with Get-ADGroupMember to cut down on the duplicated Get-ADGroupMember code.
The problem here seems to be that you're using a [string] variable with a value of Tom,Dick,Harry.
I bet you've got no folders like this, have you?
Whilst you can pass a [string] in to the -Exclude parameter; that will be a singleton (a single, scalar value); and the best you'll get is a single exclusion.
What you really want to pass in is a string array: [string[]]!
[string]$excludedString = "Tom,Dick,Harry"
[string[]]$excludedArray = $excludedString -split ","
Get-ChildItem -Path "C:\users" -Exclude $excludedArray

Check AD Computer Names against CSV file

I'm trying to write a Powershell script that will import a list of computer names from a CSV file and compare it against all computer names in AD. I need it to output a list of names from the CSV file that do not exist in AD.
I have been playing around with these commands but they don't do exactly what I want.
$import = Import-Csv -Path C:\Users\username\Desktop\test.csv
$AD-Names = Get-ADComputer -filter * | Select -Expand Name
Compare-Object $import $AD-Names
This seems to give me a list of everything different from both variables which is an extremely long list. I really just need to know which computer names in the CSV file are not in AD.
Simply compare the names from your CSV against the list of names from AD (I'm going to assume that the column title of the CSV is Name):
$ADNames = Get-ADComputer -Filter * | Select-Object -Expand Name
Import-Csv 'C:\path\to\input.csv' |
Where-Object { $ADNames -notcontains $_.Name } |
Export-Csv 'C:\path\to\not_in_AD.csv' -NoType
Side note: $AD-Names is not a valid variable name. Change the hyphen to an underscore or remove it entirely, or put the variable name in curly brackets (${AD-Names}).
First, why do you need a csv file if there are only computer names ?
CSV files are usually used to store a table in a simple text file.
If your CSV only contains the computer names, and you only want to check for computer names, this could work for you :
$OutputComputers = #();
$InputComputers = Import-Csv input.csv -Header ComputerName;
$AdComputers = Get-ADComputer -Filter 'Name -like "*"';
$InputComputers | ForEach-Object {$i = 0} {
if ($_.ComputerName -notin $AdComputers.Name) {
$OutputComputers += New-Object PsObject -property #{
'ComputerName' = $_.ComputerName
}
}
}
$OutputComputers | Export-Csv output.csv -Delimiter ";" -Encoding UTF8 -Force -NoTypeInformation
Hope it helps.