Powershell Recursion with Return - function

I am trying to write a recursive function that will return information in an array, however when I put a return statement into the function it misses certain entries.
I am trying to recursively look through a specified depth of folders getting the acl's associated with the folder. I know getChildItem has a recurse option, but I only want to step through 3 levels of folders.
The excerpt of code below is what I have been using for testing. When getACLS is called without a return statement (commented out below) the results are:
Folder 1
Folder 12
Folder 13
Folder 2
When the return statement is used I get the following output:
Folder 1
Folder 12
So it looks like the return statement is exiting out from the recursive loop?
The idea is that I want to return a multidimensional array like [folder name, [acls], [[subfolder, [permissions],[[...]]]]] etc.
cls
function getACLS ([string]$path, [int]$max, [int]$current) {
$dirs = Get-ChildItem -Path $path | Where { $_.psIsContainer }
$acls = Get-Acl -Path $path
$security = #()
foreach ($acl in $acls.Access) {
$security += ($acl.IdentityReference, $acl.FileSystemRights)
}
if ($current -le $max) {
if ($dirs) {
foreach ($dir in $dirs) {
$newPath = $path + '\' + $dir.Name
Write-Host $dir.Name
# return ($newPath, $security, getACLS $newPath $max ($current+1))
# getACLS $newPath $max ($current+1)
return getACLS $newPath $max ($current+1)
}
}
} elseif ($current -eq $max ) {
Write-Host max
return ($path, $security)
}
}
$results = getACLS "PATH\Testing" 2 0

The problem was the location of the return. I had it inside the foreach loop, meaning it was trying to return multiple times in the one function. I moved it outside the foreach, into the if statement instead.
function getACLS ([string]$path, [int]$max, [int]$current) {
$dirs = Get-ChildItem -Path $path | Where { $_.psIsContainer }
$acls = Get-Acl -Path $path
$security = #()
$results = #()
foreach ($acl in $acls.Access) {
$security += ($acl.IdentityReference, $acl.FileSystemRights)
}
if ($current -lt $max) {
if ($dirs) {
foreach ($dir in $dirs) {
$newPath = $path + '\' + $dir.Name
$next = $current + 1
$results += (getACLS $newPath $max $next)
}
} else {
$results = ($path, $security)
}
return ($path, $security, $results)
} elseif ($current -eq $max ) {
return ($path, $security)
}
}

In recursion, I would only use a return statement where I needed to end the recursion - just for clarity. I've done a good bit of recursion in PowerShell and it works well. However you need to remember that PowerShell functions do behave differently. The following:
return 1,2
is equivalent to:
1,2
return
In other words, anything you don't capture in a variable or redirect to a file (or $null) is automatically considered output of the function. Here's a simple example of a working, recursive function:
function recursive($path, $max, $level = 1)
{
$path = (Resolve-Path $path).ProviderPath
Write-Host "$path - $max - $level"
foreach ($item in #(Get-ChildItem $path))
{
if ($level -eq $max) { return }
recursive $item.PSPath $max ($level + 1)
}
}
recursive ~ 3

Update: I am leaving the first answer as is and adding the new code here. I see that there are multiple issues with your code. here is the updated one.
cls
function getACLS ([string]$path, [int]$max, [int]$current) {
$dirs = Get-ChildItem -Path $path | Where { $_.psIsContainer }
$acls = Get-Acl -Path $path
$security = #()
foreach ($acl in $acls.Access) {
$security += ($acl.IdentityReference, $acl.FileSystemRights)
}
if ($current -lt $max) {
if ($dirs) {
foreach ($dir in $dirs) {
$newPath = $dir.FullName
$security
getACLS $newPath $max ($current+1)
}
}
} elseif ($current -eq $max ) {
Write-Host max
return $security
}
}
$results = getACLS "C:\Scripts" 2 0
If you see above, I am not using return. I just throw the object from the GetACLs function. Also, I modified it to return on $security for testing purpose. I can see the all ACLs in $results. I changed the first if condition to if ($current -lt $max). It should not be if ($current -le $max).
Let me know if this what you are looking for. I can continue to optimize this.
==========================================OLD=============================================
Return will exit the function.
I am not providing the complete solution here but want to give you an idea about how this can be changed.
You can use PS Custom object to capture the information you need. For example,
function GetItem {
$itemsArray = #()
Get-ChildItem C:\Scripts | ForEach-Object {
$itemsObject = New-Object PSObject
Add-Member -InputObject $itemsObject -MemberType NoteProperty -Name "FullName" -Value $_.FullName
Add-Member -InputObject $itemsObject -MemberType NoteProperty -Name "Name" -Value $_.Name
$itemsArray += $itemsObject
}
return $itemsArray
}
This way you can return the object once it is completely built with the information you need.

I found none of these solutions did what I need to, which was to have an array with the results of the whole recursive function. Since we can't initialise the array inside the function, otherwise it is re-initialised every time recursion is used, we have to define it outside and use global:
# Get folder contents recursively and add to an array
function recursive($path, $max, $level = 1)
{
Write-Host "$path" -ForegroundColor Red
$global:arr += $path
foreach ($item in #(Get-ChildItem $path))
{
if ($level -eq $max) { return }
if ($item.Length -eq "1") # if it is a folder
{
$newpath = "$path\$($item.Name)"
recursive $newpath $max ($level + 1)
}
else { # else it is a file
Write-Host "$path\$($item.Name)" -ForegroundColor Blue
$global:arr +="$path\$($item.Name)"
}
}
}
$arr = #() # have to define this outside the function and make it global
recursive C:\temp\test2 4
write-host $arr.count

Related

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$

Having a hard time building PowerShell function to unflatten a JSON doc table

I built a recursive solution to read in a JSON doc and output a flattened table. Now I want to reverse the operation but am having a hard time figuring out a soliton. I need it to be in PowerShell as I just want to add it to my existing module.
I posted about my flatten code here: https://stackoverflow.com/a/63499156/13224569
I have a partial solution, but it falling well short. Here's that code:
$a = '.person.pets[0].color.black.toy.foo' -split '\.'
$b = '.john.harold.cravener' -split '\.'
$z = '.john.is.content' -split '\.'
$m = #{}
$c = #{}
$p = #{}
$pk = $null
$firstTime = $true
foreach($i in $a[1..($a.Count-1)]) {
$c = #{$i = $null}
if($firstTime){
$m = $c
$firstTime = $false
}
if($p -and $pk) {
$p[$pk] = $c
}
$p = $c
$pk = $i
}
$m
exit
And here's its output:
PS D:\> .\tester.ps1 | ConvertTo-Json -Depth 20
{
"person": {
"pets[0]": {
"color": {
"black": {
"toy": {
"foo": null
}
}
}
}
}
}
My first challenge, I can't figure out how to move on to the next row and store it to the exiting $m variable. I'm getting tripped up with the way this var is being referenced in PowerShell / .Net Core.
The second challenge is how to deal with arrays, but I haven't even started on this part yet.
Any and all help / suggestions would be greatly appreciated!
After some research, and a lot of work and debugging, I came to a solution. From my testing so far, it works quite well. It's based on simonw's python code here. And while my solution needed to be in PowerShell, it's not a straight port but pretty close. Most of the modifications are based on differences in how PowerShell and Python handles certain things. The main function is ConvertFrom-JhcUtilJsonTable which in turn depends on three helper functions. Here's all of them.
function ConvertFrom-JhcUtilJsonTable {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Collections.Hashtable]
$jsonHashTable
)
begin {}
process {
foreach ($h in $jsonHashTable) {
$m = #{}
foreach ($k in $h.Keys) {
$current = $m
$val = $h[$k]
$k = ($k -replace '\]', '') -replace '\[', '.'
$bits = $k.split('.')
$path = $bits[1..($bits.Count - 1)]
$count = 0
foreach ($bit in $path) {
$count++
if ($v = $current.item($bit)) {
$current[$bit] = $v
}
else {
if ($count -eq $path.Count) {
$current[$bit] = $val
}
else {
$current[$bit] = #{}
}
}
$current = $current[$bit]
}
}
intKeyHashToLists -obj $m
#--python code had a buit about handling root units e.g. {'$empty': '{}'} - need to add that
}
}
end {}
}
#---helper function for ConvertFrom-JhcUtilJsonTable
#
function isType {
param (
[Parameter(Mandatory)]
[System.Object]
$obj,
[Parameter(Mandatory)]
[ValidateSet('Hashtable', 'Object[]')]
[System.String]
$typeName
)
$t = $obj.GetType()
if ($t.Name -eq $typeName) {
return $true
}
return $false
}
#---helper function for ConvertFrom-JhcUtilJsonTable
#
function allKeysDigits {
param (
[Parameter(Mandatory)]
[System.Collections.Hashtable]
$h
)
foreach ($k in $h.Keys) {
if ($k -match '^0\d') {
return $false
}
if ($k -notmatch '^\d+$') {
return $false
}
}
return $true
}
#---helper function for ConvertFrom-JhcUtilJsonTable
#
function intKeyHashToLists {
param (
[Parameter(Mandatory)]
[System.Object]
$obj
)
if (isType -obj $obj -typeName 'Hashtable') {
if ($obj -and (allKeysDigits -h $obj)) {
$a = #()
foreach ($k in ($obj.Keys | Sort-Object) ) {
$a += intKeyHashToLists -obj $obj.item($k)
}
return ,$a #--- adding the comma forces this to retun an array even when it's a single element
}
else {
$h = #{}
foreach ($k in $obj.Keys) {
$h[$k] = intKeyHashToLists -obj $obj.item($k)
}
return $h
}
}
elseif (isType -obj $obj -typeName 'Object[]') {
return ( $obj | ForEach-Object { intKeyHashToLists -obj $_ } )
}
else {
return $obj
}
}

powershell convert json to hashtable recursively [duplicate]

What is the easiest way to convert a PSCustomObject to a Hashtable? It displays just like one with the splat operator, curly braces and what appear to be key value pairs. When I try to cast it to [Hashtable] it doesn't work. I also tried .toString() and the assigned variable says its a string but displays nothing - any ideas?
Shouldn't be too hard. Something like this should do the trick:
# Create a PSCustomObject (ironically using a hashtable)
$ht1 = #{ A = 'a'; B = 'b'; DateTime = Get-Date }
$theObject = new-object psobject -Property $ht1
# Convert the PSCustomObject back to a hashtable
$ht2 = #{}
$theObject.psobject.properties | Foreach { $ht2[$_.Name] = $_.Value }
Keith already gave you the answer, this is just another way of doing the same with a one-liner:
$psobject.psobject.properties | foreach -begin {$h=#{}} -process {$h."$($_.Name)" = $_.Value} -end {$h}
Here's a version that works with nested hashtables / arrays as well (which is useful if you're trying to do this with DSC ConfigurationData):
function ConvertPSObjectToHashtable
{
param (
[Parameter(ValueFromPipeline)]
$InputObject
)
process
{
if ($null -eq $InputObject) { return $null }
if ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [string])
{
$collection = #(
foreach ($object in $InputObject) { ConvertPSObjectToHashtable $object }
)
Write-Output -NoEnumerate $collection
}
elseif ($InputObject -is [psobject])
{
$hash = #{}
foreach ($property in $InputObject.PSObject.Properties)
{
$hash[$property.Name] = ConvertPSObjectToHashtable $property.Value
}
$hash
}
else
{
$InputObject
}
}
}
My extremely lazy approach, enabled by a new feature in PowerShell 6:
$myhashtable = $mypscustomobject | ConvertTo-Json | ConvertFrom-Json -AsHashTable
This works for PSCustomObjects created by ConvertFrom_Json.
Function ConvertConvertFrom-JsonPSCustomObjectToHash($obj)
{
$hash = #{}
$obj | Get-Member -MemberType Properties | SELECT -exp "Name" | % {
$hash[$_] = ($obj | SELECT -exp $_)
}
$hash
}
Disclaimer: I barely understand PowerShell so this is probably not as clean as it could be. But it works (for one level only).
My code:
function PSCustomObjectConvertToHashtable() {
param(
[Parameter(ValueFromPipeline)]
$object
)
if ( $object -eq $null ) { return $null }
if ( $object -is [psobject] ) {
$result = #{}
$items = $object | Get-Member -MemberType NoteProperty
foreach( $item in $items ) {
$key = $item.Name
$value = PSCustomObjectConvertToHashtable -object $object.$key
$result.Add($key, $value)
}
return $result
} elseif ($object -is [array]) {
$result = [object[]]::new($object.Count)
for ($i = 0; $i -lt $object.Count; $i++) {
$result[$i] = (PSCustomObjectConvertToHashtable -object $object[$i])
}
return ,$result
} else {
return $object
}
}
For simple [PSCustomObject] to [Hashtable] conversion Keith's Answer works best.
However if you need more options you can use
function ConvertTo-Hashtable {
<#
.Synopsis
Converts an object to a hashtable
.DESCRIPTION
PowerShell v4 seems to have trouble casting some objects to Hashtable.
This function is a workaround to convert PS Objects to [Hashtable]
.LINK
https://github.com/alainQtec/.files/blob/main/src/scripts/Converters/ConvertTo-Hashtable.ps1
.NOTES
Base ref: https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/turning-objects-into-hash-tables-2
#>
PARAM(
# The object to convert to a hashtable
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
$InputObject,
# Forces the values to be strings and converts them by running them through Out-String
[switch]$AsString,
# If set, empty properties are Included
[switch]$AllowNulls,
# Make each hashtable to have it's own set of properties, otherwise,
# (default) each InputObject is normalized to the properties on the first object in the pipeline
[switch]$DontNormalize
)
BEGIN {
$headers = #()
}
PROCESS {
if (!$headers -or $DontNormalize) {
$headers = $InputObject | Get-Member -type Properties | Select-Object -expand name
}
$OutputHash = #{}
if ($AsString) {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col | Out-String -Width 9999 | ForEach-Object { $_.Trim() }
}
}
} else {
foreach ($col in $headers) {
if ($AllowNulls -or ($InputObject.$col -is [bool] -or ($InputObject.$col))) {
$OutputHash.$col = $InputObject.$col
}
}
}
}
END {
return $OutputHash
}
}
Maybe this is overkill but I hope it Helps
Today, the "easiest way" to convert PSCustomObject to Hashtable would be so:
$custom_obj | ConvertTo-HashtableFromPsCustomObject
OR
[hashtable]$custom_obj
Conversely, you can convert a Hashtable to PSCustomObject using:
[PSCustomObject]$hash_table
Only snag is, these nifty options may not be available in older versions of PS

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