Write Text file data to csv - powershell

Below is my data in text file
Volume ### Ltr Label Fs Type Size Status Info
---------- --- ----------- ----- ---------- ------- --------- --------
Volume 0 Z DVD-ROM 0 B No Media
Volume 1 System Rese NTFS Partition 500 MB Healthy System
Volume 2 C SYS NTFS Partition 99 GB Healthy Boot
Volume 3 S SWAP NTFS Partition 6141 MB Healthy Pagefile
Volume 4 D DATA NTFS Partition 199 GB Healthy
Volume 5 E bit locker NTFS Partition 9 GB Healthy
Volume 6 F test NTFS Partition 10 GB Healthy
I have to write it in csv. tried below code but not able to handle to Blank values. e.g. for first row Label,Fs,Info values are blank
$data = get-content -Path "C:\d.txt"
$result = switch -Regex ($data) {
'^\s*Volume \d' {
$disk,$status,$Label,$Fs,$Type,$size,$Stat,$Info = $_.Trim() -split '\s{2,}'
[PsCustomObject]#{
'Server' = $server
'Volume ###' = $disk
'Ltr' = $status
'Label' = $Label
'Fs' = $Fs
'Type' = $Type
'Size' = $size
'Status' = $Stat
'Info' = $Info
}
}
}
# output on console screen
$result | Format-Table -AutoSize
# output to CSV file
$result | Export-Csv -Path "C:\$server.csv" -NoTypeInformation -Append
Output is coming like
Server Volume ### Ltr Label Fs Type Size Status Info
------ ---------- --- ----- -- ---- ---- ------ ----
AxxxxxxxxxxxxxP Volume 0 Z DVD-ROM 0 B No Media
AxxxxxxxxxxxxxP Volume 1 System Rese NTFS Partition 500 MB Healthy System
AxxxxxxxxxxxxxP Volume 2 C SYS NTFS Partition 99 GB Healthy Boot
AxxxxxxxxxxxxxP Volume 3 S SWAP NTFS Partition 6141 MB Healthy Pagefile
AxxxxxxxxxxxxxP Volume 4 D DATA NTFS Partition 199 GB Healthy
AxxxxxxxxxxxxxP Volume 5 E bit locker NTFS Partition 9 GB Healthy
AxxxxxxxxxxxxxP Volume 6 F test NTFS Partition 10 GB Healthy
Please let me know how to handle the spaces or any other way I can write it to csv
After executing #Mathias code
"Count","IsReadOnly","Keys","Values","IsFixedSize","SyncRoot","IsSynchronized"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
"9","False","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","System.Collections.Specialized.OrderedDictionary+OrderedDictionaryKeyValueCollection","False","System.Object","False"
this is what I am trying to do
$server = $env:COMPUTERNAME
# Start by reading in the table
# Replace this statement with `
$lines = Get-Content "c:\d.txt"
$lines = $lines -split '\r?\n'
# Assign the first two lines to separate variables
$header, $guardRails, $lines = $lines
# Split the header into individual column names
$columnNames = #(
$header.Trim() -split '\s{2,}' |ForEach-Object Trim
)
# Use regex to match all the individual `---` sequences, grab their offset + length
$columnOffsets = #(
[regex]::Matches($guardRails, '(?<!-)-+(?!-)') |Select Index,Length
)
# Parse the data based on the offsets located above
foreach($line in $lines){
# Prepare a dictionary to hold the column values, add the Server property straight away
$properties = [ordered]#{
Server = $server
}
# Now we just need to iterate over the lists of column headers and extract the corresponding substring from the line
for($i = 0; $i -lt $columnNames.Length; $i++){
# Grab the column name and offset
$propertyName = $columnNames[$i]
$offset = $columnOffsets[$i]
# Grab the substring corresponding to the column
$propertyValue = $line.Substring($offset.Index, $offset.Length).Trim()
# Add the information to our property dictionary
$properties[$propertyName] = $propertyValue
}
# Output a new object based on the properties we grabbed from the column data
[pscustomobject]$properties
# output on console screen
$properties | Format-Table -AutoSize
# output to CSV file
$properties | Export-Csv -Path "C:\$server.csv" -NoTypeInformation -Append
}

Use the line below the header ( ---------- --- -----------...) to detect at which offsets to parse data for a specific column name:
# Start by reading in the table
# Replace this statement with `$lines = Get-Content .\path\to\file.txt` in your script
$lines = #'
Volume ### Ltr Label Fs Type Size Status Info
---------- --- ----------- ----- ---------- ------- --------- --------
Volume 0 Z DVD-ROM 0 B No Media
Volume 1 System Rese NTFS Partition 500 MB Healthy System
Volume 2 C SYS NTFS Partition 99 GB Healthy Boot
Volume 3 S SWAP NTFS Partition 6141 MB Healthy Pagefile
Volume 4 D DATA NTFS Partition 199 GB Healthy
Volume 5 E bit locker NTFS Partition 9 GB Healthy
Volume 6 F test NTFS Partition 10 GB Healthy
'# -split '\r?\n'
# Assign the first two lines to separate variables
$header, $guardRails, $lines = $lines
# Split the header into individual column names
$columnNames = #(
$header.Trim() -split '\s{2,}' |ForEach-Object Trim
)
# Use regex to match all the individual `---` sequences, grab their offset + length
$columnOffsets = #(
[regex]::Matches($guardRails, '(?<!-)-+(?!-)') |Select Index,Length
)
# Parse the data based on the offsets located above
$volumeInfo = foreach($line in $lines){
# Prepare a dictionary to hold the column values, add the Server property straight away
$properties = [ordered]#{
Server = 'Server123'
}
# Now we just need to iterate over the lists of column headers and extract the corresponding substring from the line
for($i = 0; $i -lt $columnNames.Length; $i++){
# Grab the column name and offset
$propertyName = $columnNames[$i]
$offset = $columnOffsets[$i]
# Grab the substring corresponding to the column
$propertyValue = $line.Substring($offset.Index, $offset.Length).Trim()
# Add the information to our property dictionary
$properties[$propertyName] = $propertyValue
}
# Output a new object based on the properties we grabbed from the column data
[pscustomobject]$properties
}
$volumeInfo |Export-Csv path\to\output.csv -NoTypeInformation

Related

How to merge two tables that have same table structure? in PowerShell

In the following script, the outputs are displayed as two separate tables (each with two columns). How can I display both tables in a table with three columns?
# Create a hash table for File QTY
$Qty = #{}
# Create a hash table for file size
$Size = #{}
# Import all files into one $Files
$Files = Get-ChildItem 'D:\' -Filter *.* -Recurse -File
# Create a loop to check $file in $Files
foreach ($file in $Files) {
# Count files based on Extension
$Qty[$file.Extension] += 1
# Summarize file sizes based on their format
$Size[$file.Extension] += $file.Length / 1GB
}
# Show File QTY table
$Qty
# Show file size table
$Size
Like this:
Type Size Qyt
----- ----- -----
.jpg 10 GB 10000
.png 30 GB 30000
.tif 40 GB 40000
Extract the keys (the file extensions) from one table and use that as a "driver" to construct a new set of objects with the size and quantity values from the tables:
$Qty.psbase.Keys |Select-Object #{Name='Type';Expression={$_}},#{Name='Size';Expression={$Size[$_]}},#{Name='Qty'; Expression={$Qty[$_]}}
While you already got an answer that does exactly what you asked for, I would take a different approach, that doesn't need hashtables:
# Import all files into one $Files
$Files = Get-ChildItem 'D:\' -Filter *.* -Recurse -File
# Group files by extension and collect the per-extension information
$Files | Group-Object -Property Extension |
Select-Object #{ n = 'Type'; e = 'Name' },
#{ n = 'Size'; e = { '{0:0.00} GB' -f (($_.Group | Measure-Object Length -Sum).Sum / 1GB) } },
#{ n = 'Qty'; e = 'Count' }
Output is like:
Type Size Qty
----- ----- -----
.jpg 10.23 GB 10000
.png 30.07 GB 30000
.tif 40.52 GB 40000
Group-Object produces an object per unique file extension. It has a property Count, which is the number of grouped items, so we can use that directly. Its property Name contains the file extension. Its Group property contains all FileInfo objects from Get-ChildItem that have the same extension.
Using Select-Object we rename the Name property to Type and rename the Count property to Qty. We still need to calculate the total size per file type, which is done using Measure-Object -Sum. Using the format operator -f we pretty-print the result.
Replace {0:0.00} by {0:0} to remove the fractional digits from the Size column.
The syntax #{ n = ...; e = ... } is shortcut for #{ name = ...; expression = ... } to create a calculated property.
You may defer the formatting of the Size column to Format-Table, to be able to do additional processing based on the numeric value, e. g. sorting:
$files | Group-Object -Property Extension |
Select-Object #{ n = 'Type'; e = 'Name' },
#{ n = 'Size'; e = { ($_.Group | Measure-Object Length -Sum).Sum } },
#{ n = 'Qty'; e = 'Count' } |
Sort-Object Size -Descending |
Format-Table 'Type', #{ n = 'Size'; e = { '{0:0} GB' -f ($_.Size / 1GB) } }, 'Qty'

Get Column values from text file

Below is the text file data I have
Disk ### Status Size Free Dyn Gpt
-------- ------------- ------- ------- --- ---
Disk 0 Online 100 GB 1024 KB
Disk 1 Online 6144 MB 1024 KB
Disk 2 Online 200 GB 1024 KB
Disk 3 Online 10 GB 1024 KB
I want to put this data in csv like below, removing the last 2 columns and adding the server value
server Disk ### Status Size Free
------ -------- ------------- ------- -------
s1 Disk 0 Online 100 GB 1024 KB
s1 Disk 1 Online 6144 MB 1024 KB
s1 Disk 2 Online 200 GB 1024 KB
s1 Disk 3 Online 10 GB 1024 KB
below is the code
$list = Get-Content -Path "C:\server.txt"
foreach($server in $list)
{
$diskpart = cmd /c 'echo list disk | diskpart'
}
$Lines = $diskpart
$Out = $False
$Line=#()
ForEach ($Line In $Lines)
{
If ($Line -match 'DISKPART>')
{
$Out = $False
}
If ($Out -eq $True)
{
$Line | Out-File "C:\d.txt" -Append
}
If ($Line -match 'DISKPART>')
{
$Out = $True
}
}
$data = Import-Csv -Path C:\d.txt
I tried reading line using foreach,but getting output with headers and ---
Disk ### Status Size Free Dyn Gpt
---------------------------------------------------
Disk 0 Online 100 GB 1024 KB
Please let me know how can I extract columns from text file and append the servername to it. Need some idea to do this
One way of converting this into a CSV file is like below.
Assuming you have the output from diskpart in a string array $lines and the current server is called 's1' :
$result = switch -Regex ($lines) {
'^\s*Disk \d' {
$disk,$status,$size,$free = $_.Trim() -split '\s{2,}'
[PsCustomObject]#{
'Server' = $server # 's1' in this example
'Disk ###' = $disk
'Status' = $status
'Size' = $size
'Free' = $free
}
}
}
# output on console screen
$result | Format-Table -AutoSize
# output to CSV file
$result | Export-Csv -Path 'C:\diskinfo.csv' -NoTypeInformation
Result on screen:
Server Disk ### Status Size Free
------ -------- ------ ---- ----
s1 Disk 0 Online 100 GB 1024 KB
s1 Disk 1 Online 6144 MB 1024 KB
s1 Disk 2 Online 200 GB 1024 KB
s1 Disk 3 Online 10 GB 1024 KB
P.S. It is strongly not recommended to use Out-File to build a CSV file. Use that only if other options are not available. To create CSV use Export-Csv
If you need to do this on a series of computers, you might use this:
$list = Get-Content -Path "C:\server.txt"
# parameter -ComputerName can handle an array of computer names at once
$lines = Invoke-Command -ComputerName $list -ScriptBlock { "list disk" | diskpart }
$result = switch -Regex ($lines) {
'^On computer: (\w+)' { $server = $Matches[1].Trim() }
'^\s*Disk \d' {
$disk,$status,$size,$free = $_.Trim() -split '\s{2,}'
[PsCustomObject]#{
'Server' = $server
'Disk ###' = $disk
'Status' = $status
'Size' = $size
'Free' = $free
}
}
}
# output on console screen
$result | Format-Table -AutoSize
# output to CSV file
$result | Export-Csv -Path 'C:\diskinfo.csv' -NoTypeInformation

Convert txt to a object

I would like to convert the content of this file (the first two columns, BARCODE and LOCATION) to object, but I am stuck creating the object..
#BARCODE LOCATION LIBRARY STATUS COPY
#------- -------- ------- ------ ----
#L40001L8 slot 41 DRP_TAPE_DRPLTO Full Primary_Global
#L40002L8 slot 96 DRP_TAPE_DRPLTO Full Primary_Global
#L40034L8 IEPort2 DRP_TAPE_DRPLTO Full Primary_Global
$object= #{}
ForEach ($i in $(gc "C:\temp\tapes.txt"))
{
$object.BARCODE= ($i.ToString().Substring(0,8))
$object.LOCATION= ($i.ToString().Substring(0,19))
New-Object psobject -Property $object
}
Can anyone help me?
Use creating object from hashtable:
$ht = #{ 'key1' = 'value1'; key2 = 'value2' }
$ht['key3'] = 'value3'
$o = [PSCustomObject]$ht
When you have fixed-width columns:
$lines = #(#'
#BARCODE LOCATION LIBRARY STATUS COPY
#------- -------- ------- ------ ----
#L40001L8 slot 41 DRP_TAPE_DRPLTO Full Primary_Global
#L40002L8 slot 96 DRP_TAPE_DRPLTO Full Primary_Global
#L40034L8 IEPort2 DRP_TAPE_DRPLTO Full Primary_Global
'# -split "`r`n" )
# $lines = [System.IO.File]::ReadAllLines($filePath)
$lines = $lines | Select-Object -Skip 2
$objects = $lines | % {
return [PSCustomObject]#{
BARCODE = $_.Substring(1,8).Trim()
LOCATION = $_.Substring(13,8).Trim()
}
}
$objects
First of all, The content of the file looks very much like you have started off with an array of objects, then 'stringyfied' that using Format-Table and copy/pasted the output from the console to your textfile.
Why every line is now commented by placing # in front, I don't know. Perhaps you did this to format the question in SO?
What I'm trying to say is that if that is indeed what happened, then it would make much more sense to change the script you used to create the file by removing the Format-Table from the code and saving the object array using a cmdlet like Export-Csv, or by saving as JSON or XML..
Anyway, if you have no choice in the matter and need to convert that file back to objects, you can do this:
# read the file line-by-line and replace the whitespaces with a comma
$data = switch -Regex -File 'D:\Test\File1.txt' {
'^#[^-]' { $_.TrimStart("#") -replace '\s+', ',' }
# if the comment marks '#' are NOT in the file, use this instead
# '^[^-]' { $_ -replace '\s+', ',' }
}
$result = $data | ConvertFrom-Csv | Select-Object BARCODE, LOCATION
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'D:\Test\Output1.csv' -UseCulture -NoTypeInformation
Output on screen will look like
BARCODE LOCATION
------- --------
L40001L8 slot
L40002L8 slot
L40034L8 IEPort2
You can simply double-click the Output1.csv file to open in Excel

How to process out put of diskpart command using PowerShell

I'm trying to capture volume number from diskpart command output. At present I'm doing it like this:
Using batch script:
#echo off
set VOLNO=''
set DRIVE=C
for /f "tokens=2,3" %%a in ('echo list volume ^| diskpart') do (
if %%b==%DRIVE% set VOLNO=%%a
)
echo Volume No. for C Drive is: %VOLNO%
Output:
Volume No. for C Drive is: 2
Using PowerShell:
$dp = "list volume" | diskpart | ? { $_ -match "^ [^-]" }>
$dp
Output:
Volume ### Ltr Label Fs Type Size Status Info
Volume 0 E DVD-ROM 0 B No Media
Volume 1 System Rese NTFS Partition 350 MB Healthy System
Volume 2 C NTFS Partition 59 GB Healthy Boot
Volume 3 D New Volume NTFS Partition 49 GB Healthy
I want to capture the Volume-Number e.g. it's 2 for C:\ to a variable using PowerShell.
Adjust your regular expression so that you can extract the desired information via capturing groups. I generally recommend building custom objects from the extracted information, to facilitate further processing.
Something like this should do what you want:
$drive = 'c'
'list volume' | diskpart | Where-Object {
$_ -match 'Volume (\d+)\s+([a-z])\s+'
} | ForEach-Object {
New-Object -Type PSObject -Property #{
'DriveLetter' = $matches[2]
'VolumeNumber' = [int]$matches[1]
}
} | Where-Object {
$_.DriveLetter -eq $drive
} | Select-Object -Expand VolumeNumber
A concise solution that uses -match to find the line of interest and the unary form of -split to split it into whitespace-separated tokens that can be accessed by index:
$drive = 'C'
$volNo = (-split (('list volume' | diskpart) -match " $drive "))[1]
"Volume No. for $drive Drive is: $volNo"
Note that diskpart must be run from an elevated session.

Filter logfile to create a csv report using PowerShell

I have a NetApp log output in a log file which is the below format.
DeviceDetails.log file content
/vol/DBCXARCHIVE002_E_Q22014_journal/DBCXARCHIVE002_E_Q22014_journal 1.0t (1149038714880) (r/w, online, mapped)
Comment: " "
Serial#: e3eOF4y4SRrc
Share: none
Space Reservation: enabled (not honored by containing Aggregate)
Multiprotocol Type: windows_2008
Maps: DBCXARCHIVE003=33
Occupied Size: 1004.0g (1077986099200)
Creation Time: Wed Apr 30 20:14:51 IST 2014
Cluster Shared Volume Information: 0x0
Read-Only: disabled
/vol/DBCXARCHIVE002_E_Q32014_journal/DBCXARCHIVE002_E_Q32014_journal 900.1g (966429273600) (r/w, online, mapped)
Comment: " "
Serial#: e3eOF507DSuU
Share: none
Space Reservation: enabled (not honored by containing Aggregate)
Multiprotocol Type: windows_2008
Maps: DBCXARCHIVE003=34
Occupied Size: 716.7g (769556951040)
Creation Time: Tue Aug 12 20:24:14 IST 2014
Cluster Shared Volume Information: 0x0
Read-Only: disabled
Wherein the output is of only 2 devices , it has more than x devices appended in the log file.
I just need 4 details from each module ,
The first line contains 3 needed details
Device Name : /vol/DBCXARCHIVE002_E_Q22014_journal/DBCXARCHIVE002_E_Q22014_journal
Total Capacity : 1.0t (1149038714880)
Status : (r/w, online, mapped)
And the 4th Detail I need is Occupied Size: 1004.0g (1077986099200)
So the CSV output should look like below :
I am not just a beginner at coding and trying to achieve this with the below code, it does not help much though :/
$logfile = Get-Content .\DeviceDetails.log
$l1 = $logfile | select-string "/vol"
$l2 = $logfile | select-string "Occupied Size: "
$objs =#()
$l1 | ForEach {
$o = $_
$l2 | ForEach {
$o1 = $_
$Object22 = New-Object PSObject -Property #{
'LUN Name , Total Space, Status, Occupied Size' = "$o"
'Occupied Size' = "$o1"
}
}
$objs += $Object22
}
$objs
$obj = $null # variable to store each output object temporarily
Get-Content .\t.txt | ForEach-Object { # loop over input lines
if ($_ -match '^\s*(/vol.+?)\s+(.+? \(.+?\))\s+(\(.+?\))') {
# Create a custom object with all properties of interest,
# and store it in the $obj variable created above.
# What the regex's capture groups - (...) - captured is available in the
# the automatic $Matches variable via indices starting at 1.
$obj = [pscustomobject] #{
'Device Name' = $Matches[1]
'Total Space' = $Matches[2]
'Status' = $Matches[3]
'Occupied Size' = $null # filled below
}
} elseif ($_ -match '\bOccupied Size: (.*)') {
# Set the 'Occupied Size' property value...
$obj.'Occupied Size' = $Matches[1]
# ... and output the complete object.
$obj
}
} | Export-Csv -NoTypeInformation out.csv
- Note that Export-Csv defaults to ASCII output encoding; change that with the -Encoding parameter.
- To extract only the numbers inside (...) for the Total Space and Occupied Size columns, use
$_ -match '^\s*(/vol.+?)\s+.+?\s+\((.+?)\)\s+(\(.+?\))' and
$_ -match '\bOccupied Size: .+? \((.*)\)' instead.
Note how this solution processes the input file line by line, which keeps memory use down, though generally at the expense of performance.
As for what you tried:
You collect the entire input file as an array in memory ($logfile = Get-Content .\DeviceDetails.log)
You then filter this array twice into parallel arrays, containing corresponding lines of interest.
Things go wrong when you attempt to nest the processing of these 2 arrays. Instead of nesting, you must enumerate them in parallel, as their corresponding indices contain matching entries.
Additionally:
a line such as 'LUN Name , Total Space, Status, Occupied Size' = "$o" creates a single property named LUN Name , Total Space, Status, Occupied Size, which is not the intent.
in order to create distinct properties (to be reflected as distinct colums in CSV output), you must create them as such, which requires parsing the input into distinct values accordingly.