Ok so I work for a small business, and they use a google spread sheet as the "Phone list"
for finding and contacting employees. I installed the PSGSUITE powershell module and it seems to work very well, but im new to powershell and coding in general. The filter sheet i made along with the phone list places the employees in there respective groups. Example then The code.
"phone list"
Name # Company code Ext. Department Job Title Email
Hayden 111-222-333 JOP IT Technician example#example.com
"filter 2sheet"
JOP SPD
hayden#.com lisa#.com
john#.com arron#.com
david#.com mike#.com
I want to add these emails to there respective google groups
## NOVA BEAZ ##
## add groups in google based on company title
###
####
# Import Modules
Import-Module PSGSuite
# Create Array of Groups
$Title = (Import-GSSheet -SpreadsheetId "1NtCT5ruoL4Kf4-ec55xe-L8esXcSY8orfd-zOFK4q4k" -SheetName "Filter" -Headers "None" -Range "A1:1")
$Title = $Title | % { $_ }
$Groups = (Get-GSgroup -Fields "Name" )
if($Title = $Groups)
#{add that users email to the group}
#else
{echo "there is now group that matches that"}
The main issue is I really just dont know how to correctly run through the arrays and select all the emails in that row to add to the google groups, I think I need a array or object list form of storing my emails, I want this to be dynamic.
Excerpts from my blog post on how to use the PSGusite module.
Please check if the following answers your question. If not, let me know.
User Process
To begin, we need to import the module and then use the command Get-GSDriveFileList to find the Google Sheet where our data is stored.
Next, we use the command Import-GSSheet to import our user and group data.
Get Data from GSheet
# Import module
Import-Module -Name PSGSuite
# Discover spreadsheet Id in drive file list
$Spreadsheet = Get-GSDriveFileList -Filter "name = 'UserManagement'"
# Get data from each sheet from Google spreadsheet
$UserData = Import-GSSheet -SpreadsheetId $Spreadsheet.Id -SheetName 'Users'
$GroupData = Import-GSSheet -SpreadsheetId $Spreadsheet.Id -SheetName 'Groups'
Create Organization Units
We use Get-GSOrganizationalUnit to determine if the OU exists. And then we use New-GSOrganizationalUnit to create it if it does not.
foreach ($Group in $GroupData) {
$SplitPath = $Group.OrgUnitPath -Split '/'
$ParentPath = $SplitPath[0..($SplitPath.Count -2)] -join '/'
$OUPath = $SplitPath[-1]
$OrgUnit = Get-GSOrganizationalUnit -SearchBase $Group.OrgUnitPath -SearchScope Base -ErrorAction SilentlyContinue
if ($OrgUnit) {
"Org Unit {0} exists at {1}" -f $OrgUnit.OrgUnitPath,$OrgUnit.ParentOrgUnitPath
} else {
"Org Unit {0} does not exist; attempting to create in {1}" -f $Group.OrgUnitPath,$ParentPath
try {
$GSOrgUnit = New-GSOrganizationalUnit -Name $OUPath.ToLower() -ParentOrgUnitPath $ParentPath -Description $Group.Description
"Created {0} : {1}" -f $GSOrgUnit.OrgUnitPath,$GSOrgUnit.Description
}
catch {
"Unable to create {0}" -f $Group.OrgUnitPath
}
}
}
Create Groups
Using the command Get-GSGroup, we check if the group exists. If the group does not already exist, use New-GSGroup to create the group from the spreadsheet.
foreach ($Group in $GroupData) {
$GSGroup = Get-GSGroup -Group $Group.Name -ErrorAction SilentlyContinue
if ($GSGroup) {
"Group {0} exists" -f $Group.Name
} else {
"Group {0} does not exist; attempting to create" -f $Group.Name
try {
$NewGSGroup = New-GSGroup -Name $Group.Name -Email $Group.Email -Description $Group.Description
"Created {0} : {1}" -f $NewGSGroup.Name,$NewGSGroup.Description
}
catch {
"Unable to create {0}" -f $Group.Name
}
}
}
Create Users
Create the users listed in the spreadsheet.
First, determine the department based on the user type.
Using the department, set the variable for the org unit path.
Create the required hashtable for CustomSchemas to add the EmployeeType to the user.
Generate a random secure password.
Using the command New-GSUser, create the new user.
If the user is successfully created, use the command New-GSUserAlias for best effort to create an email alias based on the user’s full name.
foreach ($User in $UserData) {
$Domain = $User.Email.Split('#')[1]
switch ($User.UserType) {
'Faculty' { $Department = 'Academics'}
'Staff' { $Department = 'Business' }
}
# Set OU path
$OrgUnitPath = $GroupData.Where({$_.Name -eq $Department}).OrgUnitPath
# Set employee type custom schema
$CustomSchemas = #{
CustomUniversity = #{
EmployeeType = $User.UserType
}
}
# Set a random secure string
$Password = ConvertTo-SecureString -String (Get-RandomPassword) -AsPlainText -Force
$NewGSUserParams = #{
PrimaryEmail = $User.Email
FullName = $User.FullName
GivenName = $User.GivenName
FamilyName = $User.FamilyName
OrgUnitPath = $OrgUnitPath
CustomSchemas = $CustomSchemas
Password = $Password
}
$NewUser = New-GSUser #NewGSUserParams -ErrorAction SilentlyContinue
if ($NewUser) {
'Created user {0} with primary email {1}' -f $User.FullName,$User.Email
} else {
'Failed to create user {0}' -f $User.Email
}
New-GSUserAlias -User $NewUser.PrimaryEmail -Alias ( $NewUser.Name.FullName.Replace(' ',''),$Domain -join '#') -ErrorAction SilentlyContinue | Out-Null
}
The Get-RandomPassword function is a mock-up. You would need to provide your own password method.
You can omit CustomSchemas from the hashtable. The blog post shows how to manually create new attributes, if you are interested.
Assign Users to Groups
Next, we use Get-GSUserList to get a list of all users in the parent OU, and then add the user to the group with Add-GSGroupMember.
$UserToGroupList = Get-GSUserList -SearchBase '/test' -SearchScope Subtree
foreach ($User in $UserToGroupList) {
switch -regex ($User.OrgUnitPath) {
'academics' { $GroupName = 'Academics'}
'business' { $GroupName = 'Business'}
}
try {
Add-GSGroupMember -Identity $GroupName -Member $User.User -ErrorAction Stop | Out-Null
'Added {0} to group {1}' -f $User.User,$GroupName
}
catch {
'Failed to add {0} to group {1}' -f $User.User,$GroupName
}
}
Note: I manually created a /test organizational unit and blocked automatic assignment of a license since I’m using my personal G Suite account. I don’t want any surprises at the end of the month.
Related
I have a script from here, this is the job :
function CaptureWeight {
Start-Job -Name WeightLog -ScriptBlock {
filter timestamp {
$sw.WriteLine("$(Get-Date -Format MM/dd/yyyy_HH:mm:ss) $_")
}
try {
$sw = [System.IO.StreamWriter]::new("$using:LogDir\$FileName$(Get-Date -f MM-dd-yyyy).txt")
& "$using:PlinkDir\plink.exe" -telnet $using:SerialIP -P $using:SerialPort | TimeStamp
}
finally {
$sw.ForEach('Flush')
$sw.ForEach('Dispose')
}
}
}
I'd like to get his to run against a list of IP addresses while also having a name associated with the IP to set the file name for each file. I was thinking something like $Name = Myfilename and $name.IP = 1.1.1.1 and using those in place of $FileName and $SerialIP, but have yet to be able get anything close to working or find an example close enough to what I'm trying for.
Thanks
Here is one way you could do it with a hash table as Theo mentioned in his helpful comment. Be aware that Jobs don't have a Threshold / ThrottleLimit parameter as opposed to Start-ThreadJob or ForEach-Object -Parallel since jobs run in a different process as you have already commented instead of instances / runspaces, there is no built-in way to control how many Jobs can run at the same time. If you wish have control over this you would need to code it yourself.
# define IPs as Key and FileName as Value
$lookup = #{
'1.2.3.4' = 'FileNameForThisIP'
'192.168.1.15' = 'AnotherFileNameForTHatIP'
}
# path to directory executable
$plink = 'path\to\plinkdirectory'
# path to log directory
$LogDir = 'path\to\logDirectory'
# serial port
$serialport = 123
$jobs = foreach($i in $lookup.GetEnumerator()) {
Start-Job -Name WeightLog -ScriptBlock {
filter timestamp {
$sw.WriteLine("$(Get-Date -Format MM/dd/yyyy_HH:mm:ss) $_")
}
try {
$path = Join-Path $using:LogDir -ChildPath ('{0}{1}.txt' -f $using:i.Value, (Get-Date -f MM-dd-yyyy))
$sw = [System.IO.StreamWriter]::new($path)
$sw.AutoFlush = $true
& "$using:plink\plink.exe" -telnet $using:i.Key -P $using:serialPort | TimeStamp
}
finally {
$sw.ForEach('Dispose')
}
}
}
$jobs | Receive-Job -AutoRemoveJob -Wait
The other alternative to the hash table could be to use a Csv (either from a file with Import-Csv or hardcoded with ConvertFrom-Csv).
Adding here another alternative to my previous answer, using a RunspacePool instance which has built-in a way of concurrency and enqueuing.
using namespace System.Management.Automation.Runspaces
try {
# define number of threads that can run at the same time
$threads = 10
# define IPs as Key and FileName as Value
$lookup = #{
'1.2.3.4' = 'FileNameForThisIP'
'192.168.1.15' = 'AnotherFileNameForTHatIP'
}
# path to directory executable
$plink = 'path\to\plinkdirectory\'
# path to log directory
$LogDir = 'path\to\logDirectory'
# serial port
$port = 123
$iss = [initialsessionstate]::CreateDefault2()
$rspool = [runspacefactory]::CreateRunspacePool(1, $threads, $iss, $Host)
$rspool.ApartmentState = 'STA'
$rspool.ThreadOptions = 'ReuseThread'
# session variables that will be intialized with the runspaces
$rspool.InitialSessionState.Variables.Add([SessionStateVariableEntry[]]#(
[SessionStateVariableEntry]::new('plink', $plink, '')
[SessionStateVariableEntry]::new('serialport', $port, '')
[SessionStateVariableEntry]::new('logDir', $LogDir, '')
))
$rspool.Open()
$rs = foreach($i in $lookup.GetEnumerator()) {
$ps = [powershell]::Create().AddScript({
param($pair)
filter timestamp {
$sw.WriteLine("$(Get-Date -Format MM/dd/yyyy_HH:mm:ss) $_")
}
try {
$path = Join-Path $LogDir -ChildPath ('{0}{1}.txt' -f $pair.Value, (Get-Date -f MM-dd-yyyy))
$sw = [System.IO.StreamWriter]::new($path)
$sw.AutoFlush = $true
& "$plink\plink.exe" -telnet $pair.Key -P $serialPort | TimeStamp
}
finally {
$sw.ForEach('Dispose')
}
}).AddParameter('pair', $i)
$ps.RunspacePool = $rspool
#{
Instance = $ps
AsyncResult = $ps.BeginInvoke()
}
}
foreach($r in $rs) {
try {
$r.Instance.EndInvoke($r.AsyncResult)
$r.Instance.Dispose()
}
catch {
Write-Error $_
}
}
}
finally {
$rspool.ForEach('Dispose')
}
I got the following variable $listofusers which returns the below objects in two columns:
SourceUser DestinationUser
---------- ---------------
username1#legacy.company.corp username1#modern.company.corp
username2#legacy.company.corp username2#modern.company.corp
username3#legacy.company.corp username3#modern.company.corp
username4#legacy.company.corp username4#modern.company.corp
I now need to process this list of users in a foreach loop. I have tried so far the following but without luck yet:
$Results = ForEach ($User in $listofusers) {
Write-Host "Processing SourceUser $($User.SourceUser)"
Write-Host "Processing DestinationUser $($User.DestinationUser)"
#Assign the content to variables
$SourceUsers = $User.SourceUser
$DestinationUsers = $User.DestinationUser
}
It only returns me the last line of the objects:
$SourceUsers
RETURN ONLY: username4#legacy.company.corp
$DestinationUsers
RETURN ONLY: username4#modern.company.corp
How can I add all the objects in the variable $listofusers for further processing?
UPDATE:
I am trying to achieve the following that's why I have broken the association in listofusers
$SourceUser = #()
$DestinationUser = #()
$Results = ForEach ($User in $listofusers)
{
Write-Host "Processing SourceUser $($User.SourceUser)"
Write-Host "Processing DestinationUser $($User.DestinationUser)"
#Assign the content to variables
$SourceUser += $User.SourceUser
$DestinationUser += $User.DestinationUser
#Cannot get that variables working yet
$sourceusername, $sourcedomain = $SourceUser -split ("#")
$DestinationUsername, $destinationDomain = $DestinationUser -split ("#")
$SourceAccount = Get-ADUser $sourceusername -server $sourcedomain -Properties objectSid
$TargetAccount = Get-ADUser $DestinationUsername -Server $destinationDomain
}
Is there any better way to achieve that and get those variables to that point?
NEW UPDATE:
The purpose of the script would be to achieve the following cmdlets for processing ad objects:
#get the objectSid of the source account
$objectSid = $SourceAccount.objectSid
#copy source account objectSid to target account msExchMasterAccountSid
$TargetAccount | Set-ADUser -Replace #{"msExchMasterAccountSid"=$objectSid}
#enable targetaccount
$TargetAccount | Enable-ADAccount
#disable the source account
$SourceAccount | Disable-ADAccount
#move the migrated user into prod OU
$TargetAccount | Move-ADObject -TargetPath "ou=test,dc=contoso,dc=com"
Thanks
here is a demo of the concept i was trying to get across. [grin] it keeps the association of the objects in your CSV in the original object for as long as possible. the code has NOT been tested since i have no AD access.
what it does ...
fakes reading in a CSV file
when you are ready to use real data, replace the entire "region" with a call to Import-CSV.
iterates thru the list
builds a splat of the parameters for the AD calls
see Get-Help about_Splatting for more info on that wonderfully useful idea.
calls Get-AdUser with each to the Source/Target user data sets
stores the above
uses the stored account info to ...
== replace the .objectSid of the Target account
== enable the Target account
== disable the Source account
== Move the Target account to the desired OU
the hard coded OU could be set with a variable to make this a tad more flexible. however, this seems to be a one-off operation - so there is likely no benefit.
if you want to add logging, do so in the same loop.
there is no error handling, either. that likely should be added with a try/catch around each AD call & logging of both success and failure.
the code ...
#region >>> fake reading in a CSV file
# in real life, use Import-CSV
$UserList = #'
SourceUser, DestUser
ABravo#Old.com, ABravo#NewDomain.com
BCharlie#Old.com, BCharlie#NewDomain.com
CDelta#Old.com, CDelta#NewDomain.com
DEcho#Old.com, DEcho#NewDomain.com
EFoxtrot#Old.com, EFoxtrot#NewDomain.com
'# | ConvertFrom-Csv
#endregion >>> fake reading in a CSV file
ForEach ($UL_Item in $UserList)
{
Write-Host 'Processing ...'
Write-Host (' SourceUser {0}' -f $UL_Item.SourceUser)
Write-Host (' DestinationUser {0}' -f $UL_Item.DestUser)
Write-Host '__ Source Account __'
$GADU_Params_1 = [ordered]#{
Identity = $UL_Item.SourceUser.Split('#')[0]
Server = $UL_Item.SourceUser.Split('#')[1]
Properties = 'objectSid'
}
$SourceAccount = Get-ADUser #GADU_Params_1
Write-Host '__ Target Account __'
$GADU_Params_2 = [ordered]#{
Identity = $UL_Item.DestUser.Split('#')[0]
Server = $UL_Item.DestUser.Split('#')[1]
}
$TargetAccount = Get-ADUser #GADU_Params_2
Write-Host 'Making changes ...'
# all these piped objects are slower than making _direct_ calls
# however, i don't have any way to test the code, so i can't use what likely is faster
# something like >>>
# Set-AdUser -Identity $TargetAccount -Replace #{
# 'msExchMasterAccountSid' = $objectSid
# }
# note that i also replaced the unneeded _double_ quotes with the safer _single_ quotes
$TargetAccount |
Set-AdUser -Replace #{
'msExchMasterAccountSid' = $SourceAccount.objectSid
}
$TargetAccount |
Enable-AdAccount
$SourceAccount |
Disable-AdAccount
$TargetAccount |
Move-AdObject -TargetPath 'ou=test,dc=contoso,dc=com'
Write-Host '=' * 30
Write-Host ''
}
no output shown since i can't actually run this AD stuff. [grin]
$SourceUsers and $DestinationUsers contain only the last ones becasue youa re replacing the value on each foreach iteration.
if you want it to separate the properties try this:
$SourceUsers = $User | select SourceUser -ExpandProperty SourceUser
$DestinationUsers = $User | select DestinationUser -ExpandProperty DestinationUser
That will create a collection of only those strings. you wont be able to access those values by property anymore, meaning that is a simple String[] after the -ExpandProperty.
$SourceUsers = #()
$DestinationUsers = #()
$Results = ForEach ($User in $listofusers) {
Write-Host "Processing SourceUser $($User.SourceUser)"
Write-Host "Processing DestinationUser $($User.DestinationUser)"
#Assign the content to variables
$SourceUsers += $User.SourceUser
$DestinationUsers += $User.DestinationUser
}
$SourceUsers = #() and $DestinationUsers = #() creates two empty
arrays which we will use in the loop
+= is an assignment operator which enables us to assign more than
one value to a variable. According to the documentation: Increases
the value of a variable by the specified value, or appends the
specified value to the existing value.
I have been struggling to come up with a working solution for days on this
What am I trying to achieve?
Foreach ($item in $webApps){
$WebAppConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/config -ResourceName $item -ResourceGroupName $resourceGroup -ApiVersion $APIVersion)
}
The issue is that "-resourceName" will not accept objects, but rather only a string
I am looking for a way to take the output of the following command, convert it to a string, so that it can satisfy –ResourceName, and loop through each item in the string
$webApps = (Get-AzureRmResourceGroup -Name $resourceGroup | Get-AzureRmWebApp).name
This returns a nice list of Azure WebApps that exist in a specified ResourceGroup, however they are in object form, which –ResourceName will not take
I have tried several ways to convert the output of $webApps to a string, add a comma to the end, then do a –split ',' but nothing seems to work for properly, where –ResourceName will accept it
Method 1:
[string]$webAppsArrays =#()
Foreach ($webApp in $webApps){
$webAp+',' -split ','
}
Method 2:
$
webApps | ForEach-Object {
$webApp = $_ + ","
Write-Host $webApp
}
Method 3:
$csvPath2 = 'C:\Users\Giann\Documents\_Git Repositorys\QueriedAppList2.csv'
$webApps = (Get-AzureRmResourceGroup -Name $resourceGroup | Get-AzureRmWebApp).name | out-file -FilePath $csvPath1 -Append
$csvFile2 = import-csv -Path $csvPath1 -Header Name
This ouputs a list in a CSV, however these are still objects, so I cannot pass each item into –ResourceName
I am going in circles trying to make the below a repeatable, looping script
The desired end result would be to use the below script, with an array of webApps, being queried from the provided resource group variable:
Any help would be greatly appreciated for how to use this script, but pull a dynamic list of WebApps from a specified Resource Group, keeping in mind the -ResourceName "String" restrictions in the $WebAppConfig variable
Here is the original script to create IP Restrictions for 1 Web App and 1 Resource Group, using properties from a CSV file:
#Create a Function to create IP Restrictions for 1 Web App and 1 Resource Group, using properties from the CSV file:
#Variables
$WebApp = ""
$resourceGroup =""
$subscription_Id = ''
#Login to Azure
Remove-AzureRmAccount -ErrorAction SilentlyContinue | Out-Null
Login-AzureRmAccount -EnvironmentName AzureUSGovernment -Subscription $subscription_Id
Function CreateIpRestriction {
Param (
[string] $name,
[string] $ipAddress,
[string] $subnetMask,
[string] $action,
[string] $priority
)
$APIVersion = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Web).ResourceTypes | Where-Object ResourceTypeName -eq sites).ApiVersions[0]
$WebAppConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/config -ResourceName $WebApp -ResourceGroupName $ResourceGroup -ApiVersion $APIVersion)
$ipRestriction = $WebAppConfig.Properties.ipSecurityRestrictions
$ipRestriction.name = $name
$ipRestriction.ipAddress = $ipAddress
$ipRestriction.subnetMask = $subnetMask
$ipRestriction.action = $action
$ipRestriction.priority = $priority
return $ipRestriction
}
#Set csv file path:
$csvPath5 = 'C:\Users\Giann\Documents\_Git Repositorys\ipRestrictions5.csv'
#import CSV Contents
$ipRestrictionArray = Import-Csv -Path $csvPath5
$ipRestrictions = #()
foreach($item in $ipRestrictionArray){
Write-Host "Adding ipRestriction properties for" $item.name
$newIpRestriction = CreateIpRestriction -name $item.name -ipAddress $item.ipAddress -subnetMask $item.subnetMask -action $item.action -priority $item.priority
$ipRestrictions += $newIpRestriction
}
#Set the new ipRestriction on the WebApp
Set-AzureRmResource -ResourceGroupName $resourceGroup -ResourceType Microsoft.Web/sites/config -ResourceName $WebApp/web -ApiVersion $APIVersion -PropertyObject $ipRestrictions
As continuation on the comments, I really need multiline, so here as an answer.
Note that I cannot test this myself
This page here shows that the Set-AzureRmResource -Properties parameter should be of type PSObject.
(instead of -Properties you may also use the alias -PropertyObject)
In your code, I don't think the function CreateIpRestriction returns a PSObject but tries to do too much.
Anyway, try like this:
Function CreateIpRestriction {
Param (
[string] $name,
[string] $ipAddress,
[string] $subnetMask,
[string] $action,
[string] $priority
)
# There are many ways to create a PSObject (or PSCustomObject if you like).
# Have a look at https://social.technet.microsoft.com/wiki/contents/articles/7804.powershell-creating-custom-objects.aspx for instance.
return New-Object -TypeName PSObject -Property #{
name = $name
ipAddress = $ipAddress
subnetMask = $subnetMask
action = $action
priority = $priority
}
}
#Set csv file path:
$csvPath5 = 'C:\Users\Giann\Documents\_Git Repositorys\ipRestrictions5.csv'
#import CSV Contents
$ipRestrictionArray = Import-Csv -Path $csvPath5
# create an new array of IP restrictions (PSObjects)
$newIpRestrictions = #()
foreach($item in $ipRestrictionArray){
Write-Host "Adding ipRestriction properties for" $item.name
$newIpRestrictions += (CreateIpRestriction -name $item.name -ipAddress $item.ipAddress -subnetMask $item.subnetMask -action $item.action -priority $item.priority )
}
# here we set the restrictions we collected in $newIpRestrictions in the $WebAppConfig.Properties.ipSecurityRestrictions array
$APIVersion = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Web).ResourceTypes | Where-Object ResourceTypeName -eq sites).ApiVersions[0]
$WebAppConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/config -ResourceName $WebApp -ResourceGroupName $ResourceGroup -ApiVersion $APIVersion)
$WebAppConfig.Properties.ipSecurityRestrictions = $newIpRestrictions
$WebAppConfig | Set-AzureRmResource -ApiVersion $APIVersion -Force | Out-Null
The code above will replace the ipSecurityRestrictions by a new set. You may want to consider first getting them and adding to the already existing list.
I found examples for Getting, Adding and Removing ipSecurityRestrictions here, but I can imagine there are more examples to be found.
Hope that helps.
I'm creating a script that goes through the Active directory and then searches for information based on another query when the information is entered and there is no information from the active directory the information from the last entered area enters into that SQL row even if there is no information for that site. Should I create an if else statement within one of the foreach loop to fix this problem?
while($rows.Read()){
$SITEINFO = $rows['ActiveDirectoryOU']
$ADARRAY= Get-ADGroupMember -Identity $SITEINFO | Get-ADUser -Properties ('Mail')
ForEach($OBJECT in $ADARRAY){
$NAME = $OBJECT.Name
$USER = $OBJECT.SamAccountName
$EMAIL = $OBJECT.Mail
$SITECODE = $SITEINFO
$INSERT = "INSERT INTO $TABLE VALUES ('$USER','$SITECODE','$EMAIL', '$NAME');"
$SQL = $SQLCON.CreateCommand()
$SQL.CommandText = $INSERT
$SQL.ExecuteNonQuery()
}
}
}
$SQLCONN.Close()
I am running the following Powershell Script against a list of SQL Instances to return Instance information, including users and roles. I want to use Powershell to do this as I'm collating the data and will import into another system which will do some analysis.
I created the following script which creates an XML file output for each instance found. The script works great (yes, it's probably clunky and an awful way to do this, but I'm learning so please feel free to give me a shove in the right direction), however for one of the servers with a few hundred SQL logins I get an overflow message appear on screen, the XML file is not closed correctly and as a result I can't import the results into my analysis system.
I would like either:
Ideas on what could be causing the overflow. For reference, the output XML that crashes out is 2.5MB in size, with approx 39,000 lines in the XML output before the overflow occurs
[OR]
Another way to get this output - CSV is an option but I don't know enough how to output this - can anyone provide tips?
Thank you in advance
#Input file is a plain text file with the name of each of the instances listed in it
$InputFile="C:\Tasks\SQL\Permissions\in\Instances.txt"
$OutputFolder="\\networkdrive\sharedfolder\"
Function GetDBUserInfo($Dbase)
{
if ($dbase.status -eq "Normal") # ensures the DB is online before checking
{$users = $Dbase.users | where {$_.login -eq $SQLLogin.name} # Ignore the account running this as it is assumed to be an admin account on all servers
foreach ($u in $users)
{
if ($u)
{
$XmlWriter.WriteStartElement("Login")
$XmlWriter.WriteElementString('DBName', $dbase.name)
$XmlWriter.WriteElementString('LoginName', $SQLLogin.name)
$XmlWriter.WriteStartElement('Login_Roles')
$DBRoles = $u.enumroles()
foreach ($role in $DBRoles)
{
$XmlWriter.WriteElementString('Role', $Dbase.name)
}
$XmlWriter.WriteEndElement()#Login_Roles
#Get any explicitly granted permissions
$XmlWriter.WriteStartElement('Login_Permissions')
$XmlWriter.WriteElementString('Instance', $svr.name)
$XmlWriter.WriteElementString('DBName', $dbase.name)
$XmlWriter.WriteElementString('LoginName', $SQLLogin.name)
foreach($perm in $Dbase.EnumObjectPermissions($u.Name))
{
$XmlWriter.WriteElementString('Permissions', $perm.permissionstate.tostring() + " " + $perm.permissiontype.tostring() + " on " + $perm.objectname.tostring() + " in " + $DBase.name.tostring())
}
$XmlWriter.WriteEndElement() #Login_Permissions
$XMLWriter.WriteEndElement() #Login
} # Next user in database
}
#else
#Skip to next database.
}
}
#Main portion of script start
[reflection.assembly]::LoadWithPartialName("Microsoft.SqlServer.Smo") | out-null #ensure we have SQL SMO available
foreach ($SQLsvr in get-content $InputFile) # read the instance source file to get instance names
{
$svr = new-object ("Microsoft.SqlServer.Management.Smo.Server") $SQLsvr
#Cycle through each instance and write the instance information to the file
#Output file is base folder for each of the text files (which will be named for the instance)
$OutputFile = $svr.name
$OutputFile = $OutputFolder+$OutputFile.Replace("\", "-")+".xml"
# get an XMLTextWriter to create the XML
$XmlWriter = New-Object System.XMl.XmlTextWriter($OutputFile,$Null)
# choose a pretty formatting:
$xmlWriter.Formatting = 'Indented'
$xmlWriter.Indentation = "4"
# write the header
$xmlWriter.WriteStartDocument()
# set XSL statements
$XLSPropText="type='text/xsl' href='style.xsl'"
$xmlWriter.WriteProcessingInstruction("xml-stylesheet", $XSLPropText)
# create root element "instances" and add some attributes to it
$xmlWriter.WriteStartElement("Root")
$XmlWriter.WriteStartElement("Instance")
$XmlWriter.WriteElementString("SQLInstance", $svr.name)
$XmlWriter.WriteElementString("SQLVersion", $svr.VersionString)
$XmlWriter.WriteElementString("Edition", $svr.Edition)
$XmlWriter.WriteElementString("LoginMode", $svr.loginmode)
$XmlWriter.WriteEndElement #instance
$SQLLogins = $svr.logins
foreach ($SQLLogin in $SQLLogins)
{
#Iterate through each login, writing the details into the login details
#$XmlWriter.WriteComment("Login Details")
$xmlWriter.WriteStartElement("Logins")
$XmlWriter.WriteElementString("InstanceName", $svr.Name)
$XmlWriter.WriteElementString("LoginName", $SQLLogin.Name)
$XmlWriter.WriteElementString("LoginType", $SQLLogin.LoginType)
$XmlWriter.WriteElementString("Created", $SQLLogin.CreateDate)
$XmlWriter.WriteElementString("DefaultDatabase", $SQLLogin.DefaultDatabase)
$XmlWriter.WriteElementString("Disabled", $SQLLogin.IsDisabled)
$SQLRoles = $SQLLogin.ListMembers()
If ($SQLRoles)
{ $XmlWriter.WriteElementString("ServerRole", $SQLRoles) }
else
{ $XmlWriter.WriteElementString("ServerRole", "Public") }
If ( $SQLLogin.LoginType -eq "WindowsGroup" )
{ #get individuals in any Windows domain groups
$XmlWriter.WriteStartElement("WindowsLogins")
$XmlWriter.WriteElementString("InstanceName", $svr.name)
$XmlWriter.WriteElementString("Login", $SQLLogin.name)
try {
$ADGRoupMembers = get-adgroupmember $SQLLogin.name.Split("\")[1] -Recursive
foreach($member in $ADGRoupMembers)
{ $XmlWriter.WriteElementString("Account", $member.name.tostring() + "(" + $member.SamAccountName.tostring() +")") }
}
catch
{
#Sometimes there are 'ghost' groups left behind that are no longer in the domain, this highlights those still in SQL
$XmlWriter.WriteElementString("Account", "Unable to locate group " + $SQLLogin.name.Split("\")[1] + " in the AD Domain")
}
$XmlWriter.WriteEndElement()
}
#Check the permissions in the DBs the Login is linked to.
If ($SQLLogin.EnumDatabaseMappings())
{
$XmlWriter.WriteStartElement('Permissions')
$XmlWriter.WriteElementString('InstanceName', $svr.name)
$xmlwriter.WriteElementString('Login', $SQLLogin.name)
foreach ( $DB in $svr.Databases)
{
try {
GetDBUserInfo($DB)
}
catch
{
echo $_.Exception|format-list -force
}
} # Next Database
$XmlWriter.WriteEndElement()
}
Else
{
$XmlWriter.WriteStartElement('Permissions')
$XmlWriter.WriteElementString('InstanceName', $svr.name)
$xmlwriter.WriteElementString('Login', $SQLLogin.name)
$XmlWriter.WriteElementString('Permissions', 'No Permissions')
$XmlWriter.WriteEndElement()
}
$xmlWriter.WriteEndElement() #End Logins element
}
}
# close the "machines" node:
$xmlWriter.WriteEndElement() #root node
# finalize the document:
$xmlWriter.WriteEndDocument()
$xmlWriter.Flush()
$xmlWriter.Close()
When running, error message is:
OverloadDefinitions
--------------------
void WriteEndElement()
void WriteEndElement()
No other error or message is given. The file does get written but is incomplete.