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]"
Related
I should start by saying that i'm new to PowerShell and i'm still in the learning phase. I've hit a road block and any help would be appreciated.
I have the following code:
# LOAD WINFORMS ASSEMBLY
[reflection.assembly]::LoadWithPartialName( "System.Windows.Forms")
[reflection.assembly]::LoadWithPartialName( "System.Drawing")
# CREATE FORMS
$Form = New-Object Windows.Forms.Form
$Form.text = "Post-Image Configuration Tool"
$Form.Width = 900
$Form.Height = 560
$Form.BackColor = "#3a73b8"
$Form.ForeColor = "White"
$Form.FormBorderStyle = "None"
$Form.StartPosition = "CenterScreen"
# START NETWORK CONFIGURATION PAGE
$GetConnectedAdapters = Get-WmiObject -Class Win32_NetworkAdapter -Filter "NetConnectionStatus = 2" | Select-Object NetConnectionID, Name, MACAddress
$netConfigList1 = New-Object System.Windows.Forms.CheckedListBox
$netConfigList1.Location = New-Object System.Drawing.Size(310,300)
$netConfigList1.Size = New-Object System.Drawing.Size(480,180)
$netConfigList1.Height = 100
$netConfigList1.BackColor = "#3a73b8"
$netConfigList1.ForeColor = "White"
$netConfigList1.BorderStyle = "None"
$netConfigList1.Font = $ListFont
$netConfigList1.add_SelectedIndexChanged({ListNetAdapters})
$netConfigListAdapters = #()
ForEach ($i in $GetConnectedAdapters.NetConnectionID){
$GetAdapterName = Get-WmiObject -Class Win32_NetworkAdapter |Where {$_.NetConnectionID -eq $i} | Select-Object Name, NetConnectionID, MACAddress
$AdapterName = $i +" - " + "("+ $GetAdapterName.Name +")"
$netConfigListAdapters += ,$AdapterName
}
$netConfigList1.Items.AddRange($netConfigListAdapters)
$netConfigSubtext5 = New-Object Windows.Forms.Label
$netConfigSubtext5.Location = New-Object Drawing.Point 290,400
$netConfigSubtext5.Size = New-Object Drawing.Point 590,20
$netConfigSubtext5.text = "• Select the Standby Adapter:"
$netConfigSubtext5.font = $SubTextFont
$netConfigComboBox1 = New-Object System.Windows.Forms.ComboBox
$netConfigComboBox1.Location = New-Object System.Drawing.Size(310,420)
$netConfigComboBox1.Size = New-Object System.Drawing.Size(260,20)
$netConfigComboBox1.Font = $SubTextFont
$netConfigComboBox1.DropDownStyle = "DropDownList"
[void] $netConfigComboBox1.Items.Add("None (All Adapters Active)")
$NetConfiguration = $netConfigList1,$netConfigSubtext5,$netConfigComboBox1
# CREATE FUNCTIONS
Function ListNetAdapters
{
$RemoveItems = #()
$AddItems = #()
for($index =0; $index -lt $netConfigList1.Items.Count; $index++)
{
$test = $netConfigList1.Items | Where-Object { $netConfigList1.Items.IndexOf($index) }
if($netConfigList1.GetItemChecked($index) -AND $netConfigComboBox1.Items -notcontains $test)
{
$AddItems += ,$test
}
ForEach($i in $netConfigComboBox1.Items){
IF(($netConfigList1.CheckedItems -notcontains $i) -AND ($i -ne 'None (All Adapters Active)')){$RemoveItems += ,$i}
}
}
ForEach ($i in $RemoveItems){$netConfigComboBox1.Items.Remove($i)}
ForEach ($i in $AddItems){$netConfigComboBox1.Items.Add($i)}
}
Function AddNetConfiguration
{
ForEach ($i in $NetConfiguration){$form.controls.add($i)}
}
AddNetConfiguration
# DISPLAY FORM
$form.ShowDialog()
Basically, what i'm trying to accomplish is exactly what you would see in the Advanced Settings of a NIC Team in Windows Server 2012/2012 R2. I want the network adapters selected in the CheckedListBox to populate in the ComboBox and be removed if unchecked.
I've installed WMF 4.0 on my Windows 7 PC and this seems to work well, but I get "System.Object[]" in Windows Server 2012. So i'm apparently missing the big picture or doing something wrong.
Windows Server 2012 comes with PowerShell v3.0, you have to make it to WMF4.0
Answer moved from question by editor
I was able to get it working after I fixed the $ListNetAdapters function. I think I was over complicating it before.
Function ListNetAdapters
{
$RemoveItems = #()
$AddItems = #()
ForEach($checkedItem in $netConfigList1.CheckedItems){
IF($netConfigComboBox1.Items -notcontains $checkedItem){$AddItems += ,$checkedItem}
}
ForEach($item2Badded in $AddItems){$netConfigComboBox1.Items.Add($item2Badded)}
ForEach($dropdownItem in $netConfigComboBox1.Items){
IF($netConfigList1.CheckedItems -notcontains $dropdownItem){$RemoveItems += ,$dropdownItem}
}
ForEach($item2Bremoved in $RemoveItems){
IF($item2Bremoved -ne 'None (All Adapters Active)'){$netConfigComboBox1.Items.Remove("$item2Bremoved")}
}
}
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 }
}
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.
I have a string of data which I turn into an array thus:
$cfg_data = #(
"AppName1,data1",
"AppName2,data2"
)
I then run a foreach query to work on each line at a time:
FOREACH($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
#Do stuff here
}
But I want to convert this from a string to an Object, so I can add/remove additional items,
and then run some more foreach statements, updating the different bits as I go.
I have got as far as:
FOREACH($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
$objHere = #(
#{
appItem = "$AppN";
appData = "$AppD";
})
}
But when I check $objHere it just has the last entry in it (AppName2, datat2)
I tried adding:
$b=0
and
$objHere[$b]
but then I get
Array assignment failed because index '1' was out of range.
What is the correct way to do this?
By declaring $objHere in the loop, you overwrite the value on each iteration. You need to initialise an empty array outside the loop and append to it from within the loop:
$objHere = #()
foreach($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
$objHere +=
#{
appItem = "$AppN";
appData = "$AppD";
}
}
In addition, you're not actually creating an object, you're creating a hashtable. If you wanted to create an object instead, you could do this:
$objHere = #()
foreach($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
$objHere += New-Object PSObject -property #{appItem = "$AppN";appData = "$AppD";}
}
Giving:
appItem appData
------- -------
AppName1 data1
AppName2 data2
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)"