ssis package importing data from email attachments to excel - ssis

I have got 100 plus email in a folder and all of them have got an attachment. I want to create a package which will copy the data from all email attachments in one excel sheet and also add a column which will have the received dates of those emails against the data.Can any one guide me how to do it with help of ssis package.

Not ssis but it should produce the desired result. Just change the path on the last line.
function Get-Attachment
{
[CmdletBinding()]
Param
(
[Parameter(ParameterSetName="Path", Position=0, Mandatory=$True)]
[String]$Path,
[Parameter(ParameterSetName="LiteralPath", Mandatory=$True)]
[String]$LiteralPath,
[Parameter(ParameterSetName="FileInfo", Mandatory=$True, ValueFromPipeline=$True)]
[System.IO.FileInfo]$Item
)
Begin
{
remove-item $Path\attachments.xlsx
$outlook = New-Object -ComObject Outlook.Application
$excel = New-Object -ComObject Excel.Application
$excel.visible = $true
$workbook = $excel.Workbooks.add()
$s1 = $workbook.Sheets.add()
$s1.name = "Attachments"
$cells= $s1.Cells
$s1.range("A1:A1").font.bold = "true"
$s1.range("A1:A1").cells="RecDate"
$s1.range("B1:B1").cells="Data"
$s1.range("B1:B1").font.bold = "true"
}
Process
{
switch ($PSCmdlet.ParameterSetName)
{
"Path" { $files = Get-ChildItem -Path $Path }
"LiteralPath" { $files = Get-ChildItem -LiteralPath $LiteralPath }
"FileInfo" { $files = $Item }
}
$row=2
$col=2
$files | % {
$msgFn = $_.FullName
if ($msgFn -notlike "*.msg") {
return
}
$msg = $outlook.CreateItemFromTemplate($msgFn)
$rdate = $msg.ReceivedTime
$msg.Attachments | % {
$attFn = $msgFn -replace '\.msg$', " - Attachment - $($_.FileName)"
if (Test-Path -literalPath $attFn) {
return
}
$_.SaveAsFile($attFn)
Get-ChildItem -LiteralPath $attFn
$d = ( Get-Content -Path $attFn -Raw)
$cells.item($row,$col)=$d.ToString()
$cells.item($row,1) = $rdate
$row++
}
$s1.range("A1:B1").EntireColumn.autofit() | out-Null
$s1.range("A1:B1").EntireColumn.WrapText = $false | out-Null
}
}
End
{
$workbook.SaveAs("$Path\attachments.xlsx")
}
}
Get-Attachment -Path C:\Users\Administrator\pathtodir\

Related

How to insert bulk document into elastic using PowerShell?

I am trying to read the data from CSV file which has 2200000 records using PowerShell and storing each record in JSON file as NDJSON format.
Total document in JSON file is 2200000 (200 MB)
Sample JSON Data
{"index":{}}
{"ip-address":"1.5.0.1","is-vpn":"true","#timestamp":"2022-12-01T18:59:48.8325021+05:30"}
{"index":{}}
{"ip-address":"243.11.0.1","is-vpn":"true","#timestamp":"2022-12-01T18:59:48.8853225+05:30"}
{"index":{}}
{"ip-address":"253.11.0.1","is-vpn":"true","#timestamp":"2022-12-01T18:59:48.8853225+05:30"}
{"index":{}}
{"ip-address":"39.24.0.1","is-vpn":"true","#timestamp":"2022-12-01T18:59:48.8853225+05:30"}
{"index":{}}
{"ip-address":"163.24.0.1","is-vpn":"true","#timestamp":"2022-12-01T18:59:48.8853225+05:30"}
Code
function Get-IPDataPath
{
$dbFilePath = Get-ChildItem -Path $rootDir -Filter "IP2*.CSV" | ForEach-Object FullName | Select-Object -First 1
Write-Host "file path - $dbFilePath"
$dbFilePath # implicit output
}
function Convert-NumberToIP
{
param(
[Parameter(Mandatory=$true)][string]$number
)
[Int64] $numberInt = 0
if( [Int64]::TryParse( $number, [ref] $numberInt ) ) {
if( ($numberInt -ge 0) -and ($numberInt -le 0xFFFFFFFFl) ) {
# Convert to IP address like '192.168.23.42'
([IPAddress] $numberInt).ToString()
}
}
# In case TryParse() returns $false or the number is out of range for an IPv4 address,
# the output of this function will be empty, which converts to $false in a boolean context.
}
function Insert-Document
{
param(
[Parameter(Mandatory=$true)][string]$indexName,
[Parameter(Mandatory=$true)][object]$filePath
)
$url = "https://esdev2:9200/naveen/_doc/_bulk"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($cred))
$headers = #{
"Authorization" = "Basic $encodedCreds"
"Transfer-Encoding" = "chunked"
"Content-Type" = "application/x-ndjson"
}
$content = Get-Content -Path $filepath
$response = Invoke-WebRequest -Uri $url -Method POST -Body $content -Headers $Headers -ContentType 'application/x-ndjson'
if($response.StatusCode -eq 201)
{
Write-Host "Documents added successfully"
}
else
{
Write-Host $response
Write-Host "$($response.content)"
throw "Trouble adding document: $_"
}
}
$dbFilePath = Get-IPDataPath
$outputFile = Join-Path -Path $rootDir -ChildPath "output.json"
Write-Host "Converting CSV file $dbFilePath to $outputFile"
$object = [PSCustomObject]#{
'ip-address' = ''
'is-vpn' = 'true'
'#timestamp' = ''
}
# Enclose foreach loop in a script block to be able to pipe its output to Set-Content
$count = 0
& {
foreach( $item in [Linq.Enumerable]::Skip( [IO.File]::ReadLines( $dbFilePath ), 1 ) )
{
$row = $item -split ','
$ipNumber = $row[0].trim('"')
if( $ip = Convert-NumberToIP -number $ipNumber )
{
$index = [PSCustomObject]#{"index" = [PSCustomObject]#{}} | ConvertTo-Json -Depth 10 -Compress
$object.'ip-address' = $ip
$object.'#timestamp' = (Get-Date).ToString('o')
#$index | ConvertTo-Json -Depth 100 -Compress
$document = $object | ConvertTo-Json -Depth 100 -Compress
if($count -lt 25)
{
Add-Content -Path $outputFile $index
Add-Content -Path $outputFile $document
$count++
}
else
{
$count = 0
BulkInsert-Document -indexName $indexName -filePath $outputFile
Clear-Content -Path $outputFile
}
}
}
} | Set-Content -Path $outputFile
Write-Host "Inserting document in elastic"
Insert-Document -indexName $indexName -filePath $outputFile
But I am getting Error: {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"The bulk
request must be terminated by a newline
[\n]"}],"type":"illegal_argument_exception","reason":"The bulk request must be
terminated by a newline [\n]"},"status":400}
Though I have new line at the end of JSON file, Could you please help how to insert all the document.

Replace parameters in JSON with given Values from .csv

I have a JSON-File where I want to replace several values with a placeholder.
So i made a .csv with the parameters to replace. line[0] (if existing) is the path in the file, line[1] the element, line[2] the placeholder.
journeys.legs.origin.properties.downloads;url;placeholder;
;url;placeholder;
download;url;placeholder;
;psFileName;placeholder;
;serverTime;placeholder;
;calcTime;placeholder;
now I defined the following functions, to get the file, read the csv and replace the stuff.
$storage = "D:\Service\test.json"
$parameters2replace = "D:\Service\parameters2replace.csv"
function Get-JSONProperty([object] $InputObject, [string] $Property) {
$path = $Property -split '\.'
$obj = $InputObject
$path | %{ $obj = $obj.$_ }
$obj
}
function setParameter(){
foreach ($parameter in Get-Content $parameters2replace){
$line=$parameter.split(";")
$path = $line[0]
$elementName = $line[1]
$newValue = $line[2]
replaceElement $path $elementName $newValue
}
}
function replaceElement($path, $elementName, $newValue){
ForEach($JSONPath in Get-JSONProperty $JSON $path){
if (!$line[0]){
if($JSON.$elementName){
$JSON.$elementName = $newValue
}
}
else{
if($JSON.$path.$elementName){
echo $JSON.$path.$elementName
$JSON.$path.$elementName = $newValue
}
}
}
$JSON | ConvertTo-Json -depth 32| set-content $storage
}
$JSON = Get-Content $storage -raw | ConvertFrom-Json
setParameter
My problem now is, that the following if-argument won't work with the $path variable. If i put it in hardcoded it works just fine.
if($JSON.$path.$elementName)
I hope i could make everything clear, this was my first post.
$JSON.$path makes PowerShell look for a property with the literal name 'journeys.legs.origin.properties.downloads'. You need to split the string into it's individual parts and iterate (or recurse) through the path:
function Get-JSONPropertyValue {
param(
$JSON,
[string]$Path,
[string]$Name
)
$object = $JSON
foreach($propName in $Path.Split('.')){
$object = $object.$propName
}
return $object.$Name
}
function Set-JSONPropertyValue
{
param(
$JSON,
[string]$Path,
[string]$Name,
$Value
)
$object = $JSON
foreach($propName in $Path.Split('.')){
$object = $object.$propName
}
$object.$Name = $Value
}
Now you can do:
if($value = Get-JSONPropertyValue $JSON -Path $path -Name $elementName){
echo "Old value: $value"
Set-JSONPropertyValue $JSON -Path $path -Name $elementName -Value $newValue
}

Recursive JSON Compare Function too slow

I found a powershell deep compare object (https://github.com/kjelderg/Compare-DeepObject) and derived a recursive Json Compare Function for powershell.
The function generally seems to work fine, but its too slow for my purpose. For a batch compare it needs 8 minutes.
How can I speed up the processs?
Thanks
Alex
# walk through the two objects and generate a diff
# This extends compare-object which itself only does shallow comparisons.
# To use, impor t-module <thisFileName>
# Compare-DeepObject $foo $istar
function Compare-Json2 {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)][AllowNull()]$soll,
[Parameter(Mandatory=$true)][AllowNull()]$ist,
$isFile
# TODO: max-depth
)
PROCESS {
$diff = [PSCustomObject]#{
istJson = '{}'
sollJson = '{}'
difference = #()
}
if($isFile) {
if ($ist -eq $soll) {
$diff.difference += 'istFile == Soll File! Big Error in Testskript!'
return $diff
}
if(Test-Path $ist) {
$ist = Get-Content $ist
}
else {
if(Test-Path $soll) {
$diff.difference += 'istFileDoesNotExists'
}
return $diff
}
if(Test-Path $soll) {
$soll = Get-Content $soll
}
else {
$diff.difference += 'sollFileDoesNotExists'
return $diff
}
}
$diff.istJson = ($ist | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
$diff.sollJson = ($soll | ConvertFrom-Json) | ConvertTo-Json -Depth 100 -Compress
$ist = $ist | ConvertFrom-Json -Depth 100
$soll = $soll | ConvertFrom-Json -Depth 100
$diff.difference = Compare-JsonObjectRecursive -soll $soll -ist $ist -node $null
# }
return $diff
} # End PROCESS
} # End function$
function Compare-JsonObjectRecursive {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)][AllowNull()]$soll,
[Parameter(Mandatory=$true)][AllowNull()]$ist,
$node
# TODO: max-depth
)
PROCESS {
# if one side is null, return the other side.
if($soll -eq $null -and $ist -ne $null) { return #{ soll=$null; ist=$ist} }
if($ist -eq $null -and $soll -ne $null) { return #{ ist=$null; soll=$soll} }
if($soll -eq $null -and $ist -eq $null) { return }
# compare data types
$differences = #{}
#$differences = New-Object PSCustomObject
if (($ist -is [PsCustomObject]) -and ($soll -is [array])) {
[array]$ist = $ist
}
if (($ist -is [array]) -and ($soll -is [PsCustomObject])) {
[array]$soll = $soll
}
if($soll -is [array]) { # Recurse for each element of an array
for($i = 0; $i -lt [math]::max($soll.length, $ist.length); $i++) {
if ($node) {$node = "$node[$i]"}
else {$node = "[$i]"}
Compare-JsonObjectRecursive -soll $soll[$i] -ist $ist[$i] -node $node
}
}
elseif ($soll -is [PSCustomObject]) { # Recurse for each property of a PSCustomObject
# walk both sets of keys^Wproperty names with this cool get-unique magic.
$keys = (#($soll.PSObject.properties.name) + #($ist.PSObject.properties.name)) | Sort-Object | get-unique
if ($node) {$node = "$node."}
else {$node = ""}
foreach($key in $keys) {
Compare-JsonObjectRecursive -soll $soll.$key -ist $ist.$key -node "$node$key"
}
}
else {
if($soll -ne $ist) {
$differences[$node] = #{soll = $soll; ist = $ist}
return $differences
}
}
} # End PROCESS
} # End function$

Powershell: Hash table containing functions to be called with parameter

I have a dictionary like this:
function HashHandlerSHA256
{
param($Path, $Checksum)
$csp = new-object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider
$ComputedHash = $csp.ComputeHash([System.IO.File]::ReadAllBytes($Path))
$ComputedHash = [System.BitConverter]::ToString($ComputedHash).Replace("-", "").ToLower()
$result = $ComputedHash.CompareTo($Checksum)
return $result -eq 0
}
$HashHandler = #{"SHA256" = HashHandlerSHA256}
containing validation algorithms and the functions to be called for validation. The functions should all have the same parameter and return type.
Now when I have:
$Checksums = #{"SHA256" = "..."}
I'd like to call the correct function depending on which algorithms and values I have available. In this case I'd have a valid sha256 hash.
Now I want to do:
function Validate
{
param($Path, $Checksums)
foreach($Hash in $Checksums) {
$Type = $Hash.Name
$Value = $Hash.Value
if ($HashHandler.ContainsKey($Type)) {
$Handler = $HashHandler.Get_Item($Type)
if (-Not ($Handler -Path $Path -Checksum $Value)) {
return $FALSE
}
}
}
return $TRUE
}
The errormessage I get is:
At C:\Users\username\Desktop\hashtest.ps1:27 char:23
+ if (-Not ($Handler -Path $Path -Checksum $Value)) {
+ ~~~~~
Unexpected token '-Path' in expression or statement.
I am relatively new to PowerShell. I know how to call functions with parameters, but when stored in a variable, I didn't manage to solve this and when searching online for this, I didn't get the answers I needed.
Thanks for help.
if i understand you want Something like this
function HashHandlerSHA256
{
param($Path, $Checksum)
$csp = new-object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider
$ComputedHash = $csp.ComputeHash([System.IO.File]::ReadAllBytes($Path))
$ComputedHash = [System.BitConverter]::ToString($ComputedHash).Replace("-", "").ToLower()
$result = $ComputedHash.CompareTo($Checksum)
return $result -eq 0
}
function Validate
{
param($Path, $Checksums)
foreach($Hashkey in $Checksums.Keys)
{
$Value = $Checksums[$Hashkey]
if ($script:HashHandler.ContainsKey($Hashkey))
{
if (-Not (&$script:HashHandler[$Hashkey] -Path $Path -Checksum $Value)) { return $false}
}
}
return $TRUE
}
#add here your couples of algo/function
$script:HashHandler = #{"SHA256" = 'HashHandlerSHA256'}
#checksum to test
$Checksums=#{}
$Checksums["SHA256"]= 'd6a0a09fb1a7971b497674675d5b5621d865d6020e384137548de9c4ac6d4994'
$Checksums["MD5"]= 'xxxx'
#test list checksum and algo
Validate -Path "c:\temp\hello.csv" -Checksums $Checksums
an other solution
$file="C:\temp\exludevalue.txt"
$Checksums=#{}
$Checksums["SHA256"]= 'd6a0a09fb1a7971b497674675d5b5621d865d6020e384137548de9c4ac6d4994'
$Checksums["MD5k"]= '11A8D99F80F9B29FCF6A995D2F17B2E3'
$Checksums.Keys |
%{
if ($(gcm Get-FileHash).Parameters.Algorithm.Attributes.ValidValues -contains $_)
{
$algocalc=(Get-FileHash -path $file -Algorithm $_).Hash;
}
else
{
$algocalc='ALGO NOT FOUNDED'
}
new-object psobject -Property #{
Algo=$_
OldValue=$Checksums[$_]
CalculedValue=$algocalc
ResultComparison= $algocalc -eq $Checksums[$_]
}
}

Create editable powershell GUI for CSV data

GOAL: Create a GUI form populated with CSV data, allow the user to edit the data, then save the data in an array for further manipulation.
NOTE: Using PowerShell Studio to generate a form with data from the CSV
CURRENT CODE:
- Calling code ($path is passed from the calling form):
$rows = Import-Csv -Path $path
$table = ConvertTo-DataTable -InputObject $rows
Load-DataGridView -DataGridView $datagridviewResults -Item $table
ConvertTo-DataTable function:
function ConvertTo-DataTable {
[OutputType([System.Data.DataTable])]
param(
[ValidateNotNull()]
$InputObject,
[ValidateNotNull()]
[System.Data.DataTable]$Table,
[switch]$RetainColumns,
[switch]$FilterWMIProperties
)
if($Table -eq $null) {
$Table = New-Object System.Data.DataTable
}
if($InputObject-is [System.Data.DataTable]) {
$Table = $InputObject
} else {
if(-not $RetainColumns -or $Table.Columns.Count -eq 0) {
#Clear out the Table Contents
$Table.Clear()
if($InputObject -eq $null){ return } #Empty Data
$object = $null
#find the first non null value
foreach($item in $InputObject) {
if($item -ne $null) {
$object = $item
break
}
}
if($object -eq $null) { return } #All null then empty
#Get all the properties in order to create the columns
foreach ($prop in $object.PSObject.Get_Properties()) {
if(-not $FilterWMIProperties -or -not $prop.Name.StartsWith('__')) { #filter out WMI properties
#Get the type from the Definition string
$type = $null
if($prop.Value -ne $null) {
try{ $type = $prop.Value.GetType() } catch {}
}
if($type -ne $null) { # -and [System.Type]::GetTypeCode($type) -ne 'Object')
[void]$table.Columns.Add($prop.Name, $type)
} else { #Type info not found
[void]$table.Columns.Add($prop.Name)
}
}
}
if($object -is [System.Data.DataRow]) {
foreach($item in $InputObject) {
$Table.Rows.Add($item)
}
return #(,$Table)
}
} else {
$Table.Rows.Clear()
}
foreach($item in $InputObject) {
$row = $table.NewRow()
if($item) {
foreach ($prop in $item.PSObject.Get_Properties()) {
if($table.Columns.Contains($prop.Name)) {
$row.Item($prop.Name) = $prop.Value
}
}
}
[void]$table.Rows.Add($row)
}
}
return #(,$Table)
}
Load-DataGridView function:
function Load-DataGridView {
Param (
[ValidateNotNull()]
[Parameter(Mandatory=$true)]
[System.Windows.Forms.DataGridView]$DataGridView,
[ValidateNotNull()]
[Parameter(Mandatory=$true)]
$Item,
[Parameter(Mandatory=$false)]
[string]$DataMember
)
$DataGridView.SuspendLayout()
$DataGridView.DataMember = $DataMember
$DataGridView.EditMode = 'EditOnEnter'
if ($Item -is [System.ComponentModel.IListSource]`
-or $Item -is [System.ComponentModel.IBindingList]`
-or $Item -is [System.ComponentModel.IBindingListView]) {
$DataGridView.DataSource = $Item
} else {
$array = New-Object System.Collections.ArrayList
if ($Item -is [System.Collections.IList]) {
$array.AddRange($Item)
} else {
$array.Add($Item)
}
$DataGridView.DataSource = $array
}
$DataGridView.ResumeLayout()
}
ADDITIONAL INFORMATION: Code is working in that it generates the Grid View and populates it with CSV data. However, I cannot edit it and need help coding the ability to capture changes once it is edited.
Thanks in advance.
12/5 EDIT: Added "$DataGridView.EditMode = 'EditOnEnter'" to the function "Load-DataGridView" above. Nothing changed. Tried to invoke the "BeginEdit" Event in a new RowCellClick event, but that didn't work either. Still struggling with this one.
For anyone else you has struggled with this ....
set-strictmode -Version 2.0
function EditCSV($title, $Instructions, $csvPath, $x = 100, $y=100, $Width=600, $Height=400, $SaveChangesToFile=$true, $ReturnStatusOrArray='Status') {
#Windows Assemblies
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
#LoadCSV
#Variables MUST have script scope to allow form to see them
$script:Updated = $false
$script:CsvData = New-Object System.Collections.ArrayList
$script:CsvData.AddRange((import-csv $csvPath))
#Helper Functions
function paint($form, $ctrl, $TablIndex, $name, $Text, $x, $y, $Width, $Height){
try{$form.Controls.Add($ctrl) }catch{}
try{$ctrl.TabIndex = $TablIndex }catch{}
try{$ctrl.Text = $Text }catch{}
try{$ctrl.name = $name }catch{}
try{$ctrl.Location = System_Drawing_Point $x $y }catch{}
try{$ctrl.size = System_Drawing_Size $Width $Height }catch{}
try{$ctrl.DataBindings.DefaultDataSourceUpdateMode = 0 }catch{}
$ctrl
}
function System_Drawing_Point($x, $Y) {$_ = New-Object System.Drawing.Point; $_.x = $X; $_.Y = $Y; $_}
function System_Drawing_Size( $Width, $Height){$_ = New-Object System.Drawing.Size; $_.Width = $Width; $_.Height = $Height; $_}
#Paint Form
$form1 = paint $null (New-Object System.Windows.Forms.Form) $null 'form1' $Title $x $y $Width $Height
$form1.add_Load({
$dataGrid1.DataSource = $script:CsvData;
$form1.refresh()
})
$label1 = paint $form1 (New-Object System.Windows.Forms.Label) $null "label1" "$Instructions" 12 13 ($width-100) 23
$label1.Font = New-Object System.Drawing.Font("Microsoft Sans Serif",9.75,2,3,0)
$label1.ForeColor = [System.Drawing.Color]::FromArgb(255,0,102,204)
$buttonSave = paint $form1 (New-Object System.Windows.Forms.Button) 1 "button1" "Save" ($width-200) ($Height-75) 75 23
$buttonSave.UseVisualStyleBackColor = $True
$buttonSave.add_Click({
$script:Updated = $true
$Form1.Close()
})
$buttonClose = paint $form1 (New-Object System.Windows.Forms.Button) 2 'button2' 'Close' ($width-105) ($Height-75) 75 23
$buttonClose.UseVisualStyleBackColor = $True
$buttonClose.add_Click({
$Form1.Close()
})
$dataGrid1 = paint $form1 (New-Object System.Windows.Forms.DataGrid) 0 "dataGrid0" $Null 12 40 ($width-40) ($Height-125)
$dataGrid1.HeaderForeColor = [System.Drawing.Color]::FromArgb(255,0,0,0)
#Show and Wait till complete
$form1.ShowDialog()| Out-Null
#Save CSV
if( $SaveChangesToFile -eq $true -and $script:Updated ){
$script:CsvData| export-csv -NoTypeInformation -path $csvPath
}
if( $ReturnStatusOrArray -eq 'Status'){
return $script:Updated
}else{
return $script:CsvData
}
}
## Unit Test
cls
function script:Indent-ConsoleOutput($output, $indent = ' '){
if(!($output -eq $null)){
if(!( $indent -is [string])){
$indent = ''.PadRight($indent)
}
$width = (Get-Host).UI.RawUI.BufferSize.Width - $indent.length
($output| out-string).trim().replace( "`r", "").split("`n").trimend()| %{
for($i=0; $i -le $_.length; $i+=$width){
if(($i+$width) -le $_.length){
"$indent"+$_.substring($i, $width)
}else{
"$indent"+$_.substring($i, $_.length - $i)
}
}
}
}
}
Write-Host '## Before '.PadRight(120, '#')
$filePath = 'C:\temp\Text.csv'
$d = (dir c: |select-object -property Directory, Mode, LastWriteTime, Length, Name)[0..5]
$d |export-csv -path $filePath -NoTypeInformation
Indent-ConsoleOutput (import-csv $filePath |format-table) 4
Write-Host '## Edit - Save to File '.PadRight(120, '#')
Indent-ConsoleOutput (EditCSV 'Example of PS Editable Grid' '[SAVE] - To Save Changes' $filePath ) 4
Write-Host '## After '.PadRight(120, '#')
Indent-ConsoleOutput (import-csv $filePath |format-table) 4
Write-Host '## Edit - Return Array '.PadRight(120, '#')
Indent-ConsoleOutput (EditCSV 'Example of PS Editable Grid' '[SAVE] - To Save Changes' $filePath -SaveChangesToFile $false -ReturnStatusOrArray 'Array'|format-table) 4
Indent-ConsoleOutput (import-csv $filePath |format-table) 4
Enjoy