Separating values of a column in a CSV in powershell - excel

I have multiple files contain a persons Fullname in a single cell. I would like to separate these names into two columns - first name and Surname
the code I have used to separate values worked in a previous iteration of the script I had but now no longer works
I can't pinpoint where the error lies, can anyone advise?
$path = 'C:\MAY2019correct'
#XYZ
$excelOut = Join-Path -Path $path -ChildPath 'XYZ.csv'
$completedFile = Join-Path -Path $path -ChildPath 'Completed-XYZ.csv'
$defaultValue = 'ABC'
$filter = '*XYZ*'
$excelFile = Get-ChildItem -Path $path -Filter $filter -File |
Select-Object -First 1
$allstaff = #()
if ($excelFile) {
$excel = New-Object -ComObject Excel.Application -Property #{Visible =
$false}
# Open the file
$workbook = $excel.Workbooks.Open($excelFile.FullName)
# Activate the first worksheet
$sheet = $workbook.Sheets.Item(1)
[void]$sheet.Cells.Item(1, 1).EntireRow.Delete() # Delete the first row
[void]$sheet.Cells.Item(1, 1).EntireRow.Delete() # Delete the 2 row
[void]$sheet.Cells.Item(1, 1).EntireRow.Delete() # Delete the 3 row
$workbook.SaveAs($excelOut,6)
# Close workbook and save changes
$workbook.Close($true)
# Quit Excel
$excel.Quit()
# clean-up Com objects
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($workbook) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
$headers = 'Element Name','Surname','EmployeeNo','Amount','YTD'
# import the csv file and select all the above headers plus one that is created using a calculated property
$csv = Import-Csv -Path $excelOut -Header $headers -UseCulture | Select-Object *, #{Name = 'Paycentre'; Expression = {$defaultValue}}|
Write-Host "Creating completed csv file '$completedFile'"
$csv | Export-Csv -Path $completedFile -UseCulture -Force -
NoTypeInformation
}
else {
Write-Warning "Could not find a file using filter '$filter' in path
'$path'"
}
foreach($staff in $completedFile)
{
#Get the values from the CSV for this row
$Surname = $staff.Surname
$Surname = $Surname.Substring(0, $Surname.lastIndexOf(' '))
$Initial = $staff.Surname
$Initial = $Initial.Substring($Initial.lastIndexOf(' ') + 1 )
$Firstname = $staff.Surname
$Firstname = $Firstname.Substring($Initial.lastIndexOf(' ') + 1 )
$EmployeeNo = $staff.EmployeeNo
$NINumber = $staff.NINumber
$Amount = $staff.Amount
$Paycentre = $staff.Paycentre
$staff2 = New-Object System.Object
$staff2 | Add-Member -MemberType NoteProperty -Name "Surname" -Value $Surname
$staff2 | Add-Member -MemberType NoteProperty -Name "FirstName" -Value $Firstname
$staff2 | Add-Member -MemberType NoteProperty -Name "EmployeeNo" -Value $EmployeeNo
$staff2 | Add-Member -MemberType NoteProperty -Name "NINumber" -Value $NINumber
$staff2 | Add-Member -MemberType NoteProperty -Name "Amount" -Value $Amount
$staff2 | Add-Member -MemberType NoteProperty -Name "FirstName" -Value $Initial
$staff2 | Add-Member -MemberType NoteProperty -Name "Paycentre" -Value $Paycentre
#add to array
$allstaff += $staff2
}
$allstaff | Export-Csv -NoTypeInformation -Path $completedFile

The first thing I see is that your foreach loop is iterating over the $completedFile variable which appears to be a file path, not a collection.
Should it be foreach ($staff in $csv) instead?
Also, be very wary of dealing with names in code. It's super easy to make poor assumptions that break things. See Falsehoods Programmers Believe About Names for more info there.

Related

How to split different values in powershell by a line

With this script i am able to fetch all the Tags that a VM has but i want that in output the each key and its value should be separated by a line in the way that each key and its value appears on different lines like this
reference image
# Sign into Azure Portal
connect-azaccount
# Fetch the Virtual Machines from the subscription
$azureVMDetails = get-azvm
# Fetch the NIC details from the subscription
$azureNICDetails = Get-AzNetworkInterface | ?{ $_.VirtualMachine -NE $null}
#Fetching Virtual Machine Details
$virtual_machine_object = $null
$virtual_machine_object = #()
#Iterating over the NIC Interfaces under the subscription
foreach($azureNICDetail in $azureNICDetails){
#Fetching the VM Name
$azureVMDetail = $azureVMDetails | ? -Property Id -eq $azureNICDetail.VirtualMachine.id
#Fetching the VM Tags
foreach($azureDetail in $azureVMDetails) {
$vm_tags = $azureVMDetail| Select-Object -Property (
#{name='Tags'; expression = {($_.tags.GetEnumerator().ForEach({ '{0} : {1}' -f $_.key, $_.value }) -join ';')}}
)
}
#VM Details export
$virtual_machine_object_temp = new-object PSObject
$virtual_machine_object_temp | add-member -membertype NoteProperty -name "name" -Value $azureVMDetail.Name
$virtual_machine_object_temp | add-member -membertype NoteProperty -name "comments" -Value ($vm_tags.Tags -join ';')
$virtual_machine_object += $virtual_machine_object_temp
}
#Report format and path
$virtual_machine_object | Export-Csv "C:\Users\JOHN\Desktop\Inventory\Final Scripts\VM_details_$(get-date -f dd.MM.yyyy).csv" -NoTypeInformation -Force
I tried to reproduce the same in my environment and got the results successfully by using the below PowerShell script:
$vmdeatil = Get-AzVm -Name testvm | Select -ExpandProperty Tags
$value = $vmdeatil
foreach($i in 0..($value.Count -1))
{
$ErrorActionPreference = ‘SilentlyContinue’
[array]$report += [pscustomobject] #{
key = $key[$i]
name = $value[$i]
}
}
$report | Export-Csv -Path "C:\Users\ruk1.csv" -NoTypeInformation
Response:
The output is successfully exported in the csv file like below:

Code to grab file data into PSOObject and sort by LastWriteTime

I am looking to recursively grab a list of recently modified files under two network drives, sort them in descending date order, and make some edits to the CSV file to tidy the list for Excel
I have cobbled the code below from a number of sources (I am a powershell beginner) and it is now doing what I need (i.e. producing a list).
I need help in going a step further, I cannot sort the resultant CSV file by file last write time date, is this because my array is expecting text rather than a numeric field?
I also am returning the domain name as well as the file owner with ((Get-ACL $_.FullName).Owner). I tried using Replace to cut down the string, but had no luck with this approach.
$arr = #()
$days_to_check=$(Get-Date).AddDays(-28)
$items = #(Get-ChildItem '\\ND\dir 1\*.*' -Recurse -ErrorAction SilentlyContinue | where { $_.LastWriteTime -gt $days_to_check})
$items += #(Get-ChildItem '\\ND\dir 1\*.*' -Recurse -ErrorAction SilentlyContinue |
where { $_.LastWriteTime -gt $days_to_check})
$items | Foreach {
$obj = New-Object PSObject -prop $hash
$obj | Add-Member NoteProperty FullName $_.FullName
$obj | Add-Member NoteProperty Directory $_.Directory
$obj | Add-Member NoteProperty Name $_.Name
$obj | Add-Member NoteProperty LastTime $_.LastWriteTime
$obj | Add-Member NoteProperty Owner ((Get-ACL $_.FullName).Owner)
$arr += $obj
}
$arr | Format-List
$arr | Sort-Object -Property LastTime -Descending
$arr | Export-CSV -notypeinformation C:\temp\filenamesFO.csv
CSV file sorted by date field
You did sort your array in the output but that's all you did.
If you want to actually export it that way, you have to assign the sort to $arr
Replace
$arr | Sort-Object -Property LastTime -Descending
with
$arr = $arr | Sort-Object -Property LastTime -Descending
You can remove the Owner domain using the following Replace -replace '(.*\\)(.*)','$2'
Here's a complete example implementing the changes mentionned above.
$arr = new-object -TypeName 'System.Collections.Generic.List[PSObject]'
$days_to_check=$(Get-Date).AddDays(-28)
$items = #(Get-ChildItem '\\ND\dir 1\*.*' -Recurse -ErrorAction SilentlyContinue | where { $_.LastWriteTime -gt $days_to_check})
$items += #(Get-ChildItem '\\ND\dir 1\*.*' -Recurse -ErrorAction SilentlyContinue |
where { $_.LastWriteTime -gt $days_to_check})
Foreach ($item in $items) {
$obj = [PSCustomObject]#{
FullName = $item.FullName
Directory = $item.Directory
Name = $item.Name
LastTime = $item.LastWriteTime
Owner = (Get-ACL $item.FullName).Owner -replace '(.*\\)(.*)','$2'
}
$arr.add($obj)
}
$arr = $arr | Sort-Object -Property LastTime -Descending
#$arr | Format-List
$arr | Export-CSV -notypeinformation C:\temp\filenamesFO.csv
I made some additional changes:
Instead of using an array, I used a List of PSObject. If you have a lot of files, the processing time will be improved in comparison with an array.
I used the PSCustomObject declaration just to show an alternative to all those Add-member. I find it cleaner but it is up to you in the end.

Display the difference of Excel columns with PowerShell

I would like to get the difference in the columns in as a table but I only can do a comparison with two columns and it doesn't display all the columns. I need to see in which column the cell is not present in the first column.
The file:
ComputerName OtherComputerName OtherComputer AndAgain
infra-1 infra-852 infra-2 infra-99
infra-98 infra-85 infra-44 infra-23
infra-5 infra-8 infra-1 infra-1
infra-2 infra-55 infra-8 infra-70
infra-62 infra-5 infra-852 infra-5
The current result:
ComputerName OtherComputerName OtherComputer AndAgain
------------ ----------------- ------------- --------
I would like to get this as result in PowerShell:
ComputerName OtherComputerName OtherComputer AndAgain
------------ ----------------- ------------- --------
infra-1 infra-852 infra-99
infra-98 infra-85 infra-44 infra-23
infra-5 infra-8
infra-2 infra-55 infra-8 infra-70
infra-62 infra-852
Edit
I have some errors too when I run this code.
The script:
$csv = Import-Csv .\test1.csv -Delimiter ';'
$ref = #($csv.ComputerName)
foreach ($row in $csv) {
foreach ($col in 'OtherComputerName', 'OtherComputer', 'AndAgain') {
if ($ref -contains $row.$col) { $row.$col = '' }
}
}
$table = #()
$file = Get-Content .\test1.csv
$file = $file -replace(" "," ")
$file = $file -replace(" "," ")
$file = $file -replace(" "," ")
$file = $file -replace(" - "," ")
[string[]]$a = Import-Csv '.\test1.csv' | select -Expand ComputerName
[string[]]$b = Import-Csv '.\test1.csv' | select -Expand OtherComputerName
[string[]]$c = Import-Csv '.\test1.csv' | select -Expand OtherComputer
[string[]]$d = Import-Csv '.\test1.csv' | select -Expand AndAgain
$b | ? {$a -notcontains $_}
$c | ? {$a -notcontains $_}
$d | ? {$a -notcontains $_}
foreach ($line in $file) {
$lineb = $line -split(" ")
$obj = New-Object PSObject
$obj | Add-Member -Name "ComputerName" -MemberType NoteProperty -Value $lineb[1]
$obj | Add-Member -Name "OtherComputerName" -MemberType NoteProperty -Value $lineb[1]
$obj | Add-Member -Name "OtherComputer" -MemberType NoteProperty -Value $lineb[2]
$obj | Add-Member -Name "AndAgain" -MemberType NoteProperty -Value $lineb[3]
$table += $obj
}
$table | ft -AutoSize
You need to read the values of the first column into an array, compare the values of the other columns against that array, and clear matching fields:
$csv = Import-Csv .\test1.csv -Delimiter ';'
$ref = #($csv | Select-Object -Expand ComputerName)
foreach ($row in $csv) {
foreach ($col in 'OtherComputerName', 'OtherComputer', 'AndAgain') {
if ($ref -contains $row.$col) { $row.$col = '' }
}
}

Object within a hashtable

I'm trying to put objects in a hashtable. I'm not getting errors but cannot access the data.
$Level1Hashtable = #{}
$Level2Object = New-Object System.Object
$Level2Object | Add-Member -MemberType NoteProperty -Name Name -Value "abc"
$Level2Object | Add-Member -MemberType NoteProperty -Name IpAddress -Value "192.168.1.1"
$Level1Hashtable.Add("Test1",$Level2Object)
$Level2Object = New-Object System.Object
$Level2Object | Add-Member -MemberType NoteProperty -Name Name -Value "123"
$Level2Object | Add-Member -MemberType NoteProperty -Name IpAddress -Value "192.168.1.1"
$Level1Hashtable.Add("Test2",$Level2Object)
$Level1Hashtable.Test1.IpAddress
This kind of redundant instantiation doesn't really impress me, so I went a slightly different route:
$Servers =
#{
"DC1" = [pscustomobject]#{ FQDN = "dc1.ad.foobar.com"; IpAddress = "192.168.2.1"}
"DC2" = [pscustomobject]#{ FQDN = "dc2.ad.foobar.com"; IpAddress = "192.168.2.2"}
"STS" = [pscustomobject]#{ FQDN = "sts.ad.foobar.com"; IpAddress = "192.168.2.3"}
}
Then you can also access elements very easily:
Servers["DC1"].IpAddress = "192.168.2.4"
Tested on PowerShell Core (aka PowerShell 6), works like a charm. Cheers.
That works for me using V4. Running in V2 it doesn't work, but does if I switch from using System.Object to PSObject for the object type in the New-Object cmdlets.
$Level1Hashtable = #{}
$Level2Object = New-Object PSObject
$Level2Object | Add-Member -MemberType NoteProperty -Name Name -Value "abc"
$Level2Object | Add-Member -MemberType NoteProperty -Name IpAddress -Value "192.168.1.1"
$Level1Hashtable.Add("Test1",$Level2Object)
$Level2Object = New-Object PSObject
$Level2Object | Add-Member -MemberType NoteProperty -Name Name -Value "123"
$Level2Object | Add-Member -MemberType NoteProperty -Name IpAddress -Value "192.168.1.1"
$Level1Hashtable.Add("Test2",$Level2Object)
$Level1Hashtable.Test1.IpAddress
Depending on the version of PowerShell you're using Add-Member had an issue (v1 and maybe v2) where you had to use -PassThru and reassign to the original object e.g.:
$Level2Object = $Level2Object | Add-Member NoteProperty Name abc -PassThru
In V3 you can create this more simply like so:
$Level2Object = [pscustomobject]#{Name='abc';IpAddress='192.168.1.1'}
In V2 you can use the Property parameter on new-object to simplify as well:
$Level2Object = new-object psobject -property #{Name='abc';IpAddress='192.168.1.1'}

Using PowerShell's Add-Member results in an error

Why does the script below come up with the following error?
"Add-Member : Cannot process command because of one or more missing
mandatory parameters: InputObject.
+ $obj = Add-Member <<<< -MemberType NoteProperty -Name ComputerName -Value $ComputerName
+ CategoryInfo : InvalidArgument: (:) [Add-Member], ParameterBindingException
+ FullyQualifiedErrorId : MissingMandatoryParameter,Microsoft.PowerShell.Commands.AddMemberCommand"
Script
# Receives the computer name and stores the required results in $obj.
Function WorkerNetworkAdaptMacAddress {
Param($ComputerName)
$colItems = GWMI -cl "Win32_NetworkAdapterConfiguration" -name "root\CimV2" -comp $ComputerName -filter "IpEnabled = TRUE"
$obj = New-Object -TypeName PSobject
ForEach ($objItem in $colItems)
{
$obj = Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName
$obj = Add-Member -MemberType NoteProperty -Name MacAddress -Value $objItem.MacAddress
$obj = Add-Member -MemberType NoteProperty -Name IPAdress -Value $objitem.IpAddress
}
Write-Output $obj
}
# Receives the computer name and passes it to WorkerNetworkAdaptMacAddress.
Function Get-NetworkAdaptMacAddress {
begin {}
process{
WorkerNetworkAdaptMacAddress -computername $_
}
end {}
}
# Passes a computer name to get-networkAdaptMacAddress
'tbh00363' | Get-NetworkAdaptMacAddress
You need to move the PSObject creation into the loop. Otherwise, you'll get errors that the properties already exist on the object.
Secondly, you need to tell Add-Member on which object to operate. You do it either by piping the object to the cmdlet or by specifying it on the InputObject parameter. Finally, return the object back to the pipeline by specifying the PassThru switch on the last Add-Member call:
ForEach ($objItem in $colItems)
{
$obj = New-Object -TypeName PSobject
Add-Member -InputObject $obj -MemberType NoteProperty -Name ComputerName -Value $ComputerName
Add-Member -InputObject $obj -MemberType NoteProperty -Name MacAddress -Value $objItem.MacAddress
Add-Member -InputObject $obj -MemberType NoteProperty -Name IPAddress -Value $objitem.IpAddress -PassThru
}
Alternatively, you could simplify the process with New-Object's -Property parameter:
Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $ComputerName -Filter "IpEnabled=TRUE" | Foreach-Object {
New-Object -TypeName PSobject -Property #{
ComputerName=$ComputerName
MacAddress=$_.MacAddress
IPAddress=$_.IpAddress
}
}
Or by using Select-Object:
Get-WmiObject ... | Select-Object #{n='ComputerName';e={$_.__SERVER}},MacAddress,IpAddress
First you need to specify the input object to which the property should be added by piping it to the Add-Member cmdlet.
Then, if you want the cmdlet to return the modified object, you should invoke it with the -PassThru argument:
When you use the PassThru parameter, Add-Member returns the
newly-extended object. Otherwise, this cmdlet does not generate any
output.
Here's a slightly modified version of your script:
$obj = $objItem | Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName -PassThru
However, since in your case you don't really need to save the output object in a new variable, you could also simply say:
$obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName
Try like this:
$objcol = #()
ForEach ($objItem in $colItems)
{
$obj = New-Object System.Object
$obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName
$obj | Add-Member -MemberType NoteProperty -Name MacAddress -Value $objItem.MacAddress
$obj | Add-Member -MemberType NoteProperty -Name IPAdress -Value $objitem.IpAddress
$objcol += $obj
}
Write-Output $objcol
As indicated by Enrico, Shay and Christian, you should specify the object on which Add-Member operates, either by piping the object to Add-Member or by explicitly specifying the object on the InputObject parameter. When adding multiple members using Add-Member I usually add the PassThru switch to avoid repeating the InputObject and to provide a visual cue.
ForEach ($objItem in $colItems) {
$obj = New-Object PSobject
$obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName -PassThru `
| Add-Member -MemberType NoteProperty -Name MacAddress -Value $objItem.MacAddress -PassThru `
| Add-Member -MemberType NoteProperty -Name IPAdress -Value $objitem.IpAddress -PassThru
}
You're assigning the result of Add-Member to a variable, not adding it to a property collection within $obj.
Instead of
$obj = Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName
Try this:
Add-Member -MemberType NoteProperty -Name ComputerName -Value $ComputerName -inputObject $obj

Resources