PowerShell: Array to multiple array - arrays

i have a simple Array, created with the following Content (loop not shown)
$tmpArray += $AlarmgroupIndexString + ";" + $LanguageIDString + ";" + $ID_string + ";" + $_.Value
Is there a possibility to add the Content to a multidimensional Array?
I'll have to filter the entries later. With this setup, that's not possible.
I have made an attempt with the help of
$list = ConvertFrom-Csv $tmpArray -Header "GroupID", "Language", "TextID", "Text" -Delimiter ";"
$list | Sort-Object -Property GroupID, Language, TextID#
Unfortunately the conversion to excel did not result in a column Separation...
Please find my Code here
foreach ($file in $fileNames) {
$Content = [xml](Get-Content -Path $file.FullName)
$ns = New-Object System.Xml.XmlNamespaceManager($Content.NameTable)
$ns=#{DEF="http://br-automation.co.at/AS/VC/Project"}
$AlarmgroupIndex = Select-Xml -Xml $Content -XPath "//DEF:Property[contains(#Name,'Index')]" -namespace $ns | select -ExpandProperty node
$AlarmgroupIndexString = $AlarmgroupIndex.Value
$AlarmgroupLanguageText = Select-Xml -Xml $Content -XPath "//DEF:TextLayer" -namespace $ns | select -ExpandProperty node
$AlarmgroupIndexMap = Select-Xml -Xml $Content -XPath "//DEF:Index" -namespace $ns | select -ExpandProperty node
$LUT =#{}
$AlarmgroupIndexMap | foreach{
$LUT.($_.ID) = $_.Value
}
$tmpArray =#()
$AlarmgroupLanguageText | foreach{
$LanguageIDString = $_.LanguageId
$AlarmgroupTextLayer = Select-Xml -Xml $Content -XPath "//DEF:TextLayer[#LanguageId='$LanguageIDString']/DEF:Text" -namespace $ns | select -ExpandProperty node
$AlarmgroupTextLayer | foreach{
if($LUT.ContainsKey($_.ID))
{
$ID_string = $LUT[$_.ID]
}
$tmpArray += $AlarmgroupIndexString + ";" + $LanguageIDString + ";" + $ID_string + ";" + $_.Value
}
$LanguageIDString=""
}
$tmpArray | Out-File "$rootPath\test.txt" -Append -Encoding utf8
$list = ConvertFrom-Csv $tmpArray -Header 'GroupID', 'Language', 'TextID', 'Text' -Delimiter ";"
$list | Sort-Object -Property GroupID, Language, TextID
}
TIA

First of all:
$AlarmgroupIndexString + ";" + $LanguageIDString + ";" + $ID_string + ";" + $_.Value is not an array but a string (with sub-strings separated by semicolons)
Try to avoid using the increase assignment operator (+=) to create a collection (or a string) as it is exponential expensive (meaning that the cost will increase with each iteration).
It is a pity that you not showing more details about the loop as the result of your question actually depends how you initialize the $tmpArray variable:
If you start with a string ($tmpArray = '') or $Null it will continue to append to that string.
If you start with a string ($tmpArray = #()) it will continue to append the array (which is probably where you looking for).
If you going to use ConvertFrom-Csv you might also consider to start with a string and separate each row with a newline (e.g. ... + $_.Value + "`r`n")
Anyways, it is also not a good idea to use ConvertFrom-Csv to build a collection of PowerShell objects (for one reason: it will convert all the values to a string), the correct PowerShell way to do this is:
$List = 1..3 | Foreach-Object { # replace 1..3 with your enumerator
[pscustomobject]#{
AlarmIndex = $AlarmgroupIndexString
LanguageIndex = $LanguageIDString
ID = $ID_string
Value = $_.Value
}
}
}
Update based on the added information in the question:
I am not sure what exactly your application is supposed to do, but the loop should be something like:
$list = $AlarmgroupLanguageText | foreach {
$LanguageIDString = $_.LanguageId
$AlarmgroupTextLayer = Select-Xml -Xml $Content -XPath "//DEF:TextLayer[#LanguageId='$LanguageIDString']/DEF:Text" -namespace $ns | select -ExpandProperty node
$AlarmgroupTextLayer | foreach{
if($LUT.ContainsKey($_.ID))
{
$ID_string = $LUT[$_.ID]
}
[pscustomobject]#{
GroupID = $AlarmgroupIndexString
Language = $LanguageIDString
TextID = $ID_string
Text = $_.Value
}
}
}
$list | Sort-Object -Property GroupID, Language, TextID

I will suggest you to have a quick look on this article: https://www.andreasbijl.com/powershell-create-collections-of-custom-objects/
You can generate the list you want using the example described in the link above. For your case it should look similar to this:
$list = New-Object System.Collections.ArrayList
your_foreach_loop
{
$temp = New-Object System.Object
$temp | Add-Member -MemberType NoteProperty -Name "GroupID" -Value $AlarmgroupIndexString
$temp | Add-Member -MemberType NoteProperty -Name "Language" -Value $LanguageIDString
$temp | Add-Member -MemberType NoteProperty -Name "TextID" -Value $ID_string
$temp | Add-Member -MemberType NoteProperty -Name "Text" -Value $_.Value
$list.Add($temp)
}

Related

Array causing 'system.outofmemoryexception'

I am running the below script and it is causing exception of type 'system.outofmemoryexception' was thrown
I believe that it is due to the #Results array growing past the 2gb allocated to windows shell. Is there possibly a way to iterate through the results, or am I stuck with allocating more memory (which could ultimately be a lot...)?
$Path = "path to output"
### Get all PF
$publicFolders = Get-PublicFolder "Public Folder Name" -Recurse -resultsize unlimited | Select-Object *
### Array to contain results
$results = #()
###Begin looping through each PF and grab each user/group with AccessRights to that folder
$final = ForEach($pf in $publicFolders){
$perms = Get-PublicFolderClientPermission -Identity $pf.Identity | Where-Object {$_.User -notmatch "Default|Anonymous|S-X-X-XX"}
Foreach($perm in $perms){
$temp = [PSCustomObject]#{
MailFolderName = $pf.Identity
UserWithPerms = $perm.User
AccessRights = $perm | Select-Object -ExpandProperty AccessRights
}
$results += $temp
}
}
$final | Export-Csv $path -NoTypeInformation
Am I barking up the wrong tree?
Thanks in advance.
Use the ForEach-Object cmdlet instead of a foreach(){} loop statement for the outer loop - this way you can start piping the output to Export-Csv immediately instead of buffering it in an array:
$publicFolders |ForEach-Object {
$pf = $_
$perms = Get-PublicFolderClientPermission -Identity $pf.Identity | Where-Object {$_.User -notmatch "Default|Anonymous|S-X-X-XX"}
Foreach($perm in $perms){
[PSCustomObject]#{
MailFolderName = $pf.Identity
UserWithPerms = $perm.User
AccessRights = $perm | Select-Object -ExpandProperty AccessRights
}
}
} | Export-Csv $path -NoTypeInformation
Alternatively, flush the partial set of results to file after enumerating the permissions for each folder:
ForEach($pf in $publicFolders){
$perms = Get-PublicFolderClientPermission -Identity $pf.Identity | Where-Object {$_.User -notmatch "Default|Anonymous|S-X-X-XX"}
$results = Foreach($perm in $perms){
[PSCustomObject]#{
MailFolderName = $pf.Identity
UserWithPerms = $perm.User
AccessRights = $perm | Select-Object -ExpandProperty AccessRights
}
}
# flush the buffer before moving to the next set of permissions
$results |Export-Csv $path -NoTypeInformation -Append
}

Powershell Array: HOWTO Dedup Output

When I run this parser script on my contacts.xml, which shows one line per user, I get multiple instances of the same data. I only want a single entry for the same data. How do I dedup the data before it writes to the CSV?
#https://stackoverflow.com/questions/29999682/powershell-parsing-a-text-file
$input = Get-Content $env:USERPROFILE\Downloads\contacts.xml\Downloads\contacts.xml
$array = #()
$input | % {
$writeobj = $false
$obj = New-Object System.Object
if ($_ -match 'email*') {
$Email = ($_ -split ':')[1]
}
if ($_ -match 'FN*') {
$NAME = ($_ -split ':')[1]
$writeobj = $true
}
if ($writeobj) {
$obj | Add-Member -Type NoteProperty -Name Email -Value $Email
$obj | Add-Member -Type NoteProperty -Name Name -Value $NAME
$array += $obj
}
Write-Host $Name, $email
}
$array | Export-Csv -Path C:\scripts\reports\test.csv -NoTypeInformation
I expect this to produce single entries but I get duplicates (and they don't line up right either).
(And yes I checked the XML file for single entries)
Select the unique objects.
$array |
Select-Object -Property * -Unique |
Export-Csv -Path 'C:\scripts\reports\test.csv' -NoType
As a side note, you may want to avoid appending to an array in a loop, as that is bound to perform poorly. Just pipe your ForEach-Object loop directly into Export-Csv.
I figured it out.
I REVERSED the $obj Add-Member variables *that fixed the order) and added another "$writeobj = $true" line to the FN match, and VOILÀ no more dupes.
Is that weird or what?
#https://stackoverflow.com/questions/29999682/powershell-parsing-a-text-file
$input = Get-Content $env:USERPROFILE\Downloads\contacts.xml $array =
#() $input | % {
$writeobj = $false
$obj = New-Object System.Object
If ($_ -match 'email') {
$Email = ($_ -split ':')[1]
$writeobj = $true
}
If ($_ -match 'FN') {
$NAME = ($_ -split ':')[1]
$writeobj = $true # <-- right here
}
If ($writeobj){
$obj | Add-Member -type NoteProperty -name Email -value **$NAME**
$obj | Add-Member -type NoteProperty -name Name -value **$Email**
$array += $obj
}
Write-Host $Name, $email } $array | Export-Csv -path C:\scripts\reports\test.csv -NoTypeInformation

Scanning computers from Active Directory for Local Admin accounts

I need to scan Active Directory and pipe the information into a local admin checking script. But for some reason this script keeps failing. It fails at
$Group = $computer.psbase.children.find(”Administrators”)
Here is the script
################################################
#Start looking for windows 10 & computers 7 #
################################################
$root = {Get-ADComputer -Filter * | Where-Object {$_.Name -like "win10*"} | Select -Property Name
Get-ADComputer -Filter * | Where-Object {$_.Name -like "*win7"} | Select -Property Name}
foreach ($server in $root)
{
$computer = [ADSI](”WinNT://” + "$server" + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$members = ($Group.psbase.invoke(”Members”) | %{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) - replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/', 'DOMAIN\') -replace ('WinNT://', '')
$members }
You were looking at the wrong line. It's the $members = line that it's complaining about. You have two spaces between the hyphen and your first "replace":
- replace ('WinNT://DOMAIN/' + $server + '/')
There shouldn't be any spaces there. Try this:
$root = {Get-ADComputer -Filter * | Where-Object {$_.Name -like "win10*"} | Select -Property Name
Get-ADComputer -Filter * | Where-Object {$_.Name -like "*win7"} | Select -Property Name}
foreach ($server in $root)
{
$computer = [ADSI](”WinNT://” + "$server" + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$members = ($Group.psbase.invoke(”Members”) | %{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) -replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/', 'DOMAIN\') -replace ('WinNT://', '')
$members
}
If you aren't already, try using the Windows PowerShell ISE, which is included in Windows, for writing PowerShell scripts. It will highlight simple syntax errors like that.

Powershell - foreach to array as job (local / multi-threaded)

I'm trying to parse a site to collect price and product details. The script works in a loop however it's very slow. So I'm trying to run a multi-threaded powershell script as a job.
I've tried a lot of suggestions but I'm struggling to get the results out even though I can see its working (the web-request screen flashing up)
I'm only selecting the last 10 but I'll put in a throttle later. Just can't get it to output. Essentially I'd like all results to flow back into $arr.
#Import Danmurphy Sitelist
[xml] $XmlDocument = (New-Object System.Net.WebClient).DownloadString("http://www.example.com/sites.xml")
#get websites listed
$ImportedProducts = $XmlDocument.DocumentElement.url | select -Last 10
"Killing existing jobs . . ."
Get-Job | Remove-Job -Force
"Done."
#loop through the products
#Create Array
$arr = #()
#$argumentlist
#ScriptBlock
$ScriptBlock = {
Param($product,$arr)
if ($product.loc -like "http://www.example.com/product/*"){
$uri = $product.loc
$WebResponse = Invoke-WebRequest -Uri $uri -SessionVariable WS
#mainpricetest
$mainprice = $WebResponse.AllElements | ? { $_.Class -eq 'price-main' } | select innerText
$MainPriceArray = $mainprice.innerText.Split(' ')
$MainUnitArry = $MainPriceArray[1..10]
$MainDollar = $MainPriceArray[0]
$MainUnit = $MainUnitArry -join ' '
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Product Site' -Value $($product.loc)
$item | Add-Member -type NoteProperty -Name 'Main Price' -Value $($MainDollar)
$item | Add-Member -type NoteProperty -Name 'Main Unit' -Value $($MainUnit)
$arr += $item
}
}
foreach ($product in $ImportedProducts){
Start-Job -InputObject $ImportedProducts -ScriptBlock $ScriptBlock -ArgumentList $product,$arr
}
$data = Get-Job * | Receive-Job
#Show Array
$arr
So you would want to use runspaces for that. Runspaces is a pretty complicated thing, luckily we have Posh-RSJob which handles everything for you. https://github.com/proxb/PoshRSJob
You can pass in the script block, so you would need very little adjustments.
Probably something like this:
foreach ($product in $ImportedProducts){
Start-RSJob -ScriptBlock $ScriptBlock
}
Get-RSjob | Receive-RSJob
If you want to get the results into $arr, you can't do it from within the script block as you are attempting to do. Multiple script blocks running in parallel cannot be allowed to access a single copy of a variable without taking additional steps not worth getting into.
The answer to your problem is going to be to write the output of each script block as regular output. That output is buffered until you use Receive-Job to get the results out of the job at which time you capture it into the $arr variable in a single threaded manner. Below is cod which should get you most of the way there.
#Import Danmurphy Sitelist
[xml] $XmlDocument = (New-Object System.Net.WebClient).DownloadString("http://www.example.com/sites.xml")
#get websites listed
$ImportedProducts = $XmlDocument.DocumentElement.url | select -Last 10
"Killing existing jobs . . ."
Get-Job | Remove-Job -Force
"Done."
#loop through the products
#Create Array
$arr = #()
#$argumentlist
#ScriptBlock
$ScriptBlock = {
Param($product)
if ($product.loc -like "http://www.example.com/product/*"){
$uri = $product.loc
$WebResponse = Invoke-WebRequest -Uri $uri -SessionVariable WS
#mainpricetest
$mainprice = $WebResponse.AllElements | ? { $_.Class -eq 'price-main' } | select innerText
$MainPriceArray = $mainprice.innerText.Split(' ')
$MainUnitArry = $MainPriceArray[1..10]
$MainDollar = $MainPriceArray[0]
$MainUnit = $MainUnitArry -join ' '
$item = New-Object PSObject
$item | Add-Member -type NoteProperty -Name 'Product Site' -Value $($product.loc)
$item | Add-Member -type NoteProperty -Name 'Main Price' -Value $($MainDollar)
$item | Add-Member -type NoteProperty -Name 'Main Unit' -Value $($MainUnit)
Write-Output $item
}
}
foreach ($product in $ImportedProducts){
Start-Job -InputObject $ImportedProducts -ScriptBlock $ScriptBlock -ArgumentList $product
}
do {
$arr += Get-Job -State Completed | Receive-Job -AutoRemoveJob
} while (Get-Job -State Running)
#Show Array
$arr

How do I convert an array object to a string object in PowerShell?

While trying to create an CSV file with information about certificates I have an issue to store the userrights on the private key.
The problem is that I want to store multiple values in one attribute so I use an array.
At first I had no errors, however the column in my csv-file remained empty even in the case where the array has a value.
With a simple Write-Host I can see my array has the expected value so this part works okay.
For further investigations I have added the line:
Get-Member $certs.GetValue("UserRights")
This gives an error indicating I have to convert my variable to a string-variable.
So next I have tried to convert this array to a single string.
I have tried several ways but my error doesn't disappear so it doesn't work.
Underneath is my full code with some former attempts commented.
cls $certs = Get-ChildItem cert:\LocalMachine -Recurse | Where-Object {-not $_.PSIsContainer} | Select * Write-Host ("There were {0} certificates" -f ($certs | Measure-Object).Count)
foreach($certificate in $certs) {
if($certificate.HasPrivateKey)
{
Write-Host "Certificate's PSChildName is" $certificate.PSChildName
$rsaFile = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$fullPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\" + $rsaFile
$acl = Get-Acl -Path $fullPath
foreach($accessrule in $acl.Access)
{
Write-Host "User" $accessrule.IdentityReference "has the following rights:" $accessrule.FileSystemRights
}
Write-Host "------"
$UserRechten = #()
foreach($accessrule in $acl.Access)
{
$UserRechten += "{0}:{1};" -f ($accessrule.IdentityReference,$accessrule.FileSystemRights)
}
Write-Host "================================================================"
# -join $UserRechten
# $Userrechten | out-string
# $UserRechten = [system.String]::Join(" ", $UserRechten)
$separator = ";"
[string]::Join($separator,$UserRechten)
$certs | Add-Member -MemberType NoteProperty -Name "UserRights" -Value $UserRechten -Force
Write-Host "UserRechten has value : "$UserRechten
Get-Member $certs.GetValue("UserRights")
Write-Host "================================================================"
} }
$Certs | Add-Member -MemberType NoteProperty -Name "MachineName" -Value $env:COMPUTERNAME -Force
# $certs | Add-Member -MemberType NoteProperty -Name "Store" -Value 'My' -Force $RunDate = Get-Date -Format 'yyyy-MM-dd' $certs | Add-Member -MemberType NoteProperty -Name "RunDate" -Value $RunDate -Force $certs | Add-Member -MemberType NoteProperty -Name "Owner" -Value $env:USERNAME -Force
$Certs | Select * | Export-Csv c:\Certificaten\LocalCertsAll_$env:COMPUTERNAME.csv
$Certs | Select MachineName, Owner, PSParentPath, DnsNameList, PSChildName, NotBefore, NotAfter, Rundate, EnhancedKeyUsageList, HasPrivateKey, SerialNumber, Issuer, Subject, FriendlyName, UserRigthts |
Export-CSV c:\Certificaten\Localcerts_$env:COMPUTERNAME.csv
As noted in the comments, Get-Member is probably not what you're looking for
You (almost certainly) don't want to add the UserRights member property to the $Certs array, but rather to the individual objects in $Certs.
(I removed a bunch of superfluous Write-Host statements for readability):
$CertsAmended = foreach($Certificate in $certs)
{
if($certificate.HasPrivateKey)
{
$rsaFile = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$fullPath = "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\" + $rsaFile
$acl = Get-Acl -Path $fullPath
# Create the UserRights value using -join
$UserRechten = #(foreach($accessrule in $acl.Access){
Write-Host "User" $accessrule.IdentityReference "has the following rights:" $accessrule.FileSystemRights
"{0}:{1}" -f ($accessrule.IdentityReference,$accessrule.FileSystemRights)
}) -join ";"
# Add the property to the individual object
$Certificate | Add-Member -MemberType NoteProperty -Name "UserRights" -Value $UserRechten
Write-Host "Userrights: " $UserRechten
# "Drop" the certificate object (now with a UserRights value) back onto the pipeline
$Certificate
}
}
Now you can export the $CertsAmended array to CSV all you want
If you find the $var = #(foreach($item in $collection){}) -join ';' displeasing to the eye, break it into two statements:
$UserRechten = foreach($accessrule in $acl.Access)
{
# Create UserRight string here, without ;
}
$UserRechten = $UserRechten -join ';'
For the $fullPath variable, you may want to use the Join-Path cmdlet:
$fullPath = Join-Path "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\" $rsaFile

Resources