Before calling a recursive function (RecursiveWebs()) $webinfo.Count is 1. During the execution of recursive function, Count is increased, but as soon as the recursive function is completed and control returns to the main function, $webinfo.Count again decreases to 1.
function RecursiveWebs($web) {
$Properties = #{
Title = $web.Title
URL = $web.Url
}
$webinfo += New-Object PSObject -Property $properties
foreach ($w in $web.Webs) {
if ($w.Webs.Count -gt 0) {
RecursiveWebs $w
}
}
}
$global:webinfo = #()
$sites = Get-SPOSite -Limit All
foreach ($site in $sites) {
Write-Host $site.Url
try {
$Properties = #{
Title = $site.Title
URL = $site.Url
NoOfSubsites = $site.Webs.Count
}
$webinfo += New-Object PSObject -Property $properties
if ($site.Webs.Count -gt 0) {
RecursiveWebs $site
}
} catch {
Write-Host $_ -ForegroundColor Red
}
}
Replaces $webinfo with $Global:webinfo everywhere in the code and it did the trick
Related
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()
I'm having a hard time converting results from Invoke-SqlCmd to a PSCustomobject.
So, I input my query and SQL server, then I run the Invoke-SqlCmd function, and then I try to add data from that (the database logical name, and the autogrowth status) to my PSCustomobject, so I can return it back to my modules public function.
$sqlinstance = "---"
$query = "---"
if ($sqlInstance -match "---") {
$dbAutogrowIGP = Invoke-Sqlcmd -Query $query -ServerInstance $sqlInstance
if ($dbAutogrowIGP.Growth -gt 100 -and $dbAutogrowIGP.Growth -lt 500) {
$autogrowStatus = [PSCustomObject]#{
'SQL_Instance' = $dbAutogrowIGP.LogicalName
'Check' = "Autogrow"
'Status' = "green"
'Status_reason' = ""
}
New-Object -Type Dictionary -Property $autogrowStatus
}
foreach ($db in $dbAutogrowIGP) {
if ($db.Growth -lt 100 -or $db.Growth -gt 500 -and $db.Growth -notlike "%") {
$autogrowStatus = [PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "red"
'Status_reason' = "$($db.LogicalName) has autogrowth set to $($db.Growth)."
}
New-Object -Type Dictionary -Property $autogrowStatus
}
if ($db.Growth -like "%") {
$autogrowStatus = [PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "yellow"
'Status_reason' = "$($db.LogicalName) has autogrowth set percentually, it should be an absolute number."
}
New-Object -Type Dictionary -Property $autogrowStatus
}
}
}
return $autogrowStatus
I've debugged it, and I've noticed it fails on the New-object call. I've tried both Dictionary and PSObject/PSCustomObject - however neither works
In my other functions, this works as expected, however in those, I'm using dbatools to make a call.
$getLogSizeIGP = Get-DbaDbLogSpace -sqlInstance $sqlInstance
if ($getLogSizeIGP.LogSize.Gigabyte -lt 10 -and $getLogSizeIGP.LogSpaceUsedPercent -lt 50) {
$logStatus = #{
'SQL_Instance' = $getLogSizeIGP.SqlInstance
'Check' = "Log_size"
'Status' = [gmEnvStatuses]::green
'Status_reason' = ""
}
New-Object -Type PSObject -Property $logStatus
}
How would I go about solving this issue?
This is the whole error message:
New-Object : Cannot bind parameter 'Property'. Cannot convert the "#{SQL_Instance=Maintenance_log; Check=Autogrow; Status=red; Status_reason=Maintenance_log has autogrowth set to 10%.}" value of type "System.Management.Automation.PSCustomObject" to type
"System.Collections.IDictionary".
At C:\Users\---\Desktop\autogrowth.ps1:50 char:55
+ New-Object -Type Dictionary -Property $autogrowStatus
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Object], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.NewObjectCommand
Thanks!
The easiest way of collecting this data is by capturing it all at the beginning of the if ($sqlInstance -match "---") { statement and simply output the PsCustomObjects without trying to convert them.
Something like
$sqlinstance = "---"
$query = "---"
$autogrowStatus = if ($sqlInstance -match "---") {
$dbAutogrowIGP = Invoke-Sqlcmd -Query $query -ServerInstance $sqlInstance
if ($dbAutogrowIGP.Growth -gt 100 -and $dbAutogrowIGP.Growth -lt 500) {
# output the object to be captured in the $autogrowStatus variable
[PSCustomObject]#{
'SQL_Instance' = $dbAutogrowIGP.LogicalName
'Check' = "Autogrow"
'Status' = "green"
'Status_reason' = ""
}
}
foreach ($db in $dbAutogrowIGP) {
if ($db.Growth -lt 100 -or $db.Growth -gt 500 -and $db.Growth -notlike "%") {
[PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "red"
'Status_reason' = "$($db.LogicalName) has autogrowth set to $($db.Growth)."
}
}
if ($db.Growth -like "%") {
[PSCustomObject]#{
'SQL_Instance' = $db.LogicalName
'Check' = "Autogrow"
'Status' = "yellow"
'Status_reason' = "$($db.LogicalName) has autogrowth set percentually, it should be an absolute number."
}
}
}
}
return $autogrowStatus
The variable $autogrowStatus will become an array of PSCustomObjects to handle in the rest of your functions.
Hope this helps
I'm tinkering with the code below. It should create a PoshRSJob and run the function foo again in the runspace there.
I want to be able to turn the $list parameter into an [array] or [string[]], but when I do it throws errors. I considered flattening my array into a string, but if I change $list3 to include a space or comma in the string it also throws an error. I believe it is this line that is causing the issue, but I don't know why or what to do to circumvent this issue:
ScriptBlock = [scriptblock]::Create("`$_ | $($PSCmdlet.MyInvocation.MyCommand.Name) -Parallel:`$false -fn:$fn -sqlQuery:$SQLQuery -option:$option -List:$List")
Code:
function foo {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[Alias("ComputerName")]
[PSObject] $InputObject,
[switch] $Parallel = $true,
[string] $fn,
[string] $sqlQuery = "none",
[string] $option = "none",
[int] $number,
[int] $Throttle = 100,
#Want this to be [array] or [string[]] :
[string] $List = "none"
)
begin {
$batch = [System.Guid]::NewGuid().Guid #run all jobs under same batch number
}
process {
if ($Global:debugging -eq $true){$host.ui.WriteDebugLine("fn:$fn | SQLQuery:$sqlQuery")}
if (!$Parallel) {
$server = $InputObject.name
switch($fn){
Manage{ return $list }
} #end switch
} else {#region Parallel run
$jobArguments = #{
Throttle = $Throttle
Batch = $batch
FunctionsToLoad = $PSCmdlet.MyInvocation.MyCommand.Name
#This is the problematic line:
ScriptBlock = [scriptblock]::Create("`$_ | $($PSCmdlet.MyInvocation.MyCommand.Name) -Parallel:`$false -fn:$fn -sqlQuery:$SQLQuery -option:$option -List:$List")
}
if ($_ -and $_ -isnot [string]) { $serverName=$_ } else { $serverName=$InputObject.name }
#(if ($_ -and $_ -isnot [string]) { $_ } else { $InputObject }) | Start-RSJob #jobArguments | Out-Null
} #endregion
}
end {#region Wait for results and return them
if ($Parallel) {
Get-RSJob -batch $batch | Wait-RSJob -ShowProgress | Out-Null
}#endregion
}
}
$obj = New-Object -TypeName PSObject -Property #{
Name = 'server1'
Other = 'other'
}
$list2 = $obj
$list3 = "item1-item2"
$list2 | foo -fn 'Manage' -number 2 -option Q -List $list3
This is the error:
The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
Does anyone know how to get this working so I can pass a list into the runspace?
I'm currently trying to make a function that gets all possible combinations of array values.
I have come up with a non function version but it's limited to 3 values so i'm trying to make a function out of it to become more Dynamic
I tried searching SO but could not find a powershell example of what i was trying to do, i could find a PHP version but i'm very limited in my PHP
PHP: How to get all possible combinations of 1D array?
Non-function Script
$name = 'First','Middle','Last'
$list = #()
foreach ($c1 in $name) {
foreach ($c2 in $name) {
foreach ($c3 in $name) {
if (($c1 -ne $c2) -and ($c2 -ne $c3) -and ($c3 -ne $c1))
{
$list += "$c1 $c2 $c3"
}
}
}
}
This gives me the result
First Middle Last
First Last Middle
Middle First Last
Middle Last First
Last First Middle
Last Middle First
I'm not sure how i would rearrange the values when i'm recursing the function, this is what i have so far:
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function Get-Combinations
{
[CmdletBinding()]
[OutputType([int])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string[]]$Array,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$false,
Position=1)]
[string]$Temp,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$true,
Position=2)]
[string[]]$Return
)
Begin
{
Write-Verbose "Starting Function Get-Combinations with parameters `n`n$($Array | Out-String)`n$temp`n`n$($Return | Out-String)"
If ($Temp)
{
$Return = $Temp
}
$newArray = new-object system.collections.arraylist
}
Process
{
Write-Verbose ($return | Out-String)
For($i=0; $i -lt $Array.Length; $i++)
{
#Write-Verbose $i
$Array | ForEach-Object {$newArray.Add($_)}
$newArray.RemoveAt($i)
Write-Verbose ($newArray | Out-String)
if ($newArray.Count -le 1)
{
Get-Combinations -Array $newArray -Temp $Temp -Return $Return
}
else
{
$Return = $Temp
}
}
$newArray
}
End
{
Write-Verbose "Exiting Function Get-Combinations"
}
}
$combinations = #("First","First2","Middle","Last")
$Combos = Get-Combinations -Array $combinations
$Combos
But the output i'm getting is all over the place
First2
Last
First2
Last
First
First2
Middle
Last
First
First2
Middle
Last
28/08 Update
Getting closer but still getting weird output
<#
.Synopsis
Short description
.DESCRIPTION
Long description
.EXAMPLE
Example of how to use this cmdlet
.EXAMPLE
Another example of how to use this cmdlet
#>
function Get-Combinations
{
[CmdletBinding()]
[OutputType([int])]
Param
(
# Param1 help description
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
[string[]]$Array,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$false,
Position=1)]
[string]$Temp,
# Param1 help description
[Parameter(Mandatory=$false,
ValueFromPipelineByPropertyName=$true,
Position=2)]
[string[]]$Return
)
Begin
{
Write-Verbose "Starting Function Get-Combinations with parameters `n`n$($Array | Out-String)`n$temp`n`n$($Return | Out-String)"
If ($Temp)
{
$Return += $Temp
}
#$newArray = new-object [System.Collections.ArrayList]
#$Array | ForEach-Object {$newArray.Add($_) | Out-Null}
[System.Collections.ArrayList]$newArray = $Array
}
Process
{
Write-Verbose "return -> $return"
For($i=0; $i -lt $Array.Length; $i++)
{
Write-Verbose "`$i -> $i"
$element = $newArray[0]
$newArray.RemoveAt(0)
Write-Verbose "`$newArray -> $newArray"
Write-Verbose "Element -> $element"
if ($newArray.Count -gt 0)
{
Get-Combinations -Array $newArray -Temp (($temp + " " +$element).Trim()) -Return $Return
}
else
{
$Return = $Temp + " " + $element
}
}
$return
}
End
{
Write-Verbose "Exiting Function Get-Combinations"
}
}
$combinations = #("First","First2","Middle","Last")
$return = #()
$Combos = Get-Combinations -Array $combinations -Return $return
$Combos
New output (Yes there is a space before the 'Last' value, no i have no idea why)
First First2 Middle Last
First First2 Last
First Middle Last
First Last
First2 Middle Last
First2 Last
Middle Last
Last
Here is my solution:
function Remove ($element, $list)
{
$newList = #()
$list | % { if ($_ -ne $element) { $newList += $_} }
return $newList
}
function Append ($head, $tail)
{
if ($tail.Count -eq 0)
{ return ,$head }
$result = #()
$tail | %{
$newList = ,$head
$_ | %{ $newList += $_ }
$result += ,$newList
}
return $result
}
function Permute ($list)
{
if ($list.Count -eq 0)
{ return #() }
$list | %{
$permutations = Permute (Remove $_ $list)
return Append $_ $permutations
}
}
cls
$list = "x", "y", "z", "t", "v"
$permutations = Permute $list
$permutations | %{
Write-Host ([string]::Join(", ", $_))
}
EDIT: the same in one function (Permute). This is cheating a bit, however since I replaced plain functions whith lambdas. You could replace recursive calls with a stack you handle yourself, but that would make the code unecessarily complex ...
function Permute ($list)
{
$global:remove = {
param ($element, $list)
$newList = #()
$list | % { if ($_ -ne $element) { $newList += $_} }
return $newList
}
$global:append = {
param ($head, $tail)
if ($tail.Count -eq 0)
{ return ,$head }
$result = #()
$tail | %{
$newList = ,$head
$_ | %{ $newList += $_ }
$result += ,$newList
}
return $result
}
if ($list.Count -eq 0)
{ return #() }
$list | %{
$permutations = Permute ($remove.Invoke($_, $list))
return $append.Invoke($_, $permutations)
}
}
cls
$list = "x", "y", "z", "t"
$permutations = Permute $list
$permutations | %{
Write-Host ([string]::Join(", ", $_))
}
I tried to learn something new and help you out but Im stuck. maybe this will help you get in the right direction but I dont know enough about Powershell recursion to figure this out. I converted the php to powershell and in theory it should work but it doesnt.
$array = #('Alpha', 'Beta', 'Gamma', 'Sigma')
function depth_picker([system.collections.arraylist]$arr,$temp_string, $collect)
{
if($temp_string -ne ""){$collect += $temp_string}
for($i = 0; $i -lt $arr.count;$i++)
{
[system.collections.arraylist]$arrCopy = $arr
$elem = $arrCopy[$i]
$arrCopy.removeRange($i,1)
if($arrCopy.count -gt 0){
depth_picker -arr $arrCopy -temp_string "$temp_string $elem" -collect $collect}
else{$collect += "$temp_string $elem"}
}
}
$collect = #()
depth_picker -arr $array -temp_string "" -collect $collect
$collect
It seems to work and will get you the first set of possibles:
Alpha
Alpha Beta
Alpha Beta Gamma
Alpha Beta Gamma Sigma
But for some reason that I cant figure out when it gets back to the previous functions and does $i++ then checks ($i -lt $arr.count) $arr.count it always 0 so it never goes to the next iteration to continue finding the possibilities.
Hopefully someone else can fix what I cant seem to figure out as I dont know enough about recursion. But it seems that with each level of depth called the previous depth level $arr variable and values is lost.
Here is my solution with a recursive function. It generates space separated strings but it's quite simple to split each element with $list[$i].split(" "):
function Get-Permutations
{
param ($array, $cur, $depth, $list)
$depth ++
for ($i = 0; $i -lt $array.Count; $i++)
{
$list += $cur+" "+$array[$i]
if ($depth -lt $array.Count)
{
$list = Get-Permutations $array ($cur+" "+$array[$i]) $depth $list
}
}
$list
}
$array = #("first","second","third","fourth")
$list = #()
$list = Get-Permutations $array "" 0 $list
$list
The solution posted by Micky Balladelli almost worked for me. Here is a version that does not duplicate values:
Function Get-Permutations
{
param ($array_in, $current, $depth, $array_out)
$depth++
$array_in = $array_in | select -Unique
for ($i = 0; $i -lt $array_in.Count; $i++)
{
$array_out += ($current+" "+$array_in[$i]).Trim()
if ($depth -lt $array_in.Count)
{
$array_out = Get-Permutations $array_in ($current+" "+$array_in[$i]) $depth $array_out
}
else {}
}
if(!($array_out -contains ($array_in -Join " "))) {}
for ($i = 0; $i -lt $array_out.Count; $i++)
{
$array_out[$i] = (($array_out[$i].Split(" ")) | select -Unique) -Join " "
}
$array_out | select -Unique
}
I am interating through a list of Microsoft.SqlServer.Management.Smo.Server objects and adding them to a hashtable like so:
$instances = Get-Content -Path .\Instances.txt
$scripts = #{}
foreach ($i in $instances)
{
$instance = New-Object Microsoft.SqlServer.Management.Smo.Server $i
foreach($login in $instance.Logins)
{
$scripts.Add($instance.Name, $login.Script())
}
}
So far so good. What I want to do now is append a string to the end of the hashtable value. So for an $instance I want to append a string to the hashtable value for that $instance. How would I do that? I have started with this, but I'm not sure if I'm on the right track:
foreach ($db in $instance.Databases)
{
foreach ($luser in $db.Users)
{
if(!$luser.IsSystemObject)
{
$scripts.Set_Item ($instance, <what do I add in here?>)
}
}
}
Cheers
$h= #{}
$h.add("Test", "Item")
$h
Name Value
---- -----
Test Item
$h."Test" += " is changed"
$h
Name Value
---- -----
Test Item is changed
I would go with this code.
$instances = Get-Content -Path .\Instances.txt
$scripts = #{}
foreach ($i in $instances)
{
$instance = New-Object Microsoft.SqlServer.Management.Smo.Server $i
foreach($login in $instance.Logins)
{
$scripts[$instance.Name] = #($scripts[$instance.Name]) + $login.Script().ToString()
}
}
.
foreach ($db in $instance.Databases)
{
foreach ($luser in $db.Users)
{
if(!$luser.IsSystemObject)
{
$scripts[$instance] = #($scripts[$instance]) + $luser.Script().ToString()
}
}
}
The result will be a hash table with each instance as a key, and an array of strings where each string is the T-SQL script for a user.
The .Script() method returns a string collection. There's probably a more elegant way of doing it, but replacing
$scripts.Set_Item ($instance, <what do I add in here?>)
with
$val = $scripts[$instance]
$val.Add("text to add")
$scripts.Set_Item($instance, $val)
should work.
$test = #{}
$test.Hello = "Hello World"
Write-Host "message from $($test.Hello)"
$test.Hello += " Cosmonaut"
Write-Host "message from $($test.Hello)"