I'm trying to develop a powershell script to help with AD Group Membership management. We have a handful of large groups (30k-60k+ objects) that we want to update with data from another system.
The script loads the objects that should be in the group from a text file. Each object then has to located in AD using a System.DirectoryServices.DirectorySearcher. After that each object is added to the group membership.
The script spends some 80% of its time looking up each object, is there a bulk way to find objects in AD with powershell?
Thanks!
This is the fast way to query AD that I found in my experience, you need to change the query to find specific objects, in this code you'll find all user/person object in $objRecordSet.
$Ads_Scope_SubTree = 2
$objConnection = new-Object -com "ADODB.Connection"
$objCommand = new-Object -com "ADODB.Command"
$objConnection.Provider = "ADsDSOObject"
$objConnection.Open( "Active Directory Provider")
$objCommand.ActiveConnection = $objConnection
$objCommand.Properties.Item("Page Size").value = 1000
$objCommand.Properties.item("Searchscope").value = $Ads_Scope_SubTree
$objCommand.CommandText = "Select Name From 'LDAP://DC = int, DC= my, DC = local' Where objectCategory = 'Person'"
$objRecordSet = $objCommand.Execute()
$objRecordSet.RecordCount
More info here
You perhaps can try System.DirectoryServices.Protocols (S.DS.P) the native (non managed) version is quite efficient.
Here is a PowerShell starting script :
# ADDP-Connect.PS1
Clear-Host
# Add the needed assemblies
Add-Type -AssemblyName System.DirectoryServices.Protocols
# Connexion
$serverName = "WM2008R2ENT"
$ADDPConnect = New-Object System.DirectoryServices.Protocols.LdapConnection $serverName
$userName = "JPB"
$pwd = "PWD"
$domain = "Dom"
$ADDPConnect.Credential = New-Object system.Net.NetworkCredential -ArgumentList $userName,$pwd,$domain
# Create a searcher
$searchTargetOU = "dc=dom,dc=fr"
$searchFilter = "(samAccountName=user1)"
$searchScope = [System.DirectoryServices.Protocols.SearchScope]::Subtree
$searchAttrList = $null
foreach($user in "user1","user2","user3")
{
$searchFilter = "(samAccountName=$user)"
$searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $searchTargetOU,$searchFilter,$searchScope,$searchAttrList
$searchResponse = $ADDPConnect.SendRequest($searchRequest)
foreach($searchEntries in $searchResponse.Entries)
{
$searchEntries.DistinguishedName
}
}
If you start seeing timeout issues then set the timeout parameter appropriately like shown below
$ADDPConnect = New-Object System.DirectoryServices.Protocols.LdapConnection $serverName
$ADDPConnect.Timeout = "1000"
The below can help if you see timeout issues during execution
$ADDPConnect = New-Object System.DirectoryServices.Protocols.LdapConnection $serverName
$ADDPConnect.Timeout = "1000"
Related
I'm using EWS and trying to find e-mail messages that are flagged for action (i.e. their Flag.FlagStatus property is "Flagged"). I have successfully filtered e-mails based on their subject and other properties, but I just can't wrap my head around how to filter them based on FlagStatus.
The problem arises when defining $searchFilter in the code below.
The line returns error
"Exception calling "FindItems" with "2" argument(s): "Validation
failed. Parameter name: searchFilter""
I've tried using other variants of SearchFilter, e.g. SearchFilter+IsEqualTo, but all return the same error.
Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP3
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
$service.Credentials = New-Object System.Net.NetworkCredential("username","password")
$service.Url = "https://mail.server.net/EWS/Exchange.asmx"
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$propertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$propertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.ItemSchema]::Flag.FlagStatus, "Flagged")
$view = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1,0)
$messages = $inbox.FindItems($searchFilter, $view)
foreach ($item in $messages.Items) {
$item.Load($propertySet)
write-host $item.Flag.FlagStatus
write-host $item.Body.Text
}
This one works:
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.ItemSchema]::Categories, "Blue Category")
This one doesn't:
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.ItemSchema]::Flag.FlagStatus, "Flagged")
I'd suggest you use the Extendedproperty instead of the strongly typed one (which is derived from the ep anyway) eg
ExtendedPropertyDefinition PidTagFlagStatus = new ExtendedPropertyDefinition(0x1090, MapiPropertyType.Integer);
SearchFilter.Exists sfExits = new SearchFilter.Exists(PidTagFlagStatus);
should work to give you any messages that are flagged (eg the property exists) or you could be more specific to get followupFlagged
SearchFilter.IsEqualTo sfEqualTo = new SearchFilter.IsEqualTo(PidTagFlagStatus, 0x00000002);
in Powershell you need
$PidTagFlagStatus = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x1090, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+Exists($PidTagFlagStatus)
or
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PidTagFlagStatus, 0x00000002)
I am trying to add 1 ID to multiple security groups in Active Directory.
The ID needs to be only added to the "Security Tab" of the Security Group and not added as a member.
I need to set "write" permission for this ID.
Is there anyways to do this in Power-Shell?
There are instructions here, although that gives a user full control of the group (including rights to delete), and has some other issues (like a hard-coded username).
I've modified that example for you to only give GenericWrite permissions, and to accept the username as a parameter. This also assumes the user, group, and computer you're running this on are all on the same domain:
function Set-GroupSecurity {
[CmdletBinding()]
param (
[string] $GroupName,
[string] $UserName
)
$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$root = $dom.GetDirectoryEntry()
$search = [System.DirectoryServices.DirectorySearcher]$root
$search.Filter = "(&(objectclass=group)(sAMAccountName=$GroupName))"
$search.SizeLimit = 3000
$result = $search.FindOne()
$object = $result.GetDirectoryEntry()
$sec = $object.ObjectSecurity
## set the rights and control type
$allow = [System.Security.AccessControl.AccessControlType]::Allow
$read = [System.DirectoryServices.ActiveDirectoryRights]::GenericRead
$write = [System.DirectoryServices.ActiveDirectoryRights]::GenericWrite
## who does this apply to
$domname = ([ADSI]"").Name
$who = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList "$domname", $UserName
# apply rules
$readrule = New-Object -TypeName System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $who, $read, $allow
$sec.AddAccessRule($readrule)
$writerule = New-Object -TypeName System.DirectoryServices.ActiveDirectoryAccessRule -ArgumentList $who, $write, $allow
$sec.AddAccessRule($writerule)
# tell it that we're only changing the DACL and not the owner
$object.get_Options().SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl
# save
$object.CommitChanges()
}
You can paste that into a PowerShell prompt and hit enter. That will make the function available to use. Then you can use it like this:
Set-GroupSecurity -GroupName "TstGroup1" -UserName "someone"
I want use powershell and EWS (see https://msdn.microsoft.com/en-us/library/office/dd633710(v=exchg.80).aspx) to search for emails that contain a particular string in the subject line.
My problem is that the emails reside in a user-defined folder in the inbox, rather than in one of the folders listed in the WellKnownFolderName enumeration (see https://msdn.microsoft.com/en-us/library/microsoft.exchange.webservices.data.wellknownfoldername(v=exchg.80).aspx)
The example code that I have found to search for emails all wants to search in one of these well-known folder names rather than in an arbitrary user-specified folder.
does anyone have some example code that i can use as a reference to figure out how to do this [or does EWS limit you to searching for emails using a well-known folder name only].
My code so far is thus:
$email = "myemail#someplace.com"
$username = "myusername"
$password = "*****"
$domain = "mydomain"
$USER_DEFINED_FOLDER_IN_MAILBOX = "myRandomFolder"
$EXCHANGE_WEB_SERVICE_DLL = "C:\Program Files (x86)\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"
# load the assembly
[void] [Reflection.Assembly]::LoadFile($EXCHANGE_WEB_SERVICE_DLL)
# set ref to exchange
$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
# use first option if you want to impersonate, otherwise, grab your own credentials
$s.Credentials = New-Object Net.NetworkCredential($username, $password, $domain)
# discover the url from your email address
$s.AutodiscoverUrl($email)
# get a handle to the inbox
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$MailboxRootid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root, $email) # selection and creation of new root
$MailboxRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,$MailboxRootid)
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(100) #page size for displayed folders
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep; #Search traversal selection Deep = recursively
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::Displayname,$NAME_OF_ARCHIVE_FOLDER_IN_MAILBOX) #for each folder in mailbox define search
$findFolderResults = $MailboxRoot.FindFolders($SfSearchFilter,$fvFolderView)
$ArchiveFolder = ""
# This next loop successfully finds my folder, but it is an inefficient way
# to do it. It's ok, because there's not that many folders, but there's tens
# of thousands of emails to search through in the folder itself, and that will
# need a more efficient search.
foreach ($Fdr in $findFolderResults.Folders)
{
$theDisplayName = $Fdr.DisplayName
if($theDisplayName -eq $USER_DEFINED_FOLDER_IN_MAILBOX)
{
$ArchiveFolder = $Fdr
}
}
# Now to actually try and search through the emails in my $ArchiveFolder (the hard way)
$textToFindInSubject = "TEST"
$emailsInFolder = $ArchiveFolder.FindItems(9999) # <-- Successfully finds ALL emails with no filtering, requiring iterative code to find the ones I want.
foreach($individualEmail in $emailsInFolder.Items)
{
if($individualEmail.Subject -match "$textToFindInSubject")
{
# found the email i want - but a super inefficient
# way to do it
echo "Successfully found the email!"
}
}
# Attempt 1 to get the emails with a more refined search
$emailSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::Subject,$textToFindInSubject)
$emailsInFolder1 = $ArchiveFolder.FindItems($emailSearchFilter) # <-- Fails to return an object
# Attempt 2 to get the emails with a more refined search
$iv = new-object Microsoft.Exchange.WebServices.Data.ItemView(2000)
$emailsInFolder2 = $s.FindItems($ArchiveFolder, $emailSearchFilter, $iv) # <-- Also fails to return an object
echo "Done."
Thanks heaps :-)
I figured it out. Here are the lines of code that work [when appended to the initial code]
$searchfilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::Subject,$textToFindInSubject)
$itemView = new-object Microsoft.Exchange.WebServices.Data.ItemView(999)
$searchResults = $s.FindItems($ArchiveFolder.ID, $searchfilter, $itemView)
foreach($result in $searchResults)
{
$subj = $result.Subject
echo "Subject: $subj"
}
This EWS script lists all folders, including custom ones. I'm running this on our Exchange 2010 SP1 server:
Import-Module -Name "C:\Program Files\Microsoft\Exchange Server\V14\ClientAccess\Owa\Bin\Microsoft.Exchange.WebServices.dll"
$userEmail = "email.address#domain.com"
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)
$service.UseDefaultCredentials = $true
$service.AutoDiscoverUrl($userEmail)
$view = New-Object Microsoft.Exchange.WebServices.Data.FolderView(100)
$view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.Webservices.Data.BasePropertySet]::FirstClassProperties)
$view.PropertySet.Add([Microsoft.Exchange.Webservices.Data.FolderSchema]::DisplayName)
$view.Traversal = [Microsoft.Exchange.Webservices.Data.FolderTraversal]::Deep
$findResults = $service.FindFolders([Microsoft.Exchange.Webservices.Data.WellKnownFolderName]::MsgFolderRoot, $view)
# List all folders
$findResults | select displayname
All,
I am trying to create a list using a custom list template that includes content for SharePoint Online using powershell and CSOM. The list template for now is already loaded into the site collection. If I go through the UI, I can create the list on a site using the template including content without issue. If I try to do it through powershell, the list gets created but without the columns and or contents.
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword)
$clientContext.Credentials = $credentials
if (!$clientContext.ServerObjectIsNull.Value)
{
Write-Host "Connected to SharePoint Online site: '$Url'" -ForegroundColor Green
}
$web = $clientContext.Web
$templates = $clientContext.Site.GetCustomListTemplates($web)
$clientContext.Load($templates)
$clientContext.ExecuteQuery()
$template = $templates | Where-Object{ $_.Name -eq "SomeTemplate" }
$lci = New-Object Microsoft.SharePoint.Client.ListCreationInformation
$lci.Title = "Some List"
$lci.TemplateFeatureId = $template.FeatureId
$lci.TemplateType = $template.ListTemplateTypeKind
$lci.DocumentTemplateType = $template.ListTemplateTypeKind
$lists = $clientContext.Web.Lists;
$clientContext.Load($lists);
$clientContext.ExecuteQuery();
$list = $lists.Add($lci)
$list.Update()
$clientContext.ExecuteQuery()
I can't figure out what is missing, any help would be much appreciated.
I think you have to associate Fields to Content Type (CT) and then add this CT to your List.
Try look at this:
http://www.sharepointfire.com/2016/01/create-new-content-type-sharepoint-online-powershell/
In previous step he created Columns(Fields) which he used in creation:
$columns = "BlogNumber", "BlogText", "BlogUser"
Hope it will help you.
Alex
I Think you have to add the list template before update. It works for me
...
$lci.Title = "Some List"
$lci.TemplateFeatureId = $template.FeatureId
$lci.TemplateType = $template.ListTemplateTypeKind
$lci.DocumentTemplateType = $template.ListTemplateTypeKind
$lci.ListTemplate = $template
...
I am working on a function that will insert a row into a SQL database. It is basically a simple change log to help me track what is changed on my various SQL instances. As part of this, I want to have the following parameters:
Timestamp
Server
Instance
Change
I've got the Timestamp, Change, and Server all figured out, but the Instance is giving me some trouble. The Server parameter is dynamic, as it pulls a list of SQL servers from my inventory. I then want the value of that parameter to be used in another dynamic parameter, which pulls a list of the instances that are on that server (also from my inventory). Here is what I have for the dynamic portion:
DynamicParam {
if (!(Get-Module sqlps)){ Pop-Location; Import-Module sqlps -DisableNameChecking; Push-Location }
$inventoryinstance = 'ServerName'
$newparams = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
$server_query = 'SELECT [Name] FROM [ServerInventory].[dbo].[Servers] WHERE [TypeID] = 1 ORDER BY [Name]'
$servers = Invoke-Sqlcmd -serverinstance $inventoryinstance -query $server_query -connectiontimeout 5
# Populate array
$serverlist = #()
foreach ($servername in $servers.Name) {
$serverlist += $servername
}
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Position = 1
$attributes.Mandatory = $true
$attributes.HelpMessage = "The server the change was made on"
# Server list parameter setup
if ($serverlist){ $servervalidationset = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $serverlist }
$serverattributes = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$serverattributes.Add($attributes)
if ($serverlist){ $serverattributes.Add($servervalidationset) }
$serverob = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Server", [String], $serverattributes)
$newparams.Add("Server", $serverob)
$instance_query = "SELECT [Name] FROM [ServerInventory].[dbo].[SQLInstances] WHERE [ServerID] = (SELECT [ServerID] FROM [ServerInventory].[dbo].[Servers] WHERE [Name] = '$($PSBoundParameters.Server)')"
$instances = Invoke-Sqlcmd -serverinstance $inventoryinstance -query $instance_query -connectiontimeout 5
# Populate array
$instancelist = #()
foreach ($instancename in $instances.Name) {
$instancelist += $instancename
}
$attributes = New-Object System.Management.Automation.ParameterAttribute
$attributes.ParameterSetName = "__AllParameterSets"
$attributes.Position = 2
$attributes.Mandatory = $false
$attributes.HelpMessage = "The instance the change was made on, do not specify for server-level changes"
# Server list parameter setup
if ($instancelist){ $instancevalidationset = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList $instancelist }
$instanceattributes = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
$instanceattributes.Add($attributes)
if ($instancelist){ $instanceattributes.Add($instancevalidationset) }
$instanceob = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Instance", [String], $instanceattributes)
$newparams.Add("Instance", $instanceob)
return $newparams
}
Everything seems to be working, except the value for the instance variable doesn't autocomplete. Is it possible to use the value of one dynamic parameter to generate another?