Script to add AD Group to Local Admin Group on multiple servers - powershell

##Roji P Rajan
$ErrorActionPreference = "silentlycontinue"
$Domain = Read-Host "`nEnter Domain name to connect"
$UserName = Read-Host "`nEnter AD Group name to add "
$DomName = $domain + "/" + $username
write-host "`n"
foreach($server in (gc .\servers.txt)){
$i= 0
$Boo= 0
if (Test-Connection $server -Count 1 -Quiet) {
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$members = #($group.psbase.Invoke("Members"))
$Check =($members | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -contains "$UserName"
If ($Check -eq $True) {
write-host "$server`t- Already Member" -foregroundcolor "yellow" }
else {
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$Group.Add("WinNT://" + $domain + "/" + $username)
$mem = ($Group.psbase.invoke(”Members”) | %{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) `
-replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/', 'DOMAIN\') -replace ('WinNT://', '')
$total = $mem.count
Foreach ($member in $mem) {
if ("$member" -eq "$Domain/$UserName"){
write-host "$server`t- Successfully Updated" -foregroundcolor "green"
$Boo = 1 }
$i=$i+1
If ($total -eq $i -And $Boo -eq 0) {
write-host "$server`t- Failed - User not exist or the server is not ready" -foregroundcolor "magenta" }
}
}
}
else {
write-host "$server `t- Failed to connect the Host Name" -foregroundcolor "Red" }
}
write-host "`n"
By using the above set of powershell code i am able to add a specific domain group to local administrative group in multiple servers. But if i run the script from any of the one server which is already in servers.txt, that specific server fails to update.. Can anybody guide me what i missed..
Thanks in advance..

Here's a link to a simple script that works:
https://deepakkhare.azurewebsites.net/powershell-add-remove-multiple-security-groups-on-multiple-windows-servers/
# This script will add multiple groups on multiple servers
# Make sure you have one server in each row in the servers text file
# you must have administrator access on the server
$ServersList = “D:\ServersList.txt”
$ServerNames = get-content $ServersList
$UserGroupFilePath = “D:\SecurityGroup.txt”
$UserGroupList = get-content $UserGroupFilePath
$DomainName =”Enter your domain name here”
foreach ($name in $ServerNames)
{
$localAdminGroup = [ADSI](“WinNT://$name/Administrators”)
# Add all the groups in text file to the current server
foreach ($UserGroupName in $UserGroupList)
{
$AdminsG = [ADSI] “WinNT://$DomainName/$UserGroupName”
$localAdminGroup.Add($AdminsG.PSBase.Path)
Write-Host “Adding” $AdminsG.PSBase.Path “to” $name
} # End of User Group Loop
} # End of Server List Loop
Remove multiple security groups on multiple servers
# This script will delete multiple security groups on multiple servers
# Make sure you have one server in each row in the servers text file
# you must have administrator access on the server
$ServersList = “D:\ServersList.txt”
$ServerNames = get-content $ServersList
$UserGroupFilePath = “D:\SecurityGroup.txt”
$UserGroupList = get-content $UserGroupFilePath
$DomainName =”Enter your domain name here”
foreach ($name in $ServerNames)
{
$localAdminGroup = [ADSI](“WinNT://$name/Administrators”)
# Add all the groups in text file to the current server
foreach ($UserGroupName in $UserGroupList)
{
$AdminsG = [ADSI] “WinNT://$DomainName/$UserGroupName”
$localAdminGroup.remove($AdminsG.PSBase.Path)
Write-Host “remove” $AdminsG.PSBase.Path “to” $name
} # End of User Group Loop
} # End of Server List Loop

Create a security group for your server administrators to the local group on all of the servers. E.g. "ServerAdmins#domain".
Use a GPO to Enforce and restrict local administrator group membership.
When it is determined that a certain role (RBA) needs ServerAdmin on all servers (your Windows Server Admins for example), add that group to the ServerAdmins group. E.g. Windows Admin Team. Put your administrators in the admins group.
When it is a smaller subset of servers that a RBA group needs access to, create a ExchangeServerAdmins group, add that to the GPO for those servers. The Exchange admins would be in a Exchange Admin Team group. Exchange Admin Team goes in the ExchangeServerAdmins group.
This way you control what groups have access to the servers by GPO, as chosen by Role Based Access. Control over the membership in the groups is controlled by storign those groups where the appropriate people can edit it. You can also delegate this by making team leads owners of their groups.
This also allows temporary rights. If you bring in a consultant who needs Exchange and Lync access, you add him to those teams. When he leaves you take him out. ONE edit coming, one going, both done at the group membership level, easy.
This also dramatically decreases maintenance when you start talking about Enterprise level administration. If you have 10-20, or even 200 servers, you can script out these changes, but what if you have 1000 or more servers?

I did a quick test by updating the local admin group of the server I was using. What is the error message? Can you remove the $ErrorActionPreference = "silentlycontinue" to see if an error is generated?
Here is the sample code:
#Set variables
$Domain = "Contoso"
$UserName = "JohnSmith"
$server = $env:COMPUTERNAME
#Get local admin group
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$CurrentMembers = $Group.PSbase.Invoke("Members") | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
#Add user to local admin group
$Group.Add("WinNT://" + $domain + "/" + $username)
#verify add
$VerifyComputer = [ADSI](”WinNT://” + $server + “,computer”)
$VerifyGroup = $VerifyComputer.psbase.children.find(”Administrators”)
$VerifyMembers= $VerifyGroup.PSbase.Invoke("Members") | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}

Related

How to speed up the process in PowerShell

I've a simple script that generate orphan OneDrive report. It's working but it's very slow so I'm just wondering how could I modify my script to speed up the process. Any help or suggestion would be really appreciated.
Basically this is what I'm doing:
Get the owner email from "Owner" column and check it using AzureAD to see if I get any error
If I get an error then check it in on-prem ADGroup
If that owner is existed in the on-prem ADGroup then it's an orphan
Export only that user to a new csv file
$ImportData = "E:\scripts\AllOneDriveUser.csv"
$Report = "E:\scripts\OrphanOneDrive.csv"
$CSVImport = Import-CSV $ImportData
ForEach ($CSVLine in $CSVImport) {
$CSVOwner = $CSVLine.Owner
try{
Get-AzureADUser -ObjectId $CSVOwner
}catch{
$StatusMessage = $_.Exception.Message
if($Null -eq $StatusMessage){
Write-Host "User Found, Ignore from orphan list."
}else{
#Owner not found in AzureAD
$group = 'TargetGroup'
$filter = '(memberof={0})' -f (Get-ADGroup $group).DistinguishedName
$filterName = Get-ADUser -LDAPFilter $filter
$ModifiedOwner = $CSVOwner -split"#"[0]
if( $ModifiedOwner[0] -in $filterName.Name ){
Write-host "Adding it into orphaned list"
$CSVLine | Export-Csv $Report -Append -notypeinformation -force
}else{
Write-Host "Not orphaned"
}
}
}
}
I have over 8000 record in my import csv file and over 5000 member in my on-prem AD group so it taking very long.
You can greatly improve your script by using a HashSet<T> in this case, but also the main issue of your code is that you're querying the same group over and over, it should be outside the loop!
There is also the use of Export-Csv -Append, appending to a file per loop iteration is very slow, better to streamline the process with the pipelines so Export-Csv receives the objects and exports only once instead of opening and closing the FileStream everytime.
Hope the inline comments explain the logic you can follow to improve it.
$ImportData = "E:\scripts\AllOneDriveUser.csv"
$Report = "E:\scripts\OrphanOneDrive.csv"
# we only need to query this once! outside the try \ catch
# a HashSet<T> enables for faster lookups,
# much faster than `-in` or `-contains`
$filter = '(memberof={0})' -f (Get-ADGroup 'GroupName').DistinguishedName
$members = [Collections.Generic.HashSet[string]]::new(
[string[]] (Get-ADUser -LDAPFilter $filter).UserPrincipalName,
[System.StringComparer]::OrdinalIgnoreCase
)
Import-CSV $ImportData | ForEach-Object {
# hitting multiple times a `catch` block is expensive,
# better use `-Filter` here and an `if` condition
$CSVOwner = $_.Owner
if(Get-AzureADUser -Filter "userprincipalname eq '$CSVOwner'") {
# we know this user exists in Azure, so go next user
return
}
# here is for user not found in Azure
# no need to split the UserPrincipalName, HashSet already has
# a unique list of UserPrincipalNames
if($hash.Contains($CSVOwner)) {
# here is if the UPN exists as member of AD Group
# so output this line
$_
}
} | Export-Csv $Report -NoTypeInformation

Struggling with If Statements

I have problems of understanding values of variables in PowerShell and I check them with if statements.
$LDAPDirectoryService = '10.10.XXX.XXX:389'
$DomainDN = 'o=Enterprise'
#$LDAPFilter = '(&(objectCategory=Person)(memberOf=cn=alc-01-Planung-rw,ou=KT,o=enterprise))'
$LDAPFilter = '(&(cn=alc-01-Planung-rw))'
$null = [System.Reflection.Assembly]::LoadWithPartialName('System.Net')
$LDAPServer = New-Object System.DirectoryServices.Protocols.LdapConnection $LDAPDirectoryService
$LDAPServer.AuthType = [System.DirectoryServices.Protocols.AuthType]::Anonymous
$Scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree
$AttributeList = #('*')
$SearchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $DomainDN,$LDAPFilter,$Scope,$AttributeList
$groups = $LDAPServer.SendRequest($SearchRequest)
$groups
if ($groups -eq $null) {"No Group found"}
if ($groups -eq " ") {"No Group found"}
foreach ($group in $groups.Entries) {
$users = $group.attributes['member'].GetValues('string')
foreach ($user in $users) {
Write-Host $user
}
}
I want to check if the group exists and then if users are existing in this group. I tried many statements but none of them seem to work.
It's not null or blank, even when nothing is written down in the console.
This is what I got when I use group which doesn't exist:
Can anybody show me a solution?
What version of PowerShell are you running? Why are you not using the built-in AD group cmdlets for this or are you not using ADDS but some other LDAP service?
Or you may be on OSX/Linux and are using PSCore, which the ADDS/RSAT cmdlets are not there, well, not yet?
For your goals of …
I want to check if the group exists and then if users are existing in
this group.
… On Windows, with PowerShell 3x or higher, it's really only this...
# Get all AD groups and all members of each group
Clear-Host
(Get-ADGroup -Filter '*').Name |
%{
"`n*** The members of $PSItem are as follows: ***`n"
If((Get-ADGroupMember -Identity $PSItem).Count -ge 1)
{
(Get-ADGroupMember -Identity $PSItem).SamAccountName
}
Else
{
Write-Warning -Message "$PSItem does not exist or has no members."
}
}
# Filtered
Clear-Host
((Get-ADGroup -Filter '*').Name -match 'Domain Admins|Domain Users' ) |
%{
"`n*** The members of $PSItem are as follows: ***`n"
If((Get-ADGroupMember -Identity $PSItem).Count -ge 1)
{
(Get-ADGroupMember -Identity $PSItem).SamAccountName
}
Else
{
Write-Warning -Message "$PSItem does not exist or has no members."
}
}
Using your LDAP approach though... How about this...
'Administrators','Distributed COM Users' |
ForEach {
# Define LDAP search root, the Global catalog of the domain
$sLDAPSearchRoot = "LDAP://$((Get-ADDomainController).IPv4Address):389"
# The Groupname to looking for
($sGroupName = "$_")
# The LDAP query - query string
$sSearchStr = "(&(objectCategory=group)(name="+$sGroupName+"))"
# Get the search object
$oSearch = New-Object directoryservices.DirectorySearcher($oADRoot,$sSearchStr)
# Looking for the group
$oFindResult = $oSearch.FindAll()
# On success, get a DirectoryEntry object for the group
$oGroup = New-Object System.DirectoryServices.DirectoryEntry($oFindResult.Path)
# And list all members
If (($oGroup.Member).Count -ge 1)
{
$oGroup.Member |
%{($oMembers = New-Object System.DirectoryServices.DirectoryEntry($sLDAPSearchRoot+"/"+$_))}
}
Else
{ Write-Warning -Message "$($oGroup.Member) does not exist or has no members"}
}

WMI pulls current user even if never logged in

I am new to powershell, and I am attempting to create a CSV from an excel file for comparison sake to see who is currently logged into a computer. However, I'm running into an odd issue where sometimes the script will pull the same user multiple times even if they've never logged into a computer. Here is my full code. I know there is a lot of optimization that can be done (and parts that need to be removed, those should be noted). I assume I've misused Get-WMIObject or something similar, can anyone see why it would pull that information like it is?
$csvRunFile = "test.csv"
$output = "Results_$(Get-Date -format yyyy.MM.dd).csv"
#Import the created csv.
$csv = import-csv $CsvRunFile
$results = foreach($csv_line in $csv) {
$ctag = $csv_line.ctag
$test_ping = test-connection $ctag -Count 1 -Quiet
#If the computer is pingable (IE: Online)
switch ($test_ping) {
$true {
#Pull the actual logged in user.
$Username = (Get-WmiObject -ComputerName $ctag -Class Win32_ComputerSystem).Username.Split("\\")[1]
#If the last modified folder is 'public' put an error, otherwise pull the username's information from AD.
#This was from when it pulled from the \User folder rather than the last log in, this is probably removeable.
if ($Username -eq "Public") {
$ADName = "No User"
} else {
$ADName = Get-ADUser -Identity $Username
$ADName = $ADName.Name
} #end If
}#end Switch:True
#Show there was an error when pinging the computer.
$false {$ADName = "ERROR"}
}#end Switch
#write the results the new output CSV.
$result = [PSCustomObject]#{
CTAG = $ctag
Username = $ADName
}#end PSCustom Object
$result
} #end foreach
#Turn the .txt into a CSV so it can be manually compared to the list in the original excel file.
$results | Export-Csv -path $output
There might be a leftover-value in $UserName for some reason or $ADName is still it's old value because you tried to run Get-ADUser -Identity $null when there was no user logged on (WMI returns $null when there's no user logged in).
I also changed your ping-test from a switch to an if-test just to cleanup the code. I've never seen Public being returned, but I left it as it doesn't really hurt either.
Try:
#If the computer is pingable (IE: Online)
if($test_ping) {
#Clear username var just to be safe
$Username = $null
#Pull the actual logged in user.
$Username = (Get-WmiObject -ComputerName $ctag -Class Win32_ComputerSystem).Username | ? { $_ } | % { $_.Split("\\")[1] }
#If the last modified folder is 'public' put an error, otherwise pull the username's information from AD.
#This was from when it pulled from the \User folder rather than the last log in, this is probably removeable.
if (($Username -eq "Public") -or ($Username -eq $null)) {
$ADName = "No User"
} else {
$ADName = Get-ADUser -Identity $Username
$ADName = $ADName.Name
} #end If username public
} else { $ADName = "ERROR"}

Select option from Array

I am working on a side project and to make it easier for managment since almost all of out server names are 15 charactors long I started to look for an RDP managment option but none that I liked; so I started to write one and I am down to only one issue, what do I do to manage if the user types not enough for a search so two servers will match the Query. I think I will have to put it in an array and then let them select the server they meant. Here is what I have so far
function Connect-RDP
{
param (
[Parameter(Mandatory = $true)]
$ComputerName,
[System.Management.Automation.Credential()]
$Credential
)
# take each computername and process it individually
$ComputerName | ForEach-Object{
Try
{
$Computer = $_
$ConnectionDNS = Get-ADComputer -server "DomainController:1234" -ldapfilter "(name=$computer)" -ErrorAction Stop | Select-Object -ExpandProperty DNSHostName
$ConnectionSearchDNS = Get-ADComputer -server "DomainController:1234" -ldapfilter "(name=*$computer*)" | Select -Exp DNSHostName
Write-host $ConnectionDNS
Write-host $ConnectionSearchDNS
if ($ConnectionDNS){
#mstsc.exe /v ($ConnectionDNS) /f
}Else{
#mstsc.exe /v ($ConnectionSearchDNS) /f
}
}
catch
{
Write-Host "Could not locate computer '$Computer' in AD." -ForegroundColor Red
}
}
}
Basically I am looking for a way to manage if a user types server1
that it will ask does he want to connect to Server10 or Server11 since both of them match the filter.
Another option for presenting choices to the user is Out-GridView, with the -OutPutMode switch.
Borrowing from Matt's example:
$selection = Get-ChildItem C:\temp -Directory
If($selection.Count -gt 1){
$IDX = 0
$(foreach ($item in $selection){
$item | select #{l='IDX';e={$IDX}},Name
$IDX++}) |
Out-GridView -Title 'Select one or more folders to use' -OutputMode Multiple |
foreach { $selection[$_.IDX] }
}
else {$Selection}
This example allows for selection of multiple folders, but can you can limit them to a single folder by simply switching -OutPutMode to Single
I'm sure what mjolinor has it great. I just wanted to show another approach using PromptForChoice. In the following example we take the results from Get-ChildItem and if there is more than one we build a collection of choices. The user would select one and then that object would be passed to the next step.
$selection = Get-ChildItem C:\temp -Directory
If($selection.Count -gt 1){
$title = "Folder Selection"
$message = "Which folder would you like to use?"
# Build the choices menu
$choices = #()
For($index = 0; $index -lt $selection.Count; $index++){
$choices += New-Object System.Management.Automation.Host.ChoiceDescription ($selection[$index]).Name, ($selection[$index]).FullName
}
$options = [System.Management.Automation.Host.ChoiceDescription[]]$choices
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
$selection = $selection[$result]
}
$selection
-Directory requires PowerShell v3 but you are using 4 so you would be good.
In ISE it would look like this:
In standard console you would see something like this
As of now you would have to type the whole folder name to select the choice in the prompt. It is hard to get a unique value across multiple choices for the shortcut also called the accelerator key. Think of it as a way to be sure they make the correct choice!

Find read-host value in file list

I have now moved on to automating IDRAC. My script below will look at a list of servernames and IDRAC IP's. I would like to have a user enter a single Servername "S0000A01PX' or whatever and have it search in a master list for the server IDRAC IP. Right now, the script as it is opens every server's virtual console in the list. I just need it to select only the user entry. How do I have this search the file, find the IP next to the user entered servername, and open only the IDRAC for that servername?
example of what's in the CSV
computername iPiDRAC
S0000A01PX 10.122.2.11
Script
$machinename = ""
$file = Import-Csv 'c:\temp\powershell\Branch Server IDRAC.csv'
$filelength = $file.length
$machine = Read-Host 'What is your Server?'
foreach ($line in $file){
$DRACip = $line.iPiDRAC
$DRACpw=cscript c:\PassGen1.vbs $machinename
$DRACpw=$DRACpw[3]
$DRACip
$machinename
$DRACPW
$openIDRAC="http://"+$DRACip+"/console"
$openIDRAC
start $openIDRAC
write-host "----------"
}
If you are just looking to have a general match and return launch all DRACs that match a simple change can get you there. Where-Object{$_.computername -match $machine}
$machinename = ""
$file = Import-Csv 'c:\temp\powershell\Branch Server IDRAC.csv'
$machinename = Read-Host 'What is your Server?'
$file | Where-Object{$_.computername -match $machinename} |ForEach-Object{
$DRACip = $_.iPiDRAC
$DRACpw=cscript c:\PassGen1.vbs $machinename
$DRACpw=$DRACpw[3]
$DRACip
$machinename
$DRACPW
$openIDRAC="http://"+$DRACip+"/console"
$openIDRAC
start $openIDRAC
write-host "----------"
}
You are mixing output and write-host which could get you into trouble so I would remove some of that extra fluff which I will assume is just there for testing. I don't see where you are using $DRACpw or $machinename unless that is somehow what you need to see in order to sign into the DRAC
I think you want a bit of Where-Object.
For this CSV file:
computername,iPiDRAC
S0000A01PX,10.122.2.11
Import the file as you have been:
$file = Import-Csv 'c:\temp\powershell\Branch Server IDRAC.csv'
Ask your question:
$machine = Read-Host 'What is your Server?'
Look for the server name in your $file array, which will output an object representing that line in the CSV, then get its iPiDRAC property:
($file | Where-Object {$_.computername -eq $machine}).iPiDRAC
10.122.2.11