Trying to dynamically connect in Powershell to nearest Exchange Server - powershell

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
}

Related

Search all servers for service account

There has to be a better way
$server = (Get-ADComputer -Filter * -Properties *).name
foreach ($s in $server)
{
Get-WmiObject Win32_Service -filter 'STARTNAME LIKE "%serviceaccount%"' -computername $s
}
I want to search all servers on the domain for a service account. The above kind of does what I'm looking for but it doesnt return what server the services account was found on. Thanks in advance.
here's what i meant about using Get-Member to find the object properties that would give you the info you want. [grin]
this could be sped up considerably by giving the G-WO call a list of systems. i wasn't ready to code that just now. lazy ... [blush]
what it does ...
sets the account to look for
i only have the LocalSystem and NetworkService accounts listed on my services. [grin]
sets the computer list to search
you will likely use Get-ADComputer. make sure to either use the property name in the loop OR to make your query return only the actual name value.
i only have one system, so my list is 3 different ways to get to the same computer.
loops thru the systems
call G-WO to get the service[s] that use the target account
builds a [PSCustomObect] with the wanted properties
sends that to the $Result collection
shows that on screen
the code ...
$TargetAccount = 'LocalSystem'
$ComputerList = #(
'LocalHost'
'127.0.0.1'
$env:COMPUTERNAME
)
$Result = foreach ($CL_Item in $ComputerList)
{
# i didn't want a gazillion services, so this uses array notation to grab the 1st item
# if you want all the items, remove the trailing "[0]"
$GWMI_Result = #(Get-WmiObject -Class Win32_Service -Filter "STARTNAME LIKE '%$TargetAccount%'" -ComputerName $CL_Item)[0]
[PSCustomObject]#{
ComputerName = $GWMI_Result.SystemName
AccountName = $GWMI_Result.StartName
ServiceName = $GWMI_Result.Name
}
}
$Result
output ...
ComputerName AccountName ServiceName
------------ ----------- -----------
MySysName LocalSystem AMD External Events Utility
MySysName LocalSystem AMD External Events Utility
MySysName LocalSystem AMD External Events Utility

Powershell GPO Login Script checking AD resource group membership

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.

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.

PowerShell ProxyEmail Address

Currently I have the script below, which will compare email addresses in the CSV to the main mail address in ActiveDirectory, but it does not take proxy addresses into account. For instance, if Mary Smith had an email address Mary.Smith#abc.com, then Mary got married and her last name changed to Jones. Her standard email address is still Mary.smith#abc.com but she now has a proxy called mary.jones#abc.com.
How do I use this script to also validate against the proxyaddresses? Preferably without a huge hit to Active Directory .
$path = "H:\users.csv"
$csv = Import-Csv $path
Import-Module ActiveDirectory
foreach ($line in $csv)
{
$User = Get-ADUser -LDAPFilter "(&(objectclass=user)(mail=$($line.Email)))"
if ($User -eq $Null) {"User does not exist in AD " + $line.Email }
else {"User found in AD - " + $line.Email}
}
AD has a proxyAddresses attribute for mail-enabled users, which includes the primary SMTP address as well as any aliases. Change your -LDAPFilter argument to this:
"(&(objectclass=user)(proxyAddresses=*$($line.Email)*))"
BTW, it's possible for the mail attribute to differ from the primary SMTP address in proxyAddresses, because the latter is enforced by Exchange but the former can be changed at will outside Exchange. It's not likely if you have a single Exchange organization integrated with the domain, but if you're concerned about that possibility you can use this filter:
"(&(objectclass=user)(|(mail=$($line.Email))(proxyAddresses=*$($line.Email)*)))"
Assuming you're running Exchange, you'd be much better off using the Exchange Get-Recipient cmdlet for this. Exchange maintains a database indexed by SMTP address, so those lookups are immediate. AD does not, so it must search all the ProxyAddreses of every user looking for a match.
$ExSession = new-pssession -configurationname Microsoft.Exchange -ConnectionURI http://<ExchangeServerName>/powershell/ -authentication kerberos
foreach($line in $csv)
{
if (Invoke-Command {Get-Recipient $args[0]} -ArgumentList $line.Email -Session $ExSession -ErrorAction SilentlyContinue)
{"User exists in AD"}
else {"User not found in AD"}
}
Substitue the name of one of your Exchange servers for in the new-pssession command.

get all ADcontroller of another domain

I'm stuck in a stupid problem that I can't figure out how to solve.
I need to get all domain controllers of a trusted domain.
With this piece of code I get all DC in the current domain Get-ADDomainController -Filter *
With this I get one DC from target domain Get-ADDomainController -domain MyTrustedDomain -Discover
But how can I get all DC in target domain?
Can't test this due to lack of AD, but you could try the -Server option with the FQDN of the trusted domain:
Get-ADDomainController -Filter * -Server trusted.example.com
One way without using AD module:
$a = new-object 'System.DirectoryServices.ActiveDirectory.DirectoryContext'("domain", "other.domain.local" )
[System.DirectoryServices.ActiveDirectory.DomainController]::FindAll($a)
You need to be an 'authenticated user' in the remote domain or add username and password parameter to the DirectoryContext object
This command will list all domain controllers in the forest for each domain
(get-adforest).domains |%{get-addomaincontrollers -filter * -server $_}
I've come across the same problem as I work regularly with multiple domains. I was hoping for a more elegant solution, but so far the best I've come up with is to take your work one step further.
if Get-ADDomainController -domain MyTrustedDomain -Discover gives you one server in the target domain, you can feed that to the -server parameter to query that one DC. You do need to provide credentials to query a DC from a different domain than your login session if a trust DOES NOT exist (in a trust, the trusting domain considers you to be 'authenticated').
$targetdcname = (Get-ADDomainController -DomainName <MyTrustedDomain> -Discover).hostname
Get-ADDomainController -Filter * `
-Server $targetdcname `
-Credential (Get-Credential MyTrustedDomain\username) | ft HostName
or
Get-ADDomainController -Filter * `
-Server $((Get-ADDomainController -DomainName <MyTrustedDomain> -Discover).hostname) `
-Credential (Get-Credential MyTrustedDomain\username) | ft HostName
If you do this sort of thing alot, you can always store your credentials in a variable for reuse, $cred = Get-Credential MyTrustedDomain\username) and save the repeated prompts. The password is stored as a System.Security.SecureString and will be secure as long as you keep it within your session.
Until the Get-ADDomainController cmdlet is updated to allow both the -filter parameter AND the Domainname parameter, we're stuck with a workaround.
from: help get-addomaincontroller -examples
This should list all DCs in your domain
-------------------------- EXAMPLE 12 --------------------------
C:\PS>Get-ADDomainController -Filter { isGlobalCatalog -eq $true -and Site -eq "Default-First-Site-Name" }
Get all global catalogs in a given site.
Get-ADDomain -Identity <DOMAIN NAME> | select -ExpandProperty ReplicaDirectoryServers
Here is what I used
cls
$domains = (Get-ADForest).Domains;
foreach ($domain in $domains)
{
Write-Host $domain
(Get-ADDomain -Identity $domain | select -ExpandProperty ReplicaDirectoryServers).Count;
Write-Host "";
$totalCount = $totalCount + (Get-ADDomain -Identity $domain | select -ExpandProperty ReplicaDirectoryServers).Count;
}
Write-Host "Total domain controller count is: "$totalCount
Thanks for the start, here's what I came up with. Then I feed it to a SharePoint list.
get-adtrust -Filter * | Select-object Name, Domain,ipv4Address, OperatingSystem, Site, HostName, OperatingSystemVersion | ForEach-Object{Get-ADDomainController -Filter * -Server $_.Name}
Sometimes Powershell adds complexity, just open a cmd prompt and enter
C:\Windows\System32\nltest.exe /dclist:[trusted domain]
Of course, replace [trusted domain] with the name of the domain whose DC's you want.