Powershell GPO Login Script checking AD resource group membership - powershell

The system I have to work with uses AD resource group membership to manage most of the permissions for users and computers. I have been asked to improve the current logon script as it currently contains some VB ADSISEARCHER calls. I started trying to do this purely in powershell but have hit a number of hurdles.
Target machines do not have the Active Directory Module installed
The users logging into the system have a restricted user accounts
The resource groups are nested so the script needs to handle this
I have tried a couple of approaches firstly the pure Powershell Cmdlet method of Get-ADGroup or Get-ADPricipalGroupMembership but these Cmdlet's require the Active Directory Module. Then I tried the .net approach with System.DirectoryServices.DirectoryEntry although this is a step away from a pure PowerShell solution at least it isn't as Legacy as the VB route. However when I try to build the object it also appears to be missing the name space.
First Attempt:
function Get-UserResourceMembership
{
[CmdletBinding()]
Param
(
# Username or Groupname to Discover Group Membership
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$User
)
Begin
{
$Groups = #(Get-ADPrincipalGroupMembership $User)
}
Process
{
foreach($ADGroup in $Groups)
{
if($ADGroup.ObjectClass -eq "Group")
{
Get-UserResourceMembership $ADGroup
}
$GrpMembership = #($ADGroup)
}
}
End
{
return ,$GrpMembership
}
}
Second Attempt:
# $rootGroup is passed in from earlier in the script
$groupname = $rootGroup.'Group Name'
$filter = ("(&(objectCategory=Group)(name=$($groupname)))")
$searcher.Filter = $filter
$searcher.SearchScope = "Subtree"
$searchResults = $searcher.FindAll().GetDirectoryEntry().memberOf |
% { (New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$_)) } |
Sort-Object Children | select #{name="Group Name";expression={$_.Name}}
foreach($resource in $searchResults)
{
if($resource.'Group Name' -match "<Groupname>")
{
$printResource += $resource
}
}
Does anyone in the community have any suggestions how to pull group membership [nested] from Active Directory from a standard users login script??? Any idea's much appreciated....
PS I can't change the way the system is designed (above my pay grade).

As for ...
• Target machines do not have the Active Directory Module installed
• The users logging into the system have a restricted user accounts
• The resource groups are nested so the script needs to handle this
Does not matter, they do not need to be installed on a client to use
them. You can use PSRemoting to proxy those using 'Implicit
Remoting'. The cmdlets are only available in the remote session.
Does not matter, as every user has read access, by default to ADDS
in Windows.
You can get to those using the cmdlets you are using and
there are even pre-built scripts in the Microsoft PowershellGallery.com
for this as well.
As for …
I have tried a couple of approaches firstly the pure Powershell Cmdlet
method of Get-ADGroup or Get-ADPricipalGroupMembership but these
Cmdlet's require the Active Directory Module.
As noted above, this can be addressed as described below:
PowerShell Implicit Remoting: Never Install a Module Again
Remote Session
# create a session then import a module via the session, for example:
$adsess = New-PSSession -ComputerName savdaldc01
Import-Module -Name ActiveDirectory -PSSession $adsess
Get-Module
Get-ADUser -Filter *
Remove-Module ActiveDirectory
# It's also possible to prefix modules loaded from remote servers to differentiate from local modules, e.g.
Import-Module -Name ActiveDirectory -PSSession $adsess -Prefix OnDC
Get-OnDCADUser -Filter * #I don't have regular Get-ADUser anymore
Remove-Module ActiveDirectory
Remove-PSSession $adsess
As for ...
Does anyone in the community have any suggestions how to pull group
membership [nested]
Get nested group membership - function
This function will recursively enumerate members of a given group
along with nesting level and parent group information. If there is a
circular membership, it will be displayed in Comment column.It accepts
input from pipeline and works well with get-adgroup. Download:
Get-ADNestedGroupMembers.ps1
As well as just doing this...
We can get group members by using the Active Directory powershell
cmlet Get-ADGroupMember. The Get-ADGroupMember cmdlet provides the
option to get all the nested group members by passing the parameter
-Recursive. This powershell script also handles circular membership (infinite loop) problem.
Function Get-ADNestedGroupMembers
{
[cmdletbinding()]
param
(
[String] $Group
)
Import-Module ActiveDirectory
($Members = Get-ADGroupMember -Identity $Group -Recursive)
}
Get-ADNestedGroupMembers "Domain Admins" | Select Name,DistinguishedName
or this way.
function Get-NestedGroupMember
{
param
(
[Parameter(Mandatory, ValueFromPipeline)]
[string]$Identity
)
process
{
Import-Module ActiveDirectory
$user = Get-ADUser -Identity $Identity
$userdn = $user.DistinguishedName
$strFilter = "(member:1.2.840.113556.1.4.1941:=$userdn)"
Get-ADGroup -LDAPFilter $strFilter -ResultPageSize 1000
}
}

All of the methods below list all groups including nested groups.
The example below would execute a gpresult command in the user's context. The gpresult outputs to an XML file within the user's local profile, which they should have full access to already. Then the XML file is read and traversed through each node until you reach the node containing the groups. The group list contains local and domain groups and is outputted directly to the console. This can easily be stored in a variable or output to a file. If you only want domain groups, that could easily be filtered from here with a Regex. It requires that the client machines are running at least Windows Vista SP1 or later.
gpresult /USER "$env:userdomain\$env:username" /X "$env:userprofile\rsop.xml"
$xml = [xml](Get-Content "$env:userprofile\rsop.xml")
$xml.Rsop.UserResults.SecurityGroup.Name."#text" # Displays the groups
Remove-Item "$env:userprofile\rsop.xml" # Removes the XML file
You could also use a potentially use Regex matching to find the group list:
$out = gpresult /R /USER $env:username
$GroupsUnfiltered = (($out | out-string) -split "-{10,}")[-1]
$Groups = ($GroupsUnfiltered.trim() -replace "(?m)^\s+","") -split "(?m)\r?\n"
$Groups
The following can also work if your group list always begins with a predictable group like Domain Users in this example:
$out = gpresult /R /USER $env:username
$GroupList = $out.where({$_ -match "domain users"},'SkipUntil').trim()
$GroupList
This code assumes that the users and machines are joined to the same domain or are at least joined to trusted domains. The second code snippet assumes every user is in the Domain Users group and the machines are natively PowerShell v4 or higher.

Related

Powershell improperly reading data in a CSV

I currently am writing a Powershell script that remotely removes users from a local admin group on a list of servers. The CSV headers are Computer and Name. For each entry of user (name), matches the server (computer).
Ex.
Computer,Name
Server1,User1
Server1,User2
Server2,User1
Script:
$List = Import-CSV C:\temp\LocalAdmin.CSV
$user = $List.Name
$objGroup = $List.Computer
write-host "Removing user" $user "from server" $objGroup "local admin group:" -ForegroundColor Green
Invoke-Command -ComputerName $objGroup -ScriptBlock {Remove-LocalGroupMember -Group "Administrators" -Member $using:user }
write-host "Completed."
When the script runs, it runs through perfectly fine the first time through, but then it runs through the script line by line for how many ever lines there are causing it to attempt to remove the users multiple times. Can someone help me fix this logic? It is almost like the CSV is being read as an array vs a list. I appreciate the help!
I would say:
$List = Import-CSV C:\temp\LocalAdmin.CSV
ForEach ($Item in $list)
{
$user = $Item.Name
$objGroup = $Item.Computer
write-host "Removing user $user from server $objGroup local admin group:" -ForegroundColor Green
Invoke-Command -ComputerName $objGroup -ScriptBlock {Remove-LocalGroupMember -Group "Administrators" -Member $user }
}
write-host "Completed."
Not tested, but this should be your starting point.
As for what you tried:
Given that $List contains an array of objects, with each element containing an object representing a row of the CSV file, $List.Name and $List.Computer similarly return arrays of property (column) values, courtesy of PowerShell's member-access enumeration
Therefore, $using:user refers to the array of all usernames, across all servers.
While the -Member parameter of Remove-LocalGroupMember does accept arrays, there are two problems with your approach:
At least hypothetically you'll run the risk of deleting users you shouldn't from certain servers, or you'll run into users that don't exist on a given server (though you could ignore that with -ErrorAction Ignore).
Since a given server name can appear multiple times in the CSV, the targeted user(s) will have already been deleted, starting with the second call to that server - this is the problem you saw.
TheStingPilot's helpful answer
provides an effective solution: loop over the objects representing the CSV rows one by one, and call Invoke-Command for each target server, with only the username at hand.
The downside of this approach - which may or may not matter, depending on how many computers you target - is that forgo the benefits of parallel execution that you get when you pass multiple computer names to Invoke-Command's -Computer parameter (by default, up to 32 computers at a time are targeted in parallel; you can modify that number with -ThrottleLimit).
To avoid multiple calls to a given server while preserving the benefits of parallel execution:
Build a hashtable from the CSV input that maps server names to user names.
Pass that hashtable to a single Invoke-Command call, as you tried, and let each remote computer look up the relevant usernames in it and act on them.
# Read the CSV file and create a hashtable (map) that
# maps server (computer) names to usernames to remove.
$serverUserMap = [ordered] #{}
Import-CSV C:\temp\LocalAdmin.CSV |
ForEach-Object {
[array] $serverUserMap[$_.Computer] += $_.Name
}
# * $serverUserMap.Keys now contains all unique server names,
# which can be passed to -ComputerName
# * Inside the script block, accessing the hashtable with the local
# computer name as the key returns only the relevant user(s).
Invoke-Command -ComputerName $serverUserMap.Keys -ScriptBlock {
Remove-LocalGroupMember -Group "Administrators" -Member ($using:serverUserMap)[$env:COMPUTERNAME]
}

How to run this script against another domain

I have the script below to give me the distinguished name for groups in a spreadsheet I have. The issue is, the groups are located in another domain. How do I point my script to that domain? Issue is I know I have to be logged in to that domain to run it but I cant.
$Groups = Get-Content -Path C:\Scripts\DistinguishedName.csv
ForEach ($Group in $Groups) {
Get-ADGroup -Identity $Group | Select-Object distinguishedName
}
The cmdlets in the Active Directory module support passing in the value of the domain controller you are wanting to query. By default when you call Get-ADGroup (or any of the other) it will validate what domain it should query by checking the domain of your current machine.
The other option is to provide the -Server (doc) with the value of the Active Directory Domain Services you want to execute your query against.
You can also provide the -Credential parameter with a PSCredential object that contains your login for that other domain. This is required if the current login of your PowerShell session is not authorized to authenticate against that other domain.
So your example script would look something like this:
$AdDomain = "whatever.company.local"
$adCred = Get-Credential
$Groups = Get-Content -Path C:\Scripts\DistinguishedName.csv
ForEach ($Group in $Groups) {
Get-ADGroup -Identity $Group -Server $AdDomain -Credential $adCred | Select-Object distinguishedName
}

Trying to dynamically connect in Powershell to nearest Exchange Server

New-PSSession wants a -ConnectionURI of an explict Exchange server. I don't want to hardcode a name in the script (we have 32 servers), and furthermore I want it to select an exchange that is in the same datacenter.
I want a solution similar to Get-ADDomainController -Discover -GetClosestSite But it seems I'm hoping for too much.
I suppose I can pull the members of cn=Exchange Install Domain Servers and do some site dependent ranking on them.
Looking for best practices.
Update Edit: 9/26 I have achieved a solution. It may be site specific, but I'll share below in an answer to show the final code. The answer provided by postanote provided pointers that helped me move forward.
There is no official documented best practices for PowerShell in general (there are too many variables in the mix, but some have put their thoughts in the topic , for example, this one - https://github.com/PoshCode/PowerShellPracticeAndStyle ) or for what you are asking for from Microsoft.
As for you point here:
I suppose I can pull the members of cn=Exchange Install Domain Servers
and do some site dependent ranking on them.
This is not something new, or tricky, so you can do this.
I have code in my personal library that I use that does this as well as for other doing resources, so I never have to hardcode server names for Exchange, SQL, DC, etc.
There are several blogs (that have been out there for a while now) on the topic with sample code to use as is or tweak as needed, which is why I asked what you've searched for.
One of those blog examples of how to do this is here:
https://use-powershell.blogspot.com/2012/12/find-exchange-servers-in-domain.html
The examples provided:
an active directory user with mailbox will have 2 attributes (msExchHomeServerName and homemdb) that will contain the name of the
mailbox server that has his mailbox - once conected to one server you
can use exchange console to find the rest of them;
Get-ADUser samaccountname -Properties msExchHomeServerName, homemdb |Select-Object msExchHomeServerName, homemdb |Format-List
active directory computer type objects contain "exchange" word in servicePrincipalName attribute; you can use only your
organizational unit that contain your servers if you have one to
narrow your search:
Get-ADComputer -Filter * -SearchBase 'OU= SERVERS, DC=domain_name,DC=net' -Properties * | Where-Object {$_.serviceprincipalname -like '*exchange*'} |select-object name
active directory configuration partition contain information about exchange servers in domain; you can search for objects of class
msExchExchangeServer:
Get-ADObject -LDAPFilter "(objectClass=msExchExchangeServer)" –SearchBase "CN=Configuration,DC=domainname,DC=net" | Select-Object name
or you can list all objects from "CN=Servers,CN=First Administrative Group,CN=Administrative Groups,CN=INTERNAL,CN=Microsoft
Exchange,CN=Services,CN=Configuration,DC=domainname,DC=net" using
powershell or ADSI Edit console;
Get-ADObject -Filter * -SearchBase "CN=Servers,CN=First Administrative Group,CN=Administrative Groups,CN=INTERNAL,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=domainname,DC=net" -SearchScope onelevel
Or this post:
https://social.technet.microsoft.com/Forums/ie/en-US/94d89161-9dfb-48fc-b307-2f0e1320c9dc/how-to-find-file-servers-and-exchange-servers-in-ad-
Example:
dsquery * "cn=Configuration,dc=MyDomain,dc=com" -Filter "(objectCategory=msExchExchangeServer)"
Or if you are really trying to get an Exchange server in given site, then this to already exists. See this GitHub source:
https://github.com/mikepfeiffer/PowerShell/blob/master/Get-ExchangeServerInSite.ps1
The sample provided is:
function Get-ExchangeServerInSite {
$ADSite = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]
$siteDN = $ADSite::GetComputerSite().GetDirectoryEntry().distinguishedName
$configNC=([ADSI]"LDAP://RootDse").configurationNamingContext
$search = new-object DirectoryServices.DirectorySearcher([ADSI]"LDAP://$configNC")
$objectClass = "objectClass=msExchExchangeServer"
$version = "versionNumber>=1937801568"
$site = "msExchServerSite=$siteDN"
$search.Filter = "(&($objectClass)($version)($site))"
$search.PageSize=1000
[void] $search.PropertiesToLoad.Add("name")
[void] $search.PropertiesToLoad.Add("msexchcurrentserverroles")
[void] $search.PropertiesToLoad.Add("networkaddress")
$search.FindAll() | %{
New-Object PSObject -Property #{
Name = $_.Properties.name[0]
FQDN = $_.Properties.networkaddress |
%{if ($_ -match "ncacn_ip_tcp") {$_.split(":")[1]}}
Roles = $_.Properties.msexchcurrentserverroles[0]
}
}
}
I'm accepting postanote's answer as being most helpful.
In the end, I created a solution that may be site and Exchange install specific, but it does illustrate another technique.
My constraints that were different than most other solutions I found and include; that the given user did not currently have a mailbox on the system. Hence the code beyond this snippet will gather some data, then select a specific database.
I discovered that the Exchange server version we have installed, creates some records in "CN=Exchange Install Domain Servers,CN=Microsoft Exchange System Objects," In particular, the Members attribute has a list of servers.
I extracted that list, sorted it by Site (local to front), and then resolved the FQDN to form a URI to connect.
Function Connect-Exchange {
# Find servers list, then sort by name given what site we are running in: SITE1 = Ascending , SITE2 = Descending
$ADSite = (Get-ADDomainController).Site
if ($ADSite -like "SITE1*") { $descOrder = $true } else { $descOrder = $false }
$exchSession = $null
$ExchServersDN = "CN=Exchange Install Domain Servers,CN=Microsoft Exchange System Objects,DC=example,DC=com”
$ExchServers = (Get-ADObject -Identity $($ExchServersDN) -Properties Member).Member | Sort-Object -Descending:$descOrder
# Iterate through Exchange server list until connection succeeds
$i = 0;
while ((-Not $exchSession) -and ($i -lt $ExchServers.Count)) {
$ExchServerURI = "http://" + (Get-ADObject -Identity $ExchServers[$i] -Properties dNSHostName).dnsHostName + "/Powershell"
$exchSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionURI $ExchServerURI -ErrorAction SilentlyContinue
If (-Not $exchSession) { $i++ }
else {
Import-PSSession $exchSession -DisableNameChecking | Out-Null
}
}
return $exchSession
}

Script that shows every group user belongs

I have to create script which lists every group that user, specified by name belongs and shows name of specified user(only local groups and accounts). Specified username should be only argument in script. If we don't declare username, script should use name of user running script. If we declare username which isn't current in system, script shows nothing so there won't be error.
It's my first script homework, I understand how basic programing algorithms work. Unfortunately I'm not familiar with scripting in powershell. I would be very glad if someone could lend a hand and write script or show some directed tutorials.
As for …
Apparently Get-ADPrincipalGroupMembership is not the way
… that's not really a valid statement and there are several Q&A's on your use case on this very site. Basically, it's just something like this.
# Get users with base properties and their group membership, display user and group name
ForEach ($TargetUser in (Get-ADUser -Filter *))
{
"`n" + "-"*12 + " Showing group membership for " + $TargetUser.SamAccountName
Get-ADPrincipalGroupMembership -Identity $TargetUser.SamAccountName | Select Name
}
# Results
------------ Showing group membership for Administrator
Name
----
Domain Users
Administrators
...
------------ Showing group membership for Guest
Domain Guests
Guests
Update for OP
I used the cmdlet to explain what you were using.
If you are on PowerShell v5, there are already local group cmdlets for this.
Get-Command -Name *LocalUser*
# Results
CommandType Name
Cmdlet Disable-LocalUser
Cmdlet Enable-LocalUser
Cmdlet Get-LocalUser
Cmdlet New-LocalUser
Cmdlet Remove-LocalUser
Cmdlet Rename-LocalUser
Cmdlet Set-LocalUser
Get-Command -Name *LocalGroup*
# Results
CommandType Name
Cmdlet Add-LocalGroupMember
Cmdlet Get-LocalGroup
Cmdlet Get-LocalGroupMember
Cmdlet New-LocalGroup
Cmdlet Remove-LocalGroup
Cmdlet Remove-LocalGroupMember
Cmdlet Rename-LocalGroup
Cmdlet Set-LocalGroup
Then doing something like this...
Clear-Host
$LocalUserName = Read-Host -Prompt 'Enter a username'
# If no user is passed, list all
If($LocalUserName -eq '')
{
ForEach($GroupName in Get-LocalGroup)
{
Get-LocalGroupMember -Group "$($GroupName.Name)" |
Select #{n='GroupName';e={$($GroupName.Name)}},Name
}
}
Else
{
# process only the user passed
Get-LocalGroup |
%{
If(Get-LocalGroupMember -Group "$($_.Name)" -Member $LocalUserName -ErrorAction SilentlyContinue)
{
[PSCustomObject]#{
GroupName = $_.Name
Username = $LocalUserName
}
}
}
}
If you are on lower versions, the you can use the PowerShellGallery.com module, or use ADSI directly. There are lots of articles and samples for this on this site and all over the web.
Example:
LocalUserManagement 3.0
a module that performs various local user management functions
See also:
Managing Local User Accounts with PowerShell - Part 1

Active Directory referral chasing issue

Maybe someone who has more experience with Active Directory can help me.
I need to get info such as OS, name, FQDN from a computer in a different domain.
I will explain what I mean.
I have root domain: example.com, with 2 subdomains: xxx.example.com and yyy.xxx.example.com
Each domain contain 1 computer. Both of them in one group, for example groupfoo, they also in different OU
I can get info about members in group, I try PowerShell and dsquery. Both of them return right list of computers in group. But I can get info only from computer in the same domain where I run PowerShell script and dsquery.
to be clear I have one more computer not in groupfoo, and this computer used for administrating Active Directory.
As I understand in Active Directory we have thing such as "referral chasing".
I read a lot and as I know Power Shell don't have an options such as "enable referral chasing". For dsquery I found option -r for recursive request.
What I have already tried:
PS> dsquery group -name goupfoo | dsget group -members
"CN=member01,OU=Domain Controllers,DC=xxx,DC=example,DC=com"
"CN=member02,OU=XXX,OU=Domain Controllers,DC=yyy,DC=xxx,DC=example,DC=com"
My computer in DC=yyy,DC=xxx,DC=example,DC=com I can get info from CN=member02,OU=XXX,OU=Domain Controllers,DC=yyy,DC=xxx,DC=example,DC=com
PS > dsquery * -filter "(&(objectClass=Computer)(objectCategory=Computer)(sAMAccountName=member02$))" -attr sAMAccountName operatingSystem
sAMAccountName operatingSystem
member02$ Windows Server 2008 R2 Standard
running the same command for member01 yielded no results :
PS > dsquery * -filter "(&(objectClass=Computer)(objectCategory=Computer)(sAMAccountName=member01$))" -attr sAMAccountName operatingSystem
PS >
I tried different variation of dsquery, I try -r key for recursive, but it's dosen't work.
Maybe important thing, in the settings of "DC=yyy,DC=xxx,DC=example,DC=com" I saw what "DC=xxx,DC=example,DC=com" it's a trusted parent for "DC=yyy,DC=xxx,DC=example,DC=com" maybe I can get info doing the same from parent domain?
The same I can get with Power Shell Get-ADGroup, Get-ADMember etc, I tried use all options, credentials, server etc. it's always return info only from one computer in the same domain as I am.
Try using a DirectorySearcher object:
$filter = "(&(objectCategory=Computer)(sAMAccountName=$computername))"
$properties = 'distinguishedName', 'sAMAccountName', ...
$search = New-Object DirectoryServices.DirectorySearcher
$search.SearchRoot = New-Object DirectoryServices.DirectoryEntry
$search.Filter = $filter
$search.SearchScope = 'Subtree'
$search.ReferralChasing = [DirectoryServices.ReferralChasingOption]::All
$properties | % { $search.PropertiesToLoad.Add($_) } | Out-Null
$search.FindAll()
I don't know if ActiveDirectory module cmdlets actually support referral chasing.