Search GC replicas and find AD account - powershell

I have an issue which is to do with AD replication. We use a 3rd party app the create accounts in AD and then a powershell script (called by the app) to create the exchange accounts.
In the 3rd party app we can not tell which GC the ad account has been created on and therefore have to wait 20 minutes for replication to happen.
What I am trying to do is find which GC the account has been created on or is replicated to and connect to that server using....
set-adserversettings -preferredserver $ADserver
I currently have the below script and what I can't work out is to get it to stop when it finds the account and assign that GC to the $ADserver variable. The write-host line is only there for testing.
$ForestInfo = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$GCs = $ForestInfo.FindAllGlobalCatalogs()
Import-module activedirectory
ForEach ($GC in $GCs)
{
Write-Host $GC.Name
Get-aduser $ADUser
}
TIA
Andy

You can check whether Get-ADUser returns more than 0 objects to determine whether the GC satisfied your query. After that, use Set-ADServerSettings -PreferredGlobalCatalog to configure the preference
You will need to specify that you want to search the Global Catalog and not just the local directory. The Global Catalog is accessible from port 3268 on the DC, so it becomes something like:
$ForestInfo = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$GCs = $ForestInfo.FindAllGlobalCatalogs()
Import-module ActiveDirectory
$ADUserName = "someusername"
$ADDomainDN = "DC=child,DC=domain,DC=tld"
$FinalGlobalCatalog = $null
foreach ($GC in $GCs)
{
$GCendpoint = "{0}:3268" -f $GC.Name
$SearchResult = Get-ADUser -LDAPFilter "(&(samaccountname=$ADUserName))" -Server $GCEndpoint -SearchBase $ADDomainDN -ErrorAction SilentlyContinue
if(#($SearchResult).Count -gt 0){
$FinalGlobalCatalog = $GC
break
}
}
if($FinalGlobalCatalog){
Write-Host "Found one: $($FinalGlobalCatalog.Name)"
Set-ADServerSettings -PreferredGlobalCatalog $FinalGlobalCatalog.Name
} else {
Write-Host "Unable to locate GC replica containing user $ADUserName"
}

Related

How do I change the permissions of a user for 600+ folders in Outlook via PowerShell?

I'm trying to give an assistant access to part of someone's O365 mailbox. She currently has foldervisible permissions on his inbox. And there is about 607 folders under the inbox that she needs access to without having anymore permissions to the inbox itself.
Below is the code I've tried to run. I've removed the domain name but otherwise the code is exact. I've run the code twice and gotten no errors and it runs for a quite a while. But once it's complete, there is no change in the permissions.
ForEach($f in (Get-EXOMailboxFolderStatistics -identity jjo | Where { $_.FolderPath.Contains("jjo:/Inbox/Case Files") -eq $True } ) ) {
$fname = "jjo:" + $f.FolderPath.Replace("/","\");
Add-MailboxFolderPermission $fname -User gka -AccessRights Owner
}
Try to narrow down what's working and not;
Get-EXOMailboxFolderStatistics -identity jjo -ErrorAction Stop | Where-Object { $_.FolderPath.Contains("jjo:/Inbox/Case Files") } | ForEach-Object {
Try {
$fname = "jjo:" + $_.FolderPath.Replace("/","\")
Write-Host "Amending: $fname ..."
Add-MailboxFolderPermission $fname -User gka -AccessRights Owner -ErrorAction Stop
Write-Host "Done"
}
Catch {
$_
}
}
Write-Host "Complete"
On the client side, Extended MAPI (C++ or Delphi) can be used modify the ACL table on the folder level. If using Redemption (I am its author) is an option (any language), you can use RDOFolder.ACL collection to modify the permissions. Something along the lines (VBA, off the op of my head):
ROLE_PUBLISH_EDITOR = &H4FB
set Session = CreateObject("Redemption.RDOSession")
Session.MAPIOBJECT = Application.Session.MAPIOBJECT
set AddressEntry = Session.AddressBook.GAL.ResolveName("The Other User Name")
set Folder = Session.GetDefaultFolder(olFolderInbox)
for each subFolder in Folder.Folders
set ACE = subFolder.ACL.Add(AddressEntry)
ACE.Rights = ROLE_PUBLISH_EDITOR
next

Powershell: Count within foreach loop

I have a Powershell script that queries AD user certificates. I'd like to check and inform the user if his VPN certificate is going to expire soon. The script runs fine so far, but I have some instances where the user already has 2 VPN certificates and I don't want to notify if this is the case (in Script on step "### Execute next steps if count is less then 2"). I've tried to add ".count" to some of the variables, but since it's within the foreach, it's always giving me a "1" as a match. I have no clue how to achieve this, please help. Here's the script:
param (
[string]$queryDN = '...DC=com',
[string]$VPNcertname = 'VPN OID',
[int]$days = 14
)
# Make decision what to check
$userpath = $queryDN
$cert2check = $VPNcertname
# Begin script
$users = (Get-ADGroupMember -Identity $userpath).distinguishedName
foreach ($dude in $users) {
$user = Get-ADUser $dude -Property Certificates
$Certificatelist = $user.Certificates | foreach {
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $_
}
ForEach($cert in $Certificatelist){
$querycertificate = if($cert.EnhancedKeyUsageList.Where({$_.FriendlyName -eq $cert2check})){
### Execute next steps if count is less then 2
$expirationDate = $Cert.NotAfter
if ($expirationDate -lt [datetime]::Today.AddDays($days)) {
write-host The $cert2check certificate for user: $user.UserPrincipalName`, expires: $expirationDate. This is less than: $days days and should be changed soon.
}
}
}
}
Thanks
Thanks #Vesper. This was exactly what I was looking for:
$CertList2=$Certificatelist|where {$_.EnhancedKeyUsageList.FriendlyName -eq $cert2check}; if ($CertList2.count -gt 1) {...}

Missing AD module and can't get it, need something similar or something to simulate it

So I'm trying to output a complete KB list for all computers on a server (which works on one computer) but it doesn't recognize Get-ADcomputer as a cmdlet. When checking various sources, it appears that the AD module isn't included. As I'm doing this on a work computer/server I'm hesitant to download anything or anything of that nature.
Is there any way I can achieve the following without using the AD module or someway I might be missing how to import the module (if it exists, which I don't think it does on this system)?
# 1. Define credentials
$cred = Get-Credential
# 2. Define a scriptblock
$sb = {
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
$Searcher.QueryHistory(0,$HistoryCount) | ForEach-Object -Process {
$Title = $null
if ($_.Title -match "\(KB\d{6,7}\)") {
# Split returns an array of strings
$Title = ($_.Title -split '.*\((?<KB>KB\d{6,7})\)')[1]
} else {
$Title = $_.Title
}
$Result = $null
switch ($_.ResultCode) {
0 { $Result = 'NotStarted'}
1 { $Result = 'InProgress' }
2 { $Result = 'Succeeded' }
3 { $Result = 'SucceededWithErrors' }
4 { $Result = 'Failed' }
5 { $Result = 'Aborted' }
default { $Result = $_ }
}
New-Object -TypeName PSObject -Property #{
InstalledOn = Get-Date -Date $_.Date;
Title = $Title;
Name = $_.Title;
Status = $Result
}
} | Sort-Object -Descending:$false -Property InstalledOn | Where {
$_.Title -notmatch "^Definition\sUpdate"
}
}
#Get all servers in your AD (if less than 10000)
Get-ADComputer -ResultPageSize 10000 -SearchScope Subtree -Filter {
(OperatingSystem -like "Windows*Server*")
} | ForEach-Object {
# Get the computername from the AD object
$computer = $_.Name
# Create a hash table for splatting
$HT = #{
ComputerName = $computer ;
ScriptBlock = $sb ;
Credential = $cred;
ErrorAction = "Stop";
}
# Execute the code on remote computers
try {
Invoke-Command #HT
} catch {
Write-Warning -Message "Failed to execute on $computer because $($_.Exception.Message)"
}
} | Format-Table PSComputerName,Title,Status,InstalledOn,Name -AutoSize
You've got 3 options:
First is to just install the RSAT feature for AD which will include the AD module. This is probably the best option unless there is something specific preventing it. If you're running your script from a client operating systems you need to install the RSAT first, though.
Option 2 (which should only be used if adding the Windows feature is somehow an issue) is to download and use the Quest AD tools, which give very similar functionality, but it looks like Dell is doing their best to hide these now so that may be difficult to locate...
Option 3 is to use the .NET ADSI classes to access AD directly, which will work without any additional downloads on any system capable of running PowerShell. If you'd like to go this route you should check out the documentation for the interface Here and for the System.DirectoryServices namespace Here.
Edit
Just noticed the last part of your question, what do you mean by "a complete KB list"? Not just Windows updates or things updated manually or whatever? What else would be in a list of Windows updates that was not a Windows update?
You have not mentioned the OSes you are using but in general if you have a server 2008 R2 or above, all you have to do it activate the RSAT feature AD PowerShell Module and you will have the cmdlet you are looking for.
On a client machine, you 'have to' install RSAT, and then activate the features. You can take a look at the technet article for more info: https://technet.microsoft.com/en-us/library/ee449483(v=ws.10).aspx
If you don't want to use that option, then you will have to use .NET ADSI classes. There are tons of examples on how to do this, it basically boils down to a couple of lines really. Technet has examples on this as well: https://technet.microsoft.com/en-us/library/ff730967.aspx

Need to check for the existence of an account if true skip if false create account

I am trying to create local user on all servers and I want to schedule this as a scheduled task so that it can run continually capturing all new servers that are created.
I want to be able to check for the existence of an account and if true, skip; if false, create account.
I have imported a module called getlocalAccount.psm1 which allows me to return all local accounts on the server and another function called Add-LocaluserAccount
which allows me to add local accounts these work with no problems
when I try and run the script I have created the script runs but does not add accounts
Import-Module "H:\powershell scripts\GetLocalAccount.psm1"
Function Add-LocalUserAccount{
[CmdletBinding()]
param (
[parameter(ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName=$env:computername,
[parameter(Mandatory=$true)]
[string]$UserName,
[parameter(Mandatory=$true)]
[string]$Password,
[switch]$PasswordNeverExpires,
[string]$Description
)
foreach ($comp in $ComputerName){
[ADSI]$server="WinNT://$comp"
$user=$server.Create("User",$UserName)
$user.SetPassword($Password)
if ($Description){
$user.Put("Description",$Description)
}
if ($PasswordNeverExpires){
$flag=$User.UserFlags.value -bor 0x10000
$user.put("userflags",$flag)
}
$user.SetInfo()
}
}
$usr = "icec"
$rand = New-Object System.Random
$computers = "ServerA.","ServerB","Serverc","ServerD","ServerE"
Foreach ($Comp in $Computers){
if (Test-Connection -CN $comp -Count 1 -BufferSize 16 -Quiet){
$admin = $usr + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122) + [char]$rand.next(97,122)
Get-OSCLocalAccount -ComputerName $comp | select-Object {$_.name -like "icec*"}
if ($_.name -eq $false) {
Add-LocalUserAccount -ComputerName $comp -username $admin -Password "password" -PasswordNeverExpires
}
Write-Output "$comp online $admin"
} Else {
Write-Output "$comp Offline"
}
}
Why bother checking? You can't create an account that already exists; you will receive an error. And with the ubiquitous -ErrorAction parameter, you can determine how that ought to be dealt with, such as having the script Continue. Going beyond that, you can use a try-catch block to gracefully handle those exceptions and provide better output/logging options.
Regarding your specific script, please provide the actual error you receive. If it returns no error but performs no action check the following:
Event Logs on the target computer
Results of -Verbose or -Debug output from the cmdlets you employ in your script
ProcMon or so to see what system calls, if any, happen.
On a sidenote, please do not tag your post with v2 and v3. If you need a v2 compatible answer, then tag it with v2. Piling on all the tags with the word "powershell" in them will not get the question answered faster or more effectively.
You can do a quick check for a local account like so:
Get-WmiObject Win32_UserAccount -Filter "LocalAccount='true' and Name='Administrator'"
If they already exist, you can either output an error (Write-Error "User $UserName Already exists"), write a warning (Write-Warning "User $UserName Already exists"), or simply silently skip the option.
Please don't use -ErrorAction SilentlyContinue. Ever. It will hide future bugs and frustrate you when you go looking for them.
This can very easily be done in one line:
Get-LocalUser 'username'
Therefore, to do it as an if statement:
if((Get-LocalUser 'username').Enabled) { # do something }
If you're not sure what the local users are, you can list all of them:
Get-LocalUser *
If the user is not in that list, then the user is not a local user and you probably need to look somewhere else (e.g. Local Groups / AD Users / AD Groups
There are similar commands for looking those up, but I will not outline them here

Checking if Distribution Group Exists in Powershell

I am writing a script to quickly create a new distribution group and populate it with a CSV. I am having trouble testing to see if the group name already exists.
If I do a get-distributiongroup -id $NewGroupName and it does not exist I get an exception, which is what I expect to happen. If the group does exist then it lists the group, which is also what I expect. However, I can not find a good way to test if the group exists before I try to create it. I have tried using a try/catch, and also doing this:
Get-DistributionGroup -id $NewGroupName -ErrorAction "Stop"
which makes the try/catch work better (as I understand non-terminating errors).
Basically, I need to have the user enter a new group name to check if it is viable. If so, then the group gets created, if not it should prompt the user to enter another name.
You can use SilentlyContinue erroraction so that no exception/error shows:
$done = $false
while(-not $done)
{
$newGroupName = Read-Host "Enter group name"
$existingGroup = Get-DistributionGroup -Id $newGroupName -ErrorAction 'SilentlyContinue'
if(-not $existingGroup)
{
# create distribution group here
$done = $true
}
else
{
Write-Host "Group already exists"
}
}
This should do the trick:
((Get-DistributionGroup $NewGroupName -ErrorAction 'SilentlyContinue').IsValid) -eq $true