How to append to powershell Hashtable value? - sql-server

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)"

Related

PowerShell problem with array and grouping objects

I am having problems to group an array of objects by Environment. The following script show three environments with servers in each one. Now, I am trying to loop those objects by groups but getting errors with the Environment names. The following is my sample logic:
$array = #(("DEVELOPMENT", ("SRV1")), ("QA", ("SRV2", "SRV3")), ("PRODUCTION", ("SRV4", "SRV5")))
ForEach ($system in $array) {
$envName = $array[0]
ForEach ($hostname in $system[1]) {
Write-Host ("Result for " + $hostname + " in " + $envName)
}
}
The variable name $envName always returns the same wrong result.
[0]:"DEVELOPMENT"
[1]:"SRV1"
How can I group and loop $array[0] and $system[1] in the following way?
DEVELOPMENT = SRV1
QA = SRV2, SRV3
PRODUCTION = SRV4, SRV5
Another option would be to create an object array instead of an embedded string array.
class Server
{
[string]$Environment
[string]$ServerName
}
$Development = #("SRV1")
$Qa = #("SRV2", "SRV3")
$Production = #("SRV4", "SRV5")
$Array = #()
foreach ($System in $Development)
{
$Server = [Server]::new()
$Server.ServerName = $System
$Server.Environment = "Development"
$Array += $Server
}
foreach ($System in $Qa)
{
$Server = [Server]::new()
$Server.ServerName = $System
$Server.Environment = "QA"
$Array += $Server
}
foreach ($System in $Production)
{
$Server = [Server]::new()
$Server.ServerName = $System
$Server.Environment = "Production"
$Array += $Server
}
foreach ($System in $Array)
{
Write-Host "Result for $($System.ServerName) in $($System.Environment)"
}
The solution in the comments
"Instead of $envName = $array[0] you should write $envName = $system[0]"

Find duplicate values in an array

i have the following code:
cls
Get-Module -ListAvailable | Import-Module
Import-Module ActiveDirectory
$Groups = Get-ADGroup -Filter {name -like 'XYZ'} | select name -
ExpandProperty name
$i=0
$tot = $Groups.count
$Table = #()
$Record = #{
"Group Name" = ""
"Name" = ""
"username" = ""
}
Foreach ($Group in $Groups) {
#// Set up progress bar
$i++
$status = "{0:N0}" -f ($i / $tot * 100)
Write-Progress -Activity "Exporting AD Groups" -status "Processing
Group $i of $tot : $status% Completed" -PercentComplete ($i / $tot *
100)
$Arrayofmembers = Get-ADGroupMember -identity $Group -recursive |
select name, SamAccountName
foreach ($Member in $Arrayofmembers) {
$Record."Group Name" = $Group
$Record."Name" = $Member.name
$Record."username" = $Member.SamAccountName
$objRecord = New-Object PSObject -property $Record
$Table += $objrecord
}
}
Write-Host $Table
which works perfectly but i want to list all duplicates in the $Record."Name" = $Member.name with specific group so for example:
username=barry is duplicated in GROUP XYZ
i tried already the following:
ForEach ($Element in $Table)
{
If (($Table -match $Element).count -gt 1)
{
"Duplicates detected"
}
}
The simplest answer is to just pipe $Table to Group-Object and filter for groups with a count greater than one at the end as such:
$Table | Group 'Group Name','Name' | Where{$_.Count -gt 1}
If you are looking to do this in the middle of your loop you could do so by grouping the results of Get-ADGroupMember, but I think it'll probably be faster to do it all at the end.
You could simply keep track of the members using a hashtable:
$seen = #{};
foreach ($Member in $Arrayofmembers)
{
if($seen[$Member.Name])
{
Write-Host "$($Member.Name) is duplicated in group $Group";
}
else
{
$seen.Add($Member.Name, $true);
}
# ... rest of the loop
}

recursive function - array values disappears

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

Easy way to List info from arrays

I have the code below which checks the registry for entries (more than 20 of them) and if it doesn't exists it creates a registry key and adds it to an array.
After that I need to check for all the names in the array to my other array and if it matches, I need it to pull the info from my second array and show it on the screen(the log location, registry location etc). But Can't really figure out how to match the array and write in on the screen without writing very long if statements.
Does anyone know a good way of doing this?
Thanks in advance!
$Reg = "HKLM:\Software\"
$NeedtoCheck = #()
$testing = #("Test1Name","Test2Name", "Test3Name")
$allTests = #(
$Test1 = #{
Name = "Test1"
Logfile = "C:\Checking\test1.log"
Version = "16"
RegName = "test1Nameinfo*"
Installname = "InstallTest1"
UninstallName = "UninstallTest1"
},
$Test2 = #{
Name = "Test"
Logfile = "C:\test2.log"
Version = "7"
RegName = "test2Nameinfo*"
Installname = "InstallTest2"
UninstallName = "UninstallTest2"
},
$Test3 = #{
Name = "Test3"
Logfile = "C:\Temp\Checkhere\test3.log"
Version = "99"
RegName = "test3Nameinfo*"
Installname = "InstallTest3"
UninstallName = "UninstallTest3"
}
$Test1Name = $Test1.name
$Test1Logfile = $Test1.Logfile
$Test1Version = $Test1.Version
$Test1RegName = $Test1.RegName
$Test1Install = $Test1.InstallName
$Test1Uninstall = $Test1.UninstallName
$Test2Name = $Test2.name
$Test2Logfile = $Test2.Logfile
$Test2Version = $Test2.Version
$Test2RegName = $Test2.RegName
$Test2Install = $Test2.InstallName
$Test2Uninstall = $Test2.UninstallName
$Test3Name = $Test3.name
$Test3Logfile = $Test3.Logfile
$Test3Version = $Test3.Version
$Test3RegName = $Test3.RegName
$Test3Install = $Test3.InstallName
$Test3Uninstall = $Test3.UninstallName
Foreach($Test in $testing){
$Key = (Get-Item "Reg").getvalue("$Test")
IF($Key -eq $null)
{
New-Itemproperty -path "HKLM:\Software\" -value "Check" -PropertyType string -name $Test -Force -ErrorAction SilentlyContinue
Write-Host "$Test created"
$Needtocheck += $Test
}
ELSEIF($key -eq "Check")
{
$Needtocheck += $Test
}
ELSE
{
Write-Host "$Test already Checked"
}
}
Foreach($item in $NeedtoCheck)
{
If($item -match $Test1Name)
{
Write-Host "$Test1Name info"
Write-host "$Test1Name`
$Test1Logfile`
$Test1Version`
$Test1RegName`
$Test1Install`
$Test1Uninstall`
}
Else
{
Write-Host "Not in the list"
}
}
....
This code doesn't make a lot of sense to be honest. If you want 20 checks to be setup, and then only run certain checks, then that's fine, but you really don't need additional cross checking to reference one array against another array, and redefining things like you do when you assign variables for each values in each hashtable. Personally I'd make objects not hashtables, but that's me. Actually, probably even better, make a hashtable with all available tests, then for the value make an object with the properties that you need. Oh, yeah, that'd be the way to go, but would need a little re-writing. Check this out...
$Reg = 'HKLM:\Software\'
$NeedtoCheck = #()
$testing = #('Test2','Test1','NotATest')
#Define Tests
$AllTests = #{'Test1' = [PSCustomObject]#{
Name = "Test1"
Logfile = "C:\Checking\test1.log"
Version = "16"
RegName = "test1Nameinfo*"
Installname = "InstallTest1"
UninstallName = "UninstallTest1"
}
'Test2' = [PSCustomObject]#{
Name = "Test"
Logfile = "C:\test2.log"
Version = "7"
RegName = "test2Nameinfo*"
Installname = "InstallTest2"
UninstallName = "UninstallTest2"
}
'Test3' = [PSCustomObject]#{
Name = "Test3"
Logfile = "C:\Temp\Checkhere\test3.log"
Version = "99"
RegName = "test3Nameinfo*"
Installname = "InstallTest3"
UnnstallName = "UninstallTest3"
}
}
#$allTests = #($Test1,$Test2,$Test3)
Foreach($Test in $Testing){
If($Test -in $allTests.Keys){
$Key = (Get-Item $Reg).getvalue($AllTests[$Test].RegName)
Switch($Key){
#Case - Key not there
{[string]::IsNullOrEmpty($_)}{
New-Itemproperty -path "HKLM:\Software\" -value "Check" -PropertyType string -name $AllTests[$Test].RegName -Force -ErrorAction SilentlyContinue
Write-Host "`n$Test created"
Write-Host "`n$Test info:"
Write-host $allTests[$test].Name
Write-host $allTests[$test].LogFile
Write-host $allTests[$test].Version
Write-host $allTests[$test].RegName
Write-host $allTests[$test].Installname
Write-host $allTests[$test].Uninstallname
}
#Case - Key = 'Check'
{$_ -eq "Check"}{
Write-Host "`n$Test info:`n"
Write-host $allTests[$test].Name
Write-host $allTests[$test].LogFile
Write-host $allTests[$test].Version
Write-host $allTests[$test].RegName
Write-host $allTests[$test].Installname
Write-host $allTests[$test].Uninstallname
}
#Default - Key exists and does not need to be checked
default {
Write-Host "`n$Test already Checked"
}
}
}Else{
Write-Host "`n$Test not in list"
}
}
That should do what you were doing before, with built in responses and checks. Plus this doesn't duplicate efforts and what not. Plus it allows you to name tests whatever you want, and have all the properties you had before associated with that name. Alternatively you could add a member to each test run, like 'Status', and set that to Created, Check, or Valid, then you could filter $AllTests later and look for entries with a Status property, and filter against that if you needed additional reporting.
You can filter down the tests you want to check like so, if I understand what you are asking for:
$Needtocheck | Where {$_ -in $testing} |
Foreach {... do something for NeedToCheck tests that existing in $testing ... }
I had to change several pieces of the code as there were syntax errors. Guessing most were from trying to create some sample code for us to play with. I have many comments in the code but I will explain some as well outside of that.
$Reg = "HKLM:\Software\"
$testing = "Test1","Test2", "Test3"
$allTests = #(
New-Object -TypeName PSCustomObject -Property #{
Name = "Test1"
Logfile = "C:\Checking\test1.log"
Version = "16"
RegName = "test1Nameinfo*"
Installname = "InstallTest1"
UninstallName = "UninstallTest1"
}
New-Object -TypeName PSCustomObject -Property #{
Name = "Test2"
Logfile = "C:\test2.log"
Version = "7"
RegName = "test2Nameinfo*"
Installname = "InstallTest2"
UninstallName = "UninstallTest2"
}
New-Object -TypeName PSCustomObject -Property #{
Name = "Test3"
Logfile = "C:\Temp\Checkhere\test3.log"
Version = "99"
RegName = "test3Nameinfo*"
Installname = "InstallTest3"
UninstallName = "UninstallTest3"
}
)
$passed = $testing | ForEach-Object{
# Changed the for construct to better allow output. Added the next line to make the rest of the code the same.
$test = $_
$Key = (Get-Item $Reg).getvalue($Test)
If($Key -eq $null){
# New-Itemproperty creates output. Cast that to void to keep it out of $passed
[void](New-ItemProperty -path "HKLM:\Software\" -value "Check" -PropertyType string -name $Test -Force -ErrorAction SilentlyContinue)
Write-Host "$Test created"
# Send this test to output
Write-Output $Test
} Elseif ($key -eq "Check")
{
# Send this test to output
Write-Output $Test
} Else {
Write-Host "$Test already Checked"
}
}
$allTests | Where-Object{$passed -contains $_.Name}
We run all the values in $testing and if one is created or already "Checked" then we send it down the pipe where it populates the variable $passed. The we take $allTests and filter out every test that has a match.

Get all combinations of an array

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
}

Resources