Powershell import-csv $variable properly is null - powershell

I have a weird issues with Powershell Version 2.0.
The following works on newer versions but its not working as expected on this version. Any help is appreciated.
$DB = Import-Csv -Path "$($path)\DBExtrat.csv"
which is fine.
Headers in DBExtrat.csv ('hostname','hostip','name','type')
all 4 headers are reorganized and show up if i run
$DB
But if I try
$DB.name or $DB.hostname it returns noting. I need to be able to call it like this because my whole logic is tied to those specific variables names.
I've already tried adding the -header option:
$DB = Import-Csv -Path "$($path)\DBExtrat.csv" -Header 'hostname','hostip','name','type'
but it doesn't work and also creates unnecessary extra row with header data.

With an expression such as $DB.name, you're trying to get the name property values of all elements of collection $DB, by performing the property access on the collection as a whole.
This feature is called member-access enumeration, and it is only available in PowerShell v3+.
The PowerShell v2 equivalent requires use of either Select-Object or ForEach-Object:
# Note the use of -ExpandProperty to ensure that only the property *value*
# is returned (without it, you get a *custom object* with a 'name' property).
$DB | Select-Object -ExpandProperty name
# Slower alternative, but can provide more flexibility
$DB | ForEach-Object { $_.name }

I'd recommend going with #mklement0 answer; short and simple. Alternatively, going off the question you asked in the comments, you may try working with a custom object and seeing if the below works.
$import = Import-Csv -Path "$($path)\DBExtrat.csv"
$Object = New-Object psobject -Property #{
'hostname' = $import | Select-Object -ExpandProperty hostname
'hostip' = $import | Select-Object -ExpandProperty hostip
'name' = $import | Select-Object -ExpandProperty name
'type' = $import | Select-Object -ExpandProperty type
}
"$Object.hostname - $Object.hostip"

Related

How to add a header ExpandProperty in PowerShell?

I am trying to build a script to help me figure out service accounts using Kerberos Constrained Delegation. Two of the properties that I am interested in are multi-valued, so I am using the -ExpandProperty switch. Unfortunately, I haven't figured out a 'clean' way to output the property name with the expanded values. Because the two expanded properties have similar values and can overlap, I need to do something to show where ServicePrincipalNames ends and msDS-AllowedToDelegateTo begins. The code below works, but it seems like there should be a way of getting the same (or very similar) output without having to use Write-Output.
$Account = "svcSomeService"
# Query AD once
$Details = Get-ADUser -Identity $Account -Properties *
# Main result set
$Details | Select-Object -Property SamAccountName, DisplayName, Enabled, PasswordNeverExpires, PasswordExpired, LockedOut, AccountNotDelegated, TrustedForDelegation, TrustedToAuthForDelegation, KerberosEncryptionType
# Expand muulti-value column ServicePrincipalNames
Write-Output "ServicePrincipalNames"
Write-Output "---------------------"
$Details | Select-Object -ExpandProperty ServicePrincipalNames #Tried with and without Format-Table
# Expand muulti-value column msDS-AllowedToDelegateTo
Write-Output "`n"
Write-Output "msDS-AllowedToDelegateTo"
Write-Output "------------------------"
$Details | Select-Object -ExpandProperty msDS-AllowedToDelegateTo #Tried with and without Format-Table
You can construct a [pscustomobject] that houses the expanded values in distinct properties:
[pscustomobject] #{
ServicePrincipalNames = $Details.ServicePrincipalNames
msDS-AllowedToDelegateTo = $Details.msDS-AllowedToDelegateTo
}
Note:
$Details.ServicePrincipalNames is a more efficient alternative to $Details | Select-Object -ExpandProperty ServicePrincipalNames, via the member-access enumeration feature.
As for display formatting on the caller's side: Since the output object has only two properties, it will implicitly render as a table (implicit Format-Table), which doesn't provide much space for showing the individual values. Piping to Format-List helps, but additionally requires you to raise $FormatEnumerationLimit to avoid truncation;[1] to provide a simple example:
$FormatEnumerationLimit=100 # !! As of v7.2.2: only effective in *global* scope
[pscustomobject] #{ prop1 = 1..100; prop2 = 80..1 } | Format-List
[1] Due to an unfortunate bug up to at least PowerShell 7.2.2, setting this preference variable is only effective in the global scope - see GitHub issue #888.

PowerShell Export-CSV - Missing Columns [duplicate]

This question already has an answer here:
Not all properties displayed
(1 answer)
Closed 1 year ago.
This is a follow-up question from PowerShell | EVTX | Compare Message with Array (Like)
I changed the tactic slightly, now I am collecting all the services installed,
$7045 = Get-WinEvent -FilterHashtable #{ Path="1system.evtx"; Id = 7045 } | select
#{N=’Timestamp’; E={$_.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')}},
Id,
#{N=’Machine Name’; E={$_.MachineName}},
#{N=’Service Name’; E={$_.Properties[0].Value}},#{N=’Image Path’;E=$_.Properties[1].Value}},
#{N=’RunAsUser’; E={$_.Properties[4].Value}},#{N=’Installed By’; E={$_.UserId}}
Now I match each object for any suspicious traits and if found, I add a column 'Suspicious' with the value 'Yes'. This is because I want to leave the decision upto the analyst and pretty sure the bad guys might use something we've not seen before.
foreach ($Evt in $7045)
{
if ($Evt.'Image Path' -match $sus)
{
$Evt | Add-Member -MemberType NoteProperty -Name 'Suspicious' -Value 'Yes'
}
}
Now, I'm unable to get PowerShell to display all columns unless I specifically Select them
$7045 | Format-Table
Same goes for CSV Export. The first two don't include the Suspicious Column but the third one does but that's because I'm explicitly asking it to.
$7045 | select * | Export-Csv -Path test.csv -NoTypeInformation
$7045 | Export-Csv -Path test.csv -NoTypeInformation
$7045 | Select-Object Timestamp, Id, 'Machine Name', 'Service Name', 'Image Path', 'RunAsUser', 'Installed By', Suspicious | Export-Csv -Path test.csv -NoTypeInformation
I read the Export-CSV documentation on MS. Searched StackOverFlow for some tips, I think it has something to do with PS checking the first Row and then compares if the property exists for the second row and so on.
Thank you
The issue you're experiencing is partially because of how objects are displayed to the console, the first object's Properties determines the displayed Properties (Columns) to the console.
The bigger problem though, is that Export-Csv will not export those properties that do not match with first object's properties unless they're explicitly added to the remaining objects or the objects are reconstructed, for this one easy way is to use Select-Object as you have pointed out in the question.
Given the following example:
$test = #(
[pscustomobject]#{
A = 'ValA'
}
[pscustomobject]#{
A = 'ValA'
B = 'ValB'
}
[pscustomobject]#{
C = 'ValC'
D = 'ValD'
E = 'ValE'
}
)
Format-Table will not display the properties B to E:
$test | Format-Table
A
-
ValA
ValA
Format-List can display the objects properly, this is because each property with it's corresponding value has it's own console line in the display:
PS /> $test | Format-List
A : ValA
A : ValA
B : ValB
C : ValC
D : ValD
E : ValE
Export-Csv and ConvertTo-Csv will also miss properties B to E:
$test | ConvertTo-Csv
"A"
"ValA"
"ValA"
You have different options as a workaround for this, you could either add the Suspicious property to all objects and for those events that are not suspicious you could add $null as Value.
Another workaround is to use Select-Object explicitly calling the Suspicious property (this works because you know the property is there and you know it's Name).
If you did not know how many properties your objects had, a dynamic way to solve this would be to discover their properties using the PSObject intrinsic member.
using namespace System.Collections.Generic
function ConvertTo-NormalizedObject {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, Mandatory)]
[object[]] $InputObject
)
begin {
$list = [List[object]]::new()
$props = [HashSet[string]]::new([StringComparer]::InvariantCultureIgnoreCase)
}
process {
foreach($object in $InputObject) {
$list.Add($object)
foreach($property in $object.PSObject.Properties) {
$null = $props.Add($property.Name)
}
}
}
end {
$list | Select-Object ([object[]] $props)
}
}
Usage:
# From Pipeline
$test | ConvertTo-NormalizedObject | Format-Table
# From Positional / Named parameter binding
ConvertTo-NormalizedObject $test | Format-Table
Lastly, a pretty easy way of doing it thanks to Select-Object -Unique:
$prop = $test.ForEach{ $_.PSObject.Properties.Name } | Select-Object -Unique
$test | Select-Object $prop
Using $test for this example, the result would become:
A B C D E
- - - - -
ValA
ValA ValB
ValC ValD ValE
Continuing from my previous answer, you can add a column Suspicious straight away if you take out the Where-Object filter and simply add another calculated property to the Select-Object cmdlet:
# create a regex for the suspicious executables:
$sus = '(powershell|cmd|psexesvc)\.exe'
# alternatively you can join the array items like this:
# $sus = ('powershell.exe','cmd.exe','psexesvc.exe' | ForEach-Object {[regex]::Escape($_)}) -join '|'
$7045 = Get-WinEvent -FilterHashtable #{ LogName = 'System';Id = 7045 } |
Select-Object Id,
#{N='Timestamp';E={$_.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')}},
#{N='Machine Name';E={$_.MachineName}},
#{N='Service Name'; E={$_.Properties[0].Value}},
#{N='Image Path'; E={$_.Properties[1].Value}},
#{N='RunAsUser'; E={$_.Properties[4].Value}},
#{N='Installed By'; E={$_.UserId}},
#{N='Suspicious'; E={
if ($_.Properties[1].Value -match $sus) { 'Yes' } else {'No'}
}}
$7045 | Export-Csv -Path 'X:\Services.csv' -UseCulture -NoTypeInformation
Because you have many columns, this will not fit the console width anymore if you do $7045 | Format-Table, but the CSV file will hold all columns you wanted.
I added switch -UseCulture to the Export-Csv cmdlet, which makes sure you can simply double-click the csv file so it opens correctly in your Excel.
As sidenote: Please do not use those curly so-called 'smart-quotes' in code as they may lead to unforeseen errors. Straighten these ’ thingies and use normal double or single quotes (" and ')

How to fix A null key is not allowed in a hash literal in PowerShell

I been following this Microsoft doc https://learn.microsoft.com/en-us/powershell/module/exchange/search-unifiedauditlog?view=exchange-ps and I'm trying to write a PowerShell Script that download Audit Log. So far everything is going well but I'm just wondering how can I read User Id from my csv file instead of having a user Id directly in my script?.
This is how my CSV file look right now.
C:\AuditLogSearch\Reference\User.csv
Name Email Id
................................
1. Ronaldo ronaldo#gmail.com KLIEKN
2. Messi messi#gmail.com LEK89K
3. LeBron lebron#gmail.com IKNLM9
I was hard coding like this in the script and it's working fine but
$Global:user = "KLIEKN", "LEK89K", "IKNLM9",
Search-UnifiedAuditLog -SessionId $sessionID -SessionCommand ReturnLargeSet -UserIds $user
I don't think that'll be good idea so I try to do read it from my CSV file like this
$Global:userPath = "C:\AuditLogSearch\Reference\User.csv"
function Log-Search {
Import-Csv $userPath | ForEach-Object -Property #{
$userId = $($_.Id)
}
Search-UnifiedAuditLog -SessionId $sessionID -SessionCommand ReturnLargeSet -UserIds $userId
}
but I'm getting this error
A null key is not allowed in a hash literal.
I'll be really appreciated any help or suggestion.
{} defines a [ScriptBlock] — which is what you'd pass to the ForEach-Object cmdlet to be invoked for each element — whereas #{} defines a [Hashtable]. $userId is $null because you have not assigned a value, so where you have...
#{
$userId = $($_.Id)
}
...you are trying to define a [Hashtable] with an element with a key of $null, hence the error.
There is also no such -Property parameter of ForEach-Object, so when you remove "-Property #", you end up with a valid script...
Import-Csv $userPath | ForEach-Object {
$userId = $($_.Id)
}
This is reading your CSV file but not yet passing the data to your Search-UnifiedAuditLog call. There are several ways to retrieve the Id field of each CSV record, but the shortest transformation from the previous snippet would be...
Import-Csv $userPath | ForEach-Object {
$_.Id
}
...which can be rewritten using the -MemberName parameter...
Import-Csv $userPath | ForEach-Object -MemberName Id
...and then all that's left is to store the pipeline results in $userId...
$userId = Import-Csv $userPath | ForEach-Object -MemberName Id
By the way, the CSV data you posted cannot be readily parsed by Import-Csv. If possible, save your data without the second line and using comma or tab as the delimiter (the latter being read with Import-Csv ... -Delimiter "`t"); otherwise, the script will have to do some manipulation before it can read the data.
Try this perhaps?
$userId=#()
Import-Csv $userPath | ForEach-Object {
$userId += $_.Id
}
or
$userId = Import-Csv $userPath | Select-Object -ExpandProperty Id

How to solve the "'IndexOf'." error in PowerShell script

While running the below script I am getting an error:
"Method invocation failed because [System.Management.Automation.PSCustomObject] doesn't contain a method named 'IndexOf'".
Please help me to find out a solution to avoid the above error while running the below script.
Code:
$serverlist_csv1 = Import-Csv -Path $file1
$serverlist_temp1 = $serverlist_csv1
$exclude_serverlist_csv = Import-Csv -Path $file2
foreach ($server in $exclude_serverlist_csv) {
$servers1 = ($serverlist_csv1.'SourceHostName' | Select-Object -ExcludeProperty 'SourceHostName')
if ($servers1 -contains $server.'Exclude Server') {
$server_object1 = ($serverlist_csv1 | Where-Object {$_.SourceHostName -eq $server.'Exclude Server'})
$serverindex1 = $serverlist_csv1.IndexOf($server_object1)
$dataResizable1 = {$serverlist_csv1}.Invoke()
$dataResizable1.RemoveAt($serverindex1)
$serverlist_csv1 = $dataResizable1
}
}
You are getting that error because you used select-object on the pipeline invocation of $serverlist_csv1, which creates a layer over the object effectively creating a new object a pscustomobject and that makes it lose the indexOf() method.
if you avoid this step and instead try for instance to exclude that on the import of the csv or modify the member to nothing if that property isn't necessary to you. The point is like people before me said that does not seem like the better way to solve whatever problem this is.
Build a list of the server names from the second file:
$exclude = Import-Csv $file2 | Select-Object -Expand 'Exclude Server'
Then filter the first file for rows whose SourceHostName column isn't one of those names and write the result back to a file:
Import-Csv $file1 | Where-Object {
$exclude -notcontains $_.SourceHostName
} | Export-Csv 'C:\output.csv' -NoType

Issue with Powershell custom table

I'm trying to create a custom table based on two other tables (csv-imported) - some kind of a VLOOKUP, but I can't seem to find a solution. I've come up with the following (failing) code:
$DrawPlaces | select Module, Workplace, #{ Name = "IPaddress"; Expression = {$Workstations.workstation.where($_.WorkPlace -eq $Workstations.Workplace)}} -First 15
Both Drawplaces and $Workplaces are PSCustomObject. The result of this would then go to another variable.
I'm not even sure the logic or syntax is correct, but the result table has the IPaddress column empty. I've also tried with -match instead of -eq.
This doesn't make sense: $Workstations.workstation.where($_.WorkPlace -eq $Workstations.Workplace)
.where() requires a scriptblock parameter like .where({}).
Keeping in mind that inside the where-statement $_ is refering to the current object in the $workstations.workstation-loop, your where-statement is testing ex. $workstations.workstation[0].workplace -eq $workstations.workplace. Is that really what you want?
Are you trying to achieve this?
$DrawPlaces |
Select-Object -First 15 -Property #(
"Module",
"Workplace",
#{ Name = "IPaddress"; Expression = {
#Save the Workspace-value for the current object from $DrawPlaces
$wp = $_.WorkPlace;
#Find the workstation with the same workplace as $wp
$Workstations | Where-Object { $_.WorkPlace -eq $wp} | ForEach-Object { $_.Workstation }
}
}
)