PowerShell New Pipline variable with String/ArrayValue in "{}" - arrays

I need a solution to create an array in a certain format.
Background I export a SharePoint list and get my fields from an XML file.
The command (via PnP) to export is as follows:
$AllSPListItems = Get-PnPListItem $SharePointListName
$ItemsSelection = #{ L = "ID"; E = { $_["ID"] } },#{ L = "SP_SiteURL"; E = { $_["SP_SiteURL"] } }
$AllSPListItems | Select $ItemsSelection | Export-Csv -Path "C:\Temp\XYZ.csv"
That works, but here's what I want:
$ItemsSelection = #{ L = "$FieldDisplayName[0]"; E = { $_[$FieldURLName[0]] } },#{ L = "$FieldDisplayName[1]"; E = { $_[$FieldURLName[1]] } } }
I get the $FieldDisplayName and $FieldURLName variables from my XML.
Unfortunately I only get the following output:
#{ L = "ID"; E = { $_[$FieldURLName[0]] } },#{ L = "SP_SiteURL"; E = { $_[$FieldURLName[1]] } } }
Name Value
---- -----
E $FieldURLName[0]
L ID
E $FieldURLName[1]
L SP_SiteURL
So how can I get the value of "E", not the text?
The variables does not resolves.
Thanks a lot!

So how can I get the value of "E", not the text?
You get the text of the E scriptblock because it hasn't executed yet - that only happens once you use it with Select-Object.
To get the correct label names, remove the " quotes around the label expression:
$ItemsSelection = #{ L = $FieldDisplayName[0]; E = { $_[$FieldURLName[0]] } },#{ L = $FieldDisplayName[1]; E = { $_[$FieldURLName[1]] } } }
$AllSPListItems | Select $ItemsSelection | Export-Csv -Path "C:\Temp\XYZ.csv"

Ok i have a solution, is unattractive but apparently there is no other possibility.
I use the switch Case:
switch ($FieldDisplayName.Length){
1 {$items | Select #{ L = $FieldDisplayName[0]; E = { $_[$FieldURLName[0]] } } | Export-Csv -Path "C:\install\ExportV1.csv"}
2 {$items | Select #{ L = $FieldDisplayName[0]; E = { $_[$FieldURLName[0]] } },#{ L = $FieldDisplayName[1]; E = { $_[$FieldURLName[1]] } } | Export-Csv -Path "C:\install\ExportV1.csv"}
}

Related

Filter TreeView Nodes in PowerShell

I have a ton of Nodes in my TreeView, and have a textbox that filters through them to highlight the matched search. However, its a bit messy as it shows all the other nodes, and after I change my search, it leaves all nodes expanded.
I am trying to make something like this, https://www.codeproject.com/Tips/1000621/Filtering-and-Hiding-Tree-Nodes-WinForms
But I am using Windows forms / Powershell ISE and seem to struggle with implementing it into my own code.
For closing nodes I tried using things along the line of (Textbox.textlength -eq 0) to trigger a close all nodes function, but that was not working.
Here is what I want it too look like. Left is what I want, Right is what mine looks like.
Here is an example of the search function I am using.
Add-Type -AssemblyName System.Windows.Forms
function GetNodes([System.Windows.Forms.TreeNodeCollection] $nodes)
{
foreach ($n in $nodes) {
$n
GetNodes($n.Nodes)
}
}
$form = New-Object System.Windows.Forms.Form
$form.Text ="Test"
$form.Controls.AddRange(#(
($txt = [System.Windows.Forms.TextBox] #{
Location = [System.Drawing.Point]::new(8, 8);
Width = 100;
}),
($btn = [System.Windows.Forms.Button] #{
Location = [System.Drawing.Point]::new(120, 8);
Width = 50;
Text = "Search";
}),
($tree = [System.Windows.Forms.TreeView] #{
Location = [System.Drawing.Point]::new(8, 40);
Width = 170;
HideSelection = $false
})
))
$form.AcceptButton= $btn
$tree.Nodes.Add("A1", "A1")
$tree.Nodes.Add("A2", "A2")
$tree.Nodes[0].Nodes.Add("A11", "A11")
$tree.Nodes[0].Nodes.Add("A12", "A12")
$tree.Nodes[1].Nodes.Add("A21", "A21")
$tree.Nodes[1].Nodes.Add("A22", "A22")
$btn.Add_Click({param($sender,$e)
$nodes = GetNodes($tree.Nodes)
foreach ($node in $nodes) {
if($node.Text -like $txt.Text){
$tree.SelectedNode = $node
$node.EnsureVisible()
break
}
}
})
$form.ShowDialog() | Out-Null
$form.Dispose()
Assuming you are searching on a data source like a folder structure, this is what I'll do:
Create a function to get list of all directories recursively into a list
Create a function to filter the list of directories and return a list of directories which contain a specific text in their names.
Create a function to populate treeview
create a function to highlight treenode if it contains a specific text
Then in the text-changed event of the textbox, I'll filter and highlight tree:
Here is the code:
Add-Type -AssemblyName System.Windows.Forms
function GetPaths($root)
{
Get-ChildItem $root -Recurse -Directory | % {
$_.FullName.Replace($root, "").Trim("\")}
}
function FilterPaths($paths, $like)
{
$paths | ? {$_ -like "*$like*"} | % {
$i = $_.LastIndexOf("$like", [System.Globalization.CompareOptions]::IgnoreCase)
if($i -gt -1) {
$j = $_.IndexOf("\", $i, [System.Globalization.CompareOptions]::IgnoreCase)
if($j -gt -1) {
$_.SubString(0,$j)
} else {
$_
}
}
}
}
function GetNodes($nodes)
{
foreach ($n in $nodes) {
$n
GetNodes($n.Nodes)
}
}
function HighlightNodes($nodes, $like)
{
if(!$like){ return }
$nodes | ? {$_ -like "*$like*"} | % {
$_.BackColor = "Yellow"
}
}
function PopulateTree($treeView, $paths)
{
$treeView.Nodes.Clear()
foreach ($path in $paths)
{
$lastNode = $null
$subPathAgg = ""
foreach ($subPath in ($path -split '\\'))
{
$subPathAgg += ($subPath + '\')
$nodes = $treeView.Nodes.Find($subPathAgg, $true)
if ($nodes.Length -eq 0) {
if ($lastNode -eq $null) {
$lastNode = $treeView.Nodes.Add($subPathAgg, $subPath)
} else {
$lastNode = $lastNode.Nodes.Add($subPathAgg, $subPath)
}
} else {
$lastNode = $nodes[0]
}
}
}
}
$form = New-Object System.Windows.Forms.Form
$form.Text ="Test"
$form.Controls.AddRange(#(
($txt = [System.Windows.Forms.TextBox] #{
Location = [System.Drawing.Point]::new(8, 8);
Width = $form.ClientSize.Width - 16;
Anchor = [System.Windows.Forms.AnchorStyles]13
}),
($tree = [System.Windows.Forms.TreeView] #{
Location = [System.Drawing.Point]::new(8, 40);
Width = $form.ClientSize.Width - 16;
Anchor = [System.Windows.Forms.AnchorStyles]15
Height = 200;
HideSelection = $false
})
))
$form.AcceptButton= $btn
$root = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\ItemTemplates\CSharp"
$paths = GetPaths $root
PopulateTree $tree $paths
$tree.ExpandAll()
$txt.Add_TextChanged({param($sender,$e)
$tree.BeginUpdate()
$like = $txt.Text
$filtered = FilterPaths $paths $like
PopulateTree $tree $filtered
HighlightNodes (GetNodes $tree.Nodes) $like
$tree.ExpandAll()
$tree.TopNode = $tree.Nodes[0]
$tree.EndUpdate()
})
$form.ShowDialog() | Out-Null
$form.Dispose()

sum values from db with array_sum() stuck at 3 loop

after couple of days i finally got some values from 1st and 2nd line but after that at 4th line i got stacked and nothing works.
in need to get data from db where:
obrat | memb | tab3. | history
| member1 | 0. | 10
200 | member2 | member1. | 3
100 | member3 | member1. | 4
so if i will select from tab3 users which have my memb i need to sum their obrat and history. And doo the same think with them - select from tab3 where names are member1 and member2 and sum that.
my code:
while ($row = $resulto->fetch_assoc()) {
$memb2 = $row['memb'];
// second line member sel.
$resulto2 = $mysqli->query("SELECT * FROM users WHERE tab3='$memb2' ") or die($mysqli->error());
//pokud druhá linie
if ($resulto2->num_rows > 0) {
while ($row2 = $resulto2->fetch_assoc()) {
$memb3 = $row2['memb'];
} // should be 3. line
// get numbers from 2. line
$resulto2 = $mysqli->query("SELECT * FROM users WHERE tab3='$memb2' ") or die($mysqli->error());
$d = 0;
$rows2 = array();
$rows3c = array();
while ($rowd = $resulto2->fetch_array()) {
array_push($rows2, $rowd['obrat']);
array_push($rows3c, $rowd['history']);
}
$array2 = array_sum($rows2);
$count3 = array_sum($rows3c);
}
}
//end / get values first line
$resulto = $mysqli->query("SELECT * FROM users WHERE tab3='$memb' ") or die($mysqli->error());
$d = 0;
$rows = array();
$rows2c = array();
while ($rowd = $resulto->fetch_array()) {
array_push($rows, $rowd['obrat']);
array_push($rows2c, $rowd['history']);
}
$array = array_sum($rows);
$count2 = array_sum($rows2c);
}
some idea where is the problem?
or next way iam trying to :
$resulto2 = $mysqli->query("SELECT * FROM users WHERE tab3='$memb' ") or die($mysqli->error());
if ($resulto2->num_rows > 0) {
while ($row2 = $resulto2->fetch_assoc()) {
$memb2 = $row2['memb'];
$resulto2a = $mysqli->query("SELECT * FROM users WHERE tab3='$memb2' ") or die($mysqli->error());
while ($row2a = $resulto2a->fetch_assoc()) {
$memb3 = " OR memb='".$row2a['memb']."'";
//here i need to get one value which is sum from all users selecting
}
}

Compare similar values from hashtable with loop in Powershell

I have 2 hash tables :
[hashtable]$Localisation = #{
"Macdo" = "OU=France,OU=Paris";
"BurgerKing" = "OU=USA,OU=LA";
"Quick" = "OU=Japan,OU=Tokyo";
}
[hashtable]$Profil = #{
"Big Mac" = "Macdo";
"Whooper" = "BurgerKing";
"Burger" = "Quick, BurgerKing, Macdo";
"Fries" = "BurgerKing, Macdo";
"Coke" = "Quick, Macdo";
"HappyMeal" = "Macdo";
}
I need to get this kind of result:
"Big Mac" = "OU=France,OU=Paris"
"Whooper" = "OU=USA,OU=LA";
"Burger" = "OU=Japan,OU=Tokyo, OU=USA,OU=LA, OU=France,OU=Paris"
"Fries" = "OU=USA,OU=LA, OU=France,OU=Paris";
"Coke" = "OU=Japan,OU=Tokyo, OU=France,OU=Paris";
"HappyMeal" = "OU=France,OU=Paris";
or
Big Mac = OU=France,OU=Paris
Whooper = OU=USA,OU=LA
Burger = OU=Japan,OU=Tokyo,
OU=USA,OU=LA,
OU=France,OU=Paris
Fries = OU=USA,OU=LA,
OU=France,OU=Paris
Coke = OU=Japan,OU=Tokyo,
OU=France,OU=Paris
HappyMeal = OU=France,OU=Paris
I tried :
$tempLoca = #()
foreach ($value in $Profil.values) {
if($Localisation.Contains($value)) {
$tempLoca = $Localisation.Contains($value),$Profil.key
}
}
But I get :
$tempLoca
OU=France,OU=Paris
With my code I have only the last value. I don't know if I need to put my values in array or in hashtable (because they are multiple similar values).
Do you have an idea? Thanks
Try this:
[hashtable]$Localisation = #{
"Macdo" = "OU=France,OU=Paris";
"BurgerKing" = "OU=USA,OU=LA";
"Quick" = "OU=Japan,OU=Tokyo";
}
[hashtable]$Profil = #{
"Big Mac" = "Macdo";
"Whooper" = "BurgerKing";
"Burger" = "Quick, BurgerKing, Macdo";
"Fries" = "BurgerKing, Macdo";
"Coke" = "Quick, Macdo";
"HappyMeal" = "Macdo";
}
$tempLoca = #()
foreach ($key in $Profil.Keys) {
$locals = ($Profil.$key -split ',') | ForEach-Object { $_.Trim() }
$result = #()
foreach ($item in $locals) {
if($Localisation.ContainsKey($item)) {
$result += $Localisation.$item
}
}
$tempLoca += '"{0}" = "{1}"' -f $key, ($result -join '; ')
}
$temploca
It will output
"Big Mac" = "OU=France,OU=Paris"
"HappyMeal" = "OU=France,OU=Paris"
"Burger" = "OU=Japan,OU=Tokyo; OU=USA,OU=LA; OU=France,OU=Paris"
"Whooper" = "OU=USA,OU=LA"
"Fries" = "OU=USA,OU=LA; OU=France,OU=Paris"
"Coke" = "OU=Japan,OU=Tokyo; OU=France,OU=Paris"
Note that I combine the OU values from the $Localisation hash with a semicolon ; to distinct them from the values themselves. If that is not what you want, just replace ($result -join '; ') with ($result -join ', ')
An IMO more PowerShell like way, building a PSCustomObject and grouping it:
$ProfileLocalisation = ForEach ($key in $Profil.Keys) {
ForEach ($local in ($Profil.$key -split ',').Trim() ) {
[PSCustomObject]#{
Profile = $key
Localisation = $Localisation.$local
}
}
}
$ProfileLocalisation
Sample output:
Profile Localisation
------- ------------
Big Mac OU=France,OU=Paris
HappyMeal OU=France,OU=Paris
Burger OU=Japan,OU=Tokyo
Burger OU=USA,OU=LA
Burger OU=France,OU=Paris
Whooper OU=USA,OU=LA
Fries OU=USA,OU=LA
Fries OU=France,OU=Paris
Coke OU=Japan,OU=Tokyo
Coke OU=France,OU=Paris
And grouped:
$ProfileLocalisation | Group-Object Profile | ForEach-Object {
[PSCustomObject]#{
Profile = $_.Name
Localisations = ($_.Group.Localisation -join ';')
}
}
Profile Localisations
------- -------------
Big Mac OU=France,OU=Paris
HappyMeal OU=France,OU=Paris
Burger OU=Japan,OU=Tokyo;OU=USA,OU=LA;OU=France,OU=Paris
Whooper OU=USA,OU=LA
Fries OU=USA,OU=LA;OU=France,OU=Paris
Coke OU=Japan,OU=Tokyo;OU=France,OU=Paris

Return one of many variables from a function

I'm trying to figure out how to simplify this process, but it's not as simple as I thought.
I have a config file that looks similar to this:
[string][1][options]
$List = #(
"c:\path\to\file,1,-a,-b,-c,-d,-e"
)
The only items required are the [string] and the [1]. There are 10 options (-a, -b etc), potentially more.
Each of which is optional and could be supplied in any order.
In the main script I then do the following at present:
foreach ($a in $List) {
$dataSplit = $a -split"(,)"
$string = $dataSplit[0]
$number = $dataSplit[2]
$ds4 = $dataSplit[4]
if(!$ds4) {
$ds4 = "0"
} elseif($ds4.StartsWith("-a")) {
$a_set = 1
write-host "a_set has been set to $a_set"
} elseif($ds4.StartsWith("-b")) {
$b_set = 1
write-host "b_set has been set to $b_set"
}
. . .
if(!$ds5) {
$ds5 = "0"
}
. . .
As you can imagine this gets quite long. So I thought I would simplify it with a function. e.g.
function get-additional($item) {
if($item.StartsWith("-a")) {
$a_set = 1
Write-Host "$a_set has been set"
return $a_set
}
if($item.StartsWith("-b")) {
$b_set = 1
Write-Host "$b_set has been set"
return $b_set
}
}
And then call it thus:
if(!$ds4) {
$ds4 = "0"
} else {
get-additional($ds4)
}
Is there a way to do this? I've seen pleanty of examples if you only have a single variable to return, or even a fixed number, but none that allow for the return of 'one of many' variables.
Here is the (shortened) script in one if it helps:
$List = #(
"c:\path\to\file,1,-b,-c,-d,-e"
)
function get-additional($item) {
if($item.StartsWith("-a")) {
$a_set = 1
Write-Host "a_set has been set to $a_set"
return $a_set
}
if($item.StartsWith("-b")) {
$b_set = 1
Write-Host "b_set has been set to $b_set"
return $b_set
}
}
$a_set = 0
$b_set = 0
$c_set = 0
foreach ($a in $List) {
$dataSplit = $a -split"(,)"
$string = $dataSplit[0]
$number = $dataSplit[2]
$ds4 = $dataSplit[4]
Write-Host "ds4 = $ds4"
if(!$ds4) {
$ds4 = "0"
} else {
get-additional($ds4)
}
$ds5 = $dataSplit[6]
Write-Host "ds5 = $ds5"
if(!$ds5) {
$ds5 = "0"
} else {
get-additional($ds5)
}
}
Write-Host "a = $a_set"
Write-Host "b = $b_set"
The desired result at the end would be
a = 0
b = 1
- - - UPDATE 2015-11-30 16:54
In case it helps to understand what I am going for here's a Sample from my actual script
$cfg_AppList = #(
"C:\Path\to\application1\app1.exe instance1,1"
"C:\Path\to\application2\app2.exe instance2,1,-p12345"
"C:\Path\to\application3\app3.exe instance3,0"
"C:\Path\to\application3\app3.exe instance3,1,-p78901"
)
function get-additional($item)
{
$script:pval = "0"
if($item.StartsWith("-p"))
{
$script:pval = $ds4.substring(2)
write-host "$pval is a pval"
}
}
$AppObject = #()
foreach($a in $cfg_AppList)
{
$dataSplit = $a -split","
$AppVal = $dataSplit[0]
$checkVal = $dataSplit[1]
$ds4 = $dataSplit[2]
if(!$ds4)
{
$ds4 = "0"
}
else
{
get-additional($ds4)
}
$AppObject += New-Object PSObject -property #{
AppVal = "$AppVal";
checkVal = "$checkVal";
pval = "$pval";
}
}
The $AppObject object is then referenced and updated as the script progresses.
The values supplied in pval and (see below eval) will determine what happens.
I now need to add a second element -e which will be included thus:
$cfg_AppList = #(
"C:\Path\to\application1\app1.exe instance1,1"
"C:\Path\to\application2\app2.exe instance2,1,-p12345"
"C:\Path\to\application3\app3.exe instance3,0,-e"
"C:\Path\to\application3\app3.exe instance3,1,-e,-p78901"
)
It will be either selected 1 or not selected 0, and added to the $AppObject Array as eval=$eval (1|0).
Going forward I have more options I plan to introduce, hence the need to find the most efficient way to handle them all.
- - - UPDATE 2015-12-01 11:39
OK, What I have gone with is a combination of both ideas below.
Placing the options into an array and looping through them, then using a SWITCH statement to see which ones are set.
$AppObject = #()
foreach($a in $cfg_AppList)
{
$pval = 0
$eval = 0
$AppVal,$CheckVal,$options = $a -split","
foreach($opt in $options)
{
switch -wildcard ($opt)
{
'-p*' { $pval = $opt.substring(2) }
'-e' { $eval = 1 }
}
}
$AppObject += New-Object PSObject -property #{
AppVal = "$AppVal";
CheckVal = "$CheckVal";
pval = "$pval";
eval = "$eval";
}
}
First off, don't capture the , in your split operation if you're not planning to use it for anything, just use -split "," (no parentheses).
We can make use of multiple variable assignment to "shift" away to string and number 1:
$s,$n,$opts = "string,1,-a,-b,-c" -split ","
$opts will now contain the string array: #("-a","-b","-c")
The easiest way to check for whether a predetermined set of options is present or not, is to simply loop through all possible options and see if they are contained in the input string:
function Parse-InputString
{
param($InputString)
# prepare the options you want to check for
$PossibleOptions = "abcde".ToCharArray()
# Split the input string
$String,$Number,$Options = $InputString -split ","
# Create a new object with the string and number values
$OutputObject = New-Object psobject -Property #{
"String" = $String
"Number" = $Number
}
# Now inspect the $Options array to see if any of them are set
foreach($PossibleOption in $PossibleOptions){
$OptionSet = if($Options -contains "-$PossibleOption"){
1
} else {
0
}
# Add the information to the object
$OutputObject |Add-Member -MemberType NoteProperty -Name $PossibleOption -Value $OptionSet
}
# return the object carrying all the information
return $OutputObject
}
Now you can have your input string parsed nicely into an actual object:
PS C:\> Parse-InputString -InputString "c:\path\to\file,1,-b,-c,-d,-e"
Number : 1
String : c:\path\to\file
a : 0
b : 1
c : 1
d : 1
e : 1
The easiest way would be to update the global variables in your function without returning anything:
function Get-Additional($item) {
if ($item.StartsWith("-a")) {
$global:a_set = 1
Write-Host "a_set has been set to $a_set"
}
if ($item.StartsWith("-b")) {
$global:b_set = 1
Write-Host "b_set has been set to $b_set"
}
}
However, modifying global variables in functions is not a good practice, because it's difficult to debug. I wouldn't recommend going this route.
A better approach is to pass your current values as parameters into the function, return the modified values, and assign them back to variables.
function Get-Additional($item, $a, $b) {
if ($item.StartsWith("-a")) {
$a = 1
Write-Host "a_set has been set to $a_set"
}
if ($item.StartsWith("-b")) {
$b = 1
Write-Host "b_set has been set to $b_set"
}
#($a, $b)
}
$set_a, $set_b = Get-Additional $ds4 $set_a $set_b
In the above sample the function returns a list of the modified values (#($a, $b)), which are then assigned back to the list $set_a, $set_b. The return keyword is not required for returning something from a PowerShell function. It controls only where to return from a function, not what to return.
With that said, for your scenario I wouldn't use a function in the first place. A switch statement would be better suited for this kind of manipulation:
switch -wildcard ($ds4) {
'-a*' { $set_a = 1 }
'-b*' { $set_b = 1 }
}

Is there a function pointer or array of functions in PowerShell?

I would like to do something like this. Index into an array of functions and apply the appropriate function for the desired loop index.
for ($i = 0; $i -lt 9; $i++)
{
$Fields[$i] = $Fields[$i] | $($FunctionTable[$i])
}
#F1..F9 are defined functions or rather filter functions
$FunctionTable = {F1},
{F2},
{F3},
{F4},
{F5},
{F6},
{F7},
{F8},
{F9}
Here's an example of how to do this using the call (&) operator.
# define 3 functions
function a { "a" }
function b { "b" }
function c { "c" }
# create array of 3 functioninfo objects
$list = #(
(gi function:a),
(gi function:b),
(gi function:c)
)
0, 1, 2 | foreach {
# call functions at index 0, 1 and 2
& $list[$_]
}
-Oisin
p.s. this means your pipeline should bve amended to something like:
$Fields[$i] = $Fields[$i] | & $FunctionTable[$i]
Here is something similar also using the & operator:
function f1
{ "Exec f1" }
function f2
{ "Exec f2" }
function f3
{ "Exec f3" }
function f4
{ "Exec f4" }
function UseFunctionList ( [string[]]$FunctionList )
{
foreach ( $functionName in $functionList )
{
& $FunctionName
}
}
function Go
{
'List 1'
$FunctionList = 'f1','f2','f3','f4'
UseFunctionList $FunctionList
'List 2'
$FunctionList = 'f4','f3','f2'
UseFunctionList $FunctionList
}

Resources