Cover flat file using mapping to table (eg. csv) - powershell

I have following several hundred entries in text file like this:
DisplayName : John, Smith
UPN : MY3043241#domain.local
Status : DeviceOk
DeviceID : ApplC39HJ3JPDTF9
DeviceEnableOutboundSMS : False
DeviceMobileOperator :
DeviceAccessState : Allowed
DeviceAccessStateReason : Global
DeviceAccessControlRule :
DeviceType : iPhone
DeviceUserAgent : Apple-iPhone4C1/902.206
DeviceModel : iPhone
... about 1500 entries of the above with blank line between each.
I'm looking to create a table with following headers from the above:
DisplayName,UPN,Status,DeviceID,DeviceEnableOutboundSMS,DeviceMobileOperator,DeviceAccessState,DeviceAccessStateReason,DeviceAccessControlRule,DeviceType,DeviceUserAgent,DeviceModel
The question is, is there a tool or some easy way to do this in excel or other application. I know it is easy task to write a simple algorithm, unfortunately I cannot go that route. Powershell would be an option but I'm not good at so if you have any tips on how to approach this that route please, let me know.

Although I am a big fan on Powershell one-liners, it wouldn't be of much help to someone trying to learn or start out with it. More so getting buy-in, in a corporate setting.
I have written a cmdlet, documentation included, to get you started.
function Import-UserDevice {
Param
(
# Path of the text file we are importing records from.
[string] $Path
)
if (-not (Test-Path -Path $Path)) { throw "Data file not found: $Path" }
# Use the StreamReader for efficiency.
$reader = [System.IO.File]::OpenText($Path)
# Create the initial record.
$entry = New-Object -TypeName psobject
while(-not $reader.EndOfStream) {
# Trimming is necessary to remove empty spaces.
$line = $reader.ReadLine().Trim()
# An empty line would indicate we need to start a new record.
if ($line.Length -le 0 -and -not $reader.EndOfStream) {
# Output the completed record and prepare a new record.
$entry
$entry = New-Object -TypeName psobject
continue
}
# Split the line through ':' to get properties names and values.
$entry | Add-Member -MemberType NoteProperty -Name $line.Split(':')[0].Trim() -Value $line.Split(':')[1].Trim()
}
# Output the residual record.
$entry
# Close the file.
$reader.Close()
}
Here's an example of how you could use it to export records to CSV.
Import-UserDevice -Path C:\temp\data.txt | Export-Csv C:\TEMP\report.csv -NoTypeInformation

Powershell answer here... I used a test file C:\Temp\test.txt which contains:
DisplayName : John, Smith
UPN : MY3043241#domain.local
Status : DeviceOk
DeviceID : ApplC39HJ3JPDTF9
DeviceEnableOutboundSMS : False
DeviceMobileOperator :
DeviceAccessState : Allowed
DeviceAccessStateReason : Global
DeviceAccessControlRule :
DeviceType : iPhone
DeviceUserAgent : Apple-iPhone4C1/902.206
DeviceModel : iPhone
DisplayName : Mary, Anderson
UPN : AR456789#domain.local
Status : DeviceOk
DeviceID : ApplC39HJ3JPDTF8
DeviceEnableOutboundSMS : False
DeviceMobileOperator :
DeviceAccessState : Allowed
DeviceAccessStateReason : Global
DeviceAccessControlRule :
DeviceType : iPhone
DeviceUserAgent : Apple-iPhone4C1/902.206
DeviceModel : iPhone
So that I could have multiple records to parse. Then I ran it against this script which creates an empty array $users, gets the content of that file 13 lines at a time (12 fields + the empty line). Then it creates a custom object with no properties. Then for each of the 13 lines, if the line is not empty it creates a new property for that object we just created, where the name is everything before the : and the value is everything after it (with spaces removed from the end of the name and value). Then it adds that object to the array.
$users=#()
gc c:\temp\test.log -ReadCount 13|%{
$User = new-object psobject
$_|?{!([string]::IsNullOrEmpty($_))}|%{
Add-Member -InputObject $User -MemberType NoteProperty -Name ($_.Split(":")[0].TrimEnd(" ")) -Value ($_.Split(":")[1].TrimEnd(" "))
}
$users+=$User
}
Once you have the array $Users filled you could do something like:
$Users | Export-CSV C:\Temp\NewFile.csv -notypeinfo
That gives you a CSV that you would expect it to.

For an input file with just a couple hundred records I'd probably read the entire file, split it at empty lines, split the text blocks at line breaks, and the lines at colons. Somewhat like this:
$infile = 'C:\path\to\input.txt'
$outfile = 'C:\path\to\output.csv'
[IO.File]::ReadAllText($infile).Trim() -split "`r`n`r`n" | % {
$o = New-Object -Type PSObject
$_.Trim() -split "`r`n" | % {
$a = "$_ :" -split '\s*:\s*'
$o | Add-Member -Type NoteProperty -Name $a[0] -Value $a[1]
}
$o
} | Export-Csv $outfile -NoType

Related

Trying to extract specific text and merge output with existing output

I want to extract text from a .txt file. The way the file is layed out is in this format (below first block). Optimally, I would like for the powershell script to take the content of username and votecount and output them side by side. With an integer of 25>= add the letter D beside it. With the output adding itself to a pre-existing output file. Say this week is week 1. And testuser voted 25 times. They should have the output "testuser" 25D. But say in week 2 they voted 24 times. Then it should be "testuser" 49D. However say they had 25 again. Output should then be "testuser" 50DD or 50D2?.. I have what I think should work as an initial baseline for the script which in itself doesn't work.. But combining an output with a pre existing output is beyond my capability. This needs to parse an entire txt file of some 100+ people. So imagine there's like an extra 100 users..
{
"username": "testuser",
"votecount": "42",
"votesclaimed": "0",
"lastvotetime": "2022-11-04 09:08:29",
"steamid": "00000000000000000000"
}
Below is what I am working with.
Get-Content -Raw C:\Users\--------\Desktop\votes.txt |
ConvertFrom-txt |
ForEach-Object {
[pscustomobject] #{
UserName = $_.username
VoteCount = '{0}{1}' -f $_.votecount, ('', 'D')[[int] $_.votecount -gt 25]
}
} |
Export-Csv -NoTypeInformation -Encoding utf8 C:\Users\---------\Desktop\outvotes.csv
Try following :
$match = Select-String -Path "c:\temp\test.txt" -Pattern '^\s*"(?<key>[^"]+)"\s*:\s*"(?<value>[^"]+)'
$table = [System.Collections.ArrayList]::new()
foreach( $row in $match.Matches )
{
$key = $row.Groups["key"].Value
$value = $row.Groups["value"].Value
if($key -eq "username") {
$newRow = New-Object -TypeName psobject
$table.Add($newRow) | Out-Null
}
$newRow | Add-Member -NotePropertyName $key -NotePropertyValue $value
}
$table | Format-Table
$groups = $table | Group-Object {$_.username}

How can i convert the output of prnmngr into custom object?

output of cscript prnmngr.vbs -l
Server name abcd
Printer name \\abcd.com\mailroom
Share name mailroom
Driver name Canon iR-ADV 4225/4235 UFR II
Port name mailroom.com
Comment
Location
Print processor winprint
Data type RAW
Parameters
Attributes 536
Priority 1
Default priority 0
Average pages per minute 0
Printer status Idle
Extended printer status Unknown
Detected error state Unknown
Extended detected error state Unknown
Server name cdef
Printer name \\cdfet.com\mailroom3
Share name mailroom3
Driver name Canon iR-ADV 4225/4235 UFR II
Port name mailroomxxx.com
Comment
Location
Print processor winprint
Data type RAW
Parameters
Attributes 536
Priority 1
Default priority 0
Average pages per minute 0
Printer status Idle
Extended printer status Unknown
Detected error state Unknown
Extended detected error state Unknown
something like (note the modified output property names):
$CustomPrinterobjects = New-Object –TypeName PSObject
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name ComputerName –Value "$a"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name Name –Value "$b"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name ShareName –Value "$c"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name DriverName –Value "$d"
$CustomPrinterobjects | Add-Member –MemberType NoteProperty –Name PortName –Value "$e"
where $a, $b, $c,$d, $e represent property values looped over the output of cscript prnmngr.vbs -l
Kory Gill helpfully suggests using the W8+ / W2K12+ Get-Printer cmdlet instead.
Similarly, kuujinbo suggests Get-WmiObject -Class Win32_Printer for earlier OS versions.
In the spirit of PowerShell, both commands returns objects whose properties you can access directly - no need for text parsing.
In case you still have a need to parse the output from cscript prnmngr.vbs -l (if it provides extra information that the cited commands do not), use the following approach - note how much effort is needed to parse the textual output into structured objects:
Given that all information is space-separated and the property name part of each line is composed of varying numbers of tokens, the only predictable way to parse the text is to:
maintain a collection of well-known property names
consider whatever comes after the property name on the line the value.
A PSv3+ solution:
# Map the input property names of interest to output property names,
# using a hashtable.
$propNameMap = #{
'Server name ' = 'ComputerName'
'Printer name ' = 'Name'
'Share name ' = 'ShareName'
'Driver name ' = 'DriverName'
'Port name ' = 'PortName'
}
# Split the output of `cscript prnmngr.vbs -l` into paragraphs and
# parse each paragaph into a custom object with only the properties of interest.
$customPrinterObjs = (cscript prnmngr.vbs -l) -join "`n" -split "`n`n" | ForEach-Object {
$ohtFields = [ordered] #{}
foreach ($line in $_ -split "`n") {
foreach ($propNamePair in $propNameMap.GetEnumerator()) {
if ($line -like ($propNamePair.Key + '*')) {
$ohtFields[$propNamePair.Value] = $line.Substring($propNamePair.Key.length)
}
}
}
[pscustomobject] $ohtFields
}
# Output the resulting custom objects.
$customPrinterObjs
With your sample input, the above yields a 2-element [pscustomobject] array:
ComputerName : abcd
Name : \\abcd.com\mailroom
ShareName : mailroom
DriverName : Canon iR-ADV 4225/4235 UFR II
PortName : mailroom.com
ComputerName : cdef
Name : \\cdfet.com\mailroom3
ShareName : mailroom3
DriverName : Canon iR-ADV 4225/4235 UFR II
PortName : mailroomxxx.com
(cscript prnmngr.vbs -l) -join "`n" collects the output lines from cscript prnmngr.vbs -l in an array and then joins them to form a single multiline string.
-split "`n`n" splits the resulting multiline string into paragraphs, each representing a single printer's properties.
The ForEach-Object script block then processes each printer's properties paragraph:
foreach($line in $_ -split "`n") splits the multiline paragraph back into an array of lines and loops over them.
$ohtFields = [ordered] #{} initializes an empty ordered hashtable (where entries are reflected in definition order on output) to serve as the basis for creating a custom object.
The inner foreach loop then checks each line for containing a property of interest, and, if so, adds an entry to the output hashtable with the output property name and the property value, which is the part that follows the well-known property name on the line.
Finally, the ordered hashtable is output as a custom object by casting it to [pscustomobject].
Calling a vbscript-script from Powershell just feels like a "re-write it in Powershell sort of thing" (not to be judgy). #KoryGill asks an interesting question, "Why not just call Get-Printer"?
But, to your question, you can totally turn that into an object, but you'll have to do some text manipulation:
$printer_stuff = $(cscript prnmngr.vbs -l)
This will create a string array named $printer_stuff where each element has a separate line of output on it. You'll want to make a list of tokens for each printer property e.g., server name, printer name, etc. You'll iterate over the output (in the string array) copying the properties to a PSObject. Here is a cheap example to demonstrate the point:
## Make a list of tokens
$tokens = #('Server name', 'Printer name', 'Share name')
## This will be your printer object
$printer = New-Object -TypeName PSObject
## Parsing the string array and stuffing the good bits into your printer object
foreach ($thing in $printer_stuff[0..17]) {
foreach ($token in $tokens) {
if ($thing -match $token) {
Add-Member -InputObject $printer -MemberType NoteProperty -Name $token -Value $thing.Replace($token, '')
}
}
}
## Here is your object...
$printer
If the prnmgr.vbs script will be returning information on a bunch of printers, you can stuff that $printer object into an an array:
$printers = #()
....
$printers += $printer
You can pull each printer's data out of the string array with something like...
$min = 0
$max = $size
while ($min -lt $printer_stuff.length) {
$printer_stuff[$min..$max]
$min = $max + 1
$max += $size
}
As you can see, this is a big pain in the ass, which is why I suggest just to re-write the thing in Powershell. If you're slick enough to do this bit, you're slick enough to port the vbscript-script.
Good Luck,
A-

How do I iterate through JSON array in powershell

How do I iterate through JSON array which is converted to PSCustomObject with ConvertFrom-JSON? Using foreach does not work.
$jsonArray ='[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'
$json = convertfrom-json $jsonArray
$json | foreach {$_}
Returns
privateKeyLocation
------------------
C:\ProgramData\docker\certs.d\key.pem
Enumerator though says there are 3 members of array
>$json.Count
3
The problem that you are having is not specific to it being a JSON array, it has to do with how custom objects in an array are displayed by default. The simplest answer is to pipe it to Format-List (or FL for short).
PS C:\Users\TMTech> $JSON|FL
privateKeyLocation : C:\ProgramData\docker\certs.d\key.pem
publicKeyLocation : C:\ProgramData\docker\certs.d\cert.pem
publicKeyCALocation : C:\ProgramData\docker\certs.d\ca.pem
Aside from that, when PowerShell outputs an array of objects it bases the columns that it displays upon the properties of the first object in the array. In your case that object has one property named 'privateKeyLocation', so that is the only column that appears, and since the other two objects do not have that property it does not display anything for them. If you want to keep it as a table you could gather all potential properties, and add them to the first item with null values, and that would allow you to display it as a table, but it still wouldn't look very good:
$json|%{$_.psobject.properties.name}|select -Unique|?{$_ -notin $json[0].psobject.Properties.Name}|%{Add-Member -InputObject $JSON[0] -NotePropertyName $_ -NotePropertyValue $null}
Then you can output as a table and get everything:
PS C:\Users\TMTech> $json
privateKeyLocation publicKeyLocation publicKeyCALocation
------------------ ----------------- -------------------
C:\ProgramData\docker\certs.d\key.pem
C:\ProgramData\docker\certs.d\cert.pem
C:\ProgramData\docker\certs.d\ca.pem
Edit: To get the value of each object in this case is tricky, because the property that you want to expand keeps changing for each object. There's two ways to do this that I can think of, what I would consider the right way, and then there's the easy way. The right way to do it would be to determine the property that you want to expand, and then reference that property directly:
$JSON |%{
$PropName = $_.PSObject.Properties.Name
$_.$PropName
}
That'll do what you want, but I think easier would be to pipe to Format-List, then Out-String, wrap the whole thing in parenthesis, split on new lines and replace everything up to : which should just leave you with the paths you want.
($JSON|FL|Out-String) -split '[\r\n]+' -replace '(?m)^.+ : '|?{$_}
Interesting enough. I responded to this exact question from the same OP on another forum. Though my response was just RegEx and be done with it, with no additional conversion.
Of course there are several ways to do this. The below is just what I came up with.
$jsonArray = '[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'
([regex]::Matches($jsonArray,'(?<=\").:\\[^\"]+(?=\")').Value) -replace '\\\\','\' `
| ForEach {
If (Test-Path -Path $_)
{"path $_ found"}
Else {Write-Warning "Path $_ not found"}
}
WARNING: Path C:\ProgramData\docker\certs.d\key.pem not found
WARNING: Path C:\ProgramData\docker\certs.d\cert.pem not found
WARNING: Path C:\ProgramData\docker\certs.d\ca.pem not found
So, maybe not as elegant as what was posted here, but it would get the OP where they wanted to be.
So, consolidating everything TheMadTechnician gave and what the OP is after, and attempting to make it as concise as possible, would give the OP the below (I added a element to show a positive response):
Clear-Host
($jsonArray = #'
[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"},
{"publicKeyTestFileLocation" : "D:\\Temp\\test.txt"}]
'# | ConvertFrom-Json | Format-List | Out-String) -split '[\r\n]+' -replace '(?m)^.+ : '`
| Where-Object {$_} | ForEach {
If(Test-Path -Path $_){"The path $_ was found"}
Else{Write-Warning -Message "The path $_ was not found}"}
}
WARNING: The path C:\ProgramData\docker\certs.d\key.pem was not found}
WARNING: The path C:\ProgramData\docker\certs.d\cert.pem was not found}
WARNING: The path C:\ProgramData\docker\certs.d\ca.pem was not found}
The path D:\Temp\test.txt was found
Which one is more to his liking is a matter of the OP choice of course.
The performance between the two varied on each test run, but the fastest time using the straight RegEx approach was:
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 43
Ticks : 439652
TotalDays : 5.08856481481481E-07
TotalHours : 1.22125555555556E-05
TotalMinutes : 0.000732753333333333
TotalSeconds : 0.0439652
TotalMilliseconds : 43.9652
and the fastest on the consolidated version here was:
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 54
Ticks : 547810
TotalDays : 6.34039351851852E-07
TotalHours : 1.52169444444444E-05
TotalMinutes : 0.000913016666666667
TotalSeconds : 0.054781
TotalMilliseconds : 54.781
Updating to add iRon's take on this topic
So this...
$jsonArray ='[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'
$json = convertfrom-json $jsonArray
$json | ForEach {
$Key = $_.psobject.properties.name;
"Testing for key " + $_.$Key
Test-Path -Path $_.$Key
}
Testing for key C:\ProgramData\docker\certs.d\key.pem
False
Testing for key C:\ProgramData\docker\certs.d\cert.pem
False
Testing for key C:\ProgramData\docker\certs.d\ca.pem
False
... and this:
('[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]' `
| convertfrom-json) | ForEach {
$Key = $_.psobject.properties.name;
"Testing for key " + $_.$Key
Test-Path -Path $_.$Key
}
Testing for key C:\ProgramData\docker\certs.d\key.pem
False
Testing for key C:\ProgramData\docker\certs.d\cert.pem
False
Testing for key C:\ProgramData\docker\certs.d\ca.pem
False
Most simple way, should be like this
$ret ='[your json]'
$ret | ConvertFrom-Json
$data = $ret | ConvertFrom-Json
foreach($data in $ret | ConvertFrom-Json) {
Write-Host $data;
}
You can index into the array. Check out $json.GetType()
$jsonArray ='[{"privateKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\key.pem"},
{"publicKeyLocation" : "C:\\ProgramData\\docker\\certs.d\\cert.pem"},
{"publicKeyCALocation" : "C:\\ProgramData\\docker\\certs.d\\ca.pem"}]'
$json = convertfrom-json $jsonArray
foreach($i in 0..($json.Count-1)){
$json[$i] | out-host
$i++
}
You can use ForEach-Object i.e:
$json | ForEach-Object -Process { Write-Hoste $_; }
That's I believe the simplest way and gives you easy access to properties if array contains objects with other properties.

Powershell - Insert column in between specific columns in csv file

I have 2 csv files
First file:
firstName,secondName
1234,Value1
2345,Value1
3456,Value1
4567,Value3
7645,Value3
Second file:
firstName,fileSplitter,Csv2ColumnOne,Csv2ColumnTwo,Csv2ColumnThree
1234,,1234,abc,Value1
1234,,1234,asd,Value1
3456,,3456,qwe,Value1
4567,,4567,mnb,Value1
I want to insert column secondName in the second file in between columns firstName and fileSplitter.
The result should look like this:
firstName,secondName,fileSplitter,Csv2ColumnOne,Csv2ColumnTwo,Csv2ColumnThree
1234,Value1,,1234,abc,Value1
1234,Value1,,1234,asd,Value1
3456,Value1,,3456,qwe,Value1
4567,Value3,,4567,mnb,Value1
I'm trying the following code:
Function InsertColumnInBetweenColumns
{
Param ($FirstFileFirstColumnTitle, $firstFile, [string]$1stColumnName, [string]$2ndColumnName, [string]$columnMergedFileBeforeInput)
Write-Host "Creating hash table with columns values `"$1stColumnName`" `"$2ndColumnName`" From $OimFileWithMatches"
$hashFirstFileTwoColumns = #{}
Import-Csv $firstFile | ForEach-Object {$hashFirstFileTwoColumns[$_.$1stColumnName] = $_.$2ndColumnName}
Write-Host "Complete."
Write-Host "Appending Merge file with column `"$2ndColumnName`" from file $secondCsvFileWithLocalPath"
Import-Csv $outputCsvFileWithLocalPath | Select-Object $columnMergedFileBeforeInput, #{n=$2ndColumnName; e={
if ($hashFirstFileTwoColumns.ContainsKey($_.$FirstFileFirstColumnTitle)) {
$hashFirstFileTwoColumns[$_.$FirstFileFirstColumnTitle]
} Else {
'Not Found'
}}}, * | Export-Csv "$outputCsvFileWithLocalPath-temp" -NoType -Force
Move-Item "$outputCsvFileWithLocalPath-temp" $outputCsvFileWithLocalPath -Force
Write-Host "Complete."
Write-Host ""
}
This function will be called in a for loop for each column found in the first file (can contain an indefinite number). For testing, I am only using 2 columns from the first file.
I'm getting an error output resulting the following:
Select : Property cannot be processed because property "firstName" already exists.
At C:\Scripts\Tests\Compare2CsvFilesOutput1WithMatchesOnly.ps1:490 char:43
+ Import-Csv $outputCsvFileWithLocalPath | Select $columnMergedFileBeforeInput, # ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (#{firstName=L...ntName=asdfas}:PSObject) [Select-Object], PSArgume
ntException
+ FullyQualifiedErrorId : AlreadyExistingUserSpecifiedPropertyNoExpand,Microsoft.PowerShell.Commands.SelectObjectC
ommand
I know the issue is where it says Select-Object $columnMergedFileBeforeInput,.
How can I get the loop statement to insert the column in between the before column (name is specified), and append the rest using *?
Update
Just an fyi, changing this line Select-Object $columnMergedFileBeforeInput, #{n=$2ndColumnName..... to this line Select-Object #{n=$2ndColumnName..... works, it just attaches the columns out of order. That is why I'm trying to insert the column in between. Maybe if i do it this way but insert the columns in backwards using the for loop, this would work...
Not sure if this is the most efficient way to do it, but it should do the trick. It just adds the property to the record from file2, then reorders the output so secondName is the second column. You can output results to csv where required too (ConvertTo-Csv).
$file1 = Import-Csv -Path file1.csv
$file2 = Import-Csv -Path file2.csv
$results = #()
ForEach ($record In $file2) {
Add-Member -InputObject $record -MemberType NoteProperty -Name secondName -Value $($file1 | ? { $_.firstName -eq $record.firstName } | Select -ExpandProperty secondName)
$results += $record
}
$results | Select-Object -Property firstName,secondName,fileSplitter,Csv2ColumnOne,Csv2ColumnTwo,Csv2ColumnThree
I've created the following function. What it does is find the match (in this case "firstname") and adds the matching columnname to the new array afther the columnname on which the match is made (little difficult to explain in my poor English).
function Add-ColumnAfterMatchingColumn{
[CmdletBinding()]
param(
[string]$MainFile,
[string]$MatchingFile,
[string]$MatchColumnName,
[string]$MatchingColumnName
)
# Import data from two files
$file1 = Import-Csv -Path $MainFile
$file2 = Import-Csv -Path $MatchingFile
# Find column names and order them
$columnnames = $file2 | gm | where {$_.MemberType -like "NoteProperty"} | Select Name | %{$_.Name}
[array]::Reverse($columnnames)
# Find $MatchColumnName index and put the $MatchingColumnName after it
$MatchColumnNameIndex = [array]::IndexOf($columnnames, $MatchColumnName)
if($MatchColumnNameIndex -eq -1){
$MatchColumnNameIndex = 0
}
$columnnames = $columnnames[0..$MatchColumnNameIndex] + $MatchingColumnName + $columnnames[($MatchColumnNameIndex+1)..($columnnames.Length -1)]
$returnObject = #()
foreach ($item in $file2){
# Find corresponding value MatchingColumnName in $file1 and add it to the current item
$item | Add-Member -Name "$MatchingColumnName" -Value ($file1 | ?{$_."$($MatchColumnName)" -eq $item."$($MatchColumnName)"})."$MatchingColumnName" -MemberType NoteProperty
# Add current item to the returnObject array, in the correct order
$newItem = New-Object psobject
foreach ($columnname in [string[]]$columnnames){
$newItem | Add-Member -Name $columnname -Value $item."$columnname" -MemberType NoteProperty
}
$returnObject += $newItem
}
return $returnObject
}
When you run this function you will have the following output:
Add-ColumnAfterMatchingColumn -MainFile C:\Temp\file1.csv -MatchingFile C:\Temp\file2.csv -MatchColumnName "firstname" -MatchingColumnName "secondname" | ft
firstName secondname fileSplitter Csv2ColumnTwo Csv2ColumnThree Csv2ColumnOne
--------- ---------- ------------ ------------- --------------- -------------
1234 Value1 abc Value1 1234
1234 Value1 asd Value1 1234
3456 Value1 qwe Value1 3456
4567 Value3 mnb Value1 4567

Trying to write a data set from a SQL query out to a CSV file in Powershell

I would like to create a CSV file (with no headers if possible) from a SQL query I make. My powershell is below. It reads the DB no problem and I see row results come back from printint to stdout. But I can't seem to figure out how to get the results into a CSV file.
The txt file created from my powershell only has one line it:
TYPE System.Int32
Which seems to me like it is just outputing the data type or something. It seems odd it is In32 but maybe that is because it is a pointer to the object?
I saw one article that used Tables[0] when using a data set but when I replaced my last line with
$QueryResults.Tables[0] | export-csv c:\tests.txt
It gave me an error saying it couldn't bind an argument to parameter because it was null.
function Get-DatabaseData {
param (
[string]$connectionString,
[string]$query,
[switch]$isSQLServer
)
if ($isSQLServer) {
Write-Verbose 'in SQL Server mode'
$connection = New-Object -TypeName System.Data.SqlClient.SqlConnection
} else {
Write-Verbose 'in OleDB mode'
$connection = New-Object -TypeName System.Data.OleDb.OleDbConnection
}
$connection.ConnectionString = $connectionString
$command = $connection.CreateCommand()
$command.CommandText = $query
if ($isSQLServer) {
$adapter = New-Object -TypeName System.Data.SqlClient.SqlDataAdapter $command
} else {
$adapter = New-Object -TypeName System.Data.OleDb.OleDbDataAdapter $command
}
$dataset = New-Object -TypeName System.Data.DataSet
$adapter.Fill($dataset)
$dataset.Tables[0]
}
$QueryResults = Get-DatabaseData -verbose -connectionString 'Server=localhost\SQLEXPRESS;;uid=sa; pwd=Mypwd;Database=DanTest;Integrated Security=False;' -isSQLServer -query "SELECT * FROM Computers"
foreach($MyRow in $QueryResults) {
write-output $MyRow.computer
write-output $MyRow.osversion
}
$QueryResults | export-csv c:\tests.txt
I added a line to print $QueryResults to stdout and below is the output in the console of that and the write form my for loop to show what my $QueryResults has.
5
computer : localhost
osversion :
biosserial :
osarchitecture :
procarchitecture :
computer : localhost
osversion :
biosserial :
osarchitecture :
procarchitecture :
computer : not-online
osversion :
biosserial :
osarchitecture :
procarchitecture :
computer :
osversion : win8
biosserial :
osarchitecture :
procarchitecture :
computer : dano
osversion : win8
biosserial :
osarchitecture :
procarchitecture :
localhost
localhost
not-online
win8
dano
win8
You might have looping issues as from the sounds of it your $QueryResults might not contain the data you are looking for. What does $QueryResults look like while in the console? Once you address that this should take care of the output for you.
From TechNet
By default, the first line of the CSV file contains "#TYPE " followed by the fully-qualified name of the type of the object.
To get around that you would just use the -NoTypeInformation switch like Alroc suggested. However you also mentioned removing the header which would require a different approach.
$QueryResults | ConvertTo-CSV -NoTypeInformation | Select-Object -Skip 1 | Set-Content "c:\tests.txt"
or something slightly different with the same result.
$QueryResults | ConvertTo-CSV | Select -Skip 2 | Set-Content "c:\tests.txt"
Convert the $QueryResults to a csv object. However you choose to we omit the lines containing the type information and the header / title row. Should just have nothing but data at that point.
Update From Discussion
The ConvertTo-CSV and Export-CSV were not showing expected data but nothing except type information. I didnt really notice at first but the output of $QueryResults contained a first line that was just the number 5. Presumably it was the # of records from the result dataset. If we skip the first record of $QueryResults then we are left with just the data needed. Might be better to look into ther reason that first line is there ( maybe there is a way to supress it. ). As the question stands currently we address this as follows. The first skip is to ignore the "5" line and the second is to remove the header from the csv output.
$QueryResults | Select -Skip 1 | Convert-ToCSV -NoTypeInformation | Select-Object -Skip 1 | Set-Content "c:\tests.txt"
If you want just the data in the CSV file, pass the -NoTypeInformation switch into Export-CSV.