I'm trying to check our users in AD to see if they are part of a group, and if they're not add them to it.
The script I have doesn't seem to be working.
*Import-Module ActiveDirectory
$Grpex26Month = "EX-Retention 26 Months"
$Grpex13Month = "EX-Retention 13 Months"
Function Check-IsGroupMember{
Param($user,$grp)
$strFilter = "(&(objectClass=Group)(name=" + $grp +"))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
$colResults = $objSearcher.FindOne()
$objItem = $colResults.Properties
([string]$objItem.member).contains($user)
}
$userList = get-aduser -f {surname -like 'm*'}
Foreach ($user in $userList) {
$Check = Check-IsGroupMember $user $grpex13month
If ($Check -eq 'False') {
Add-adgroupmember $grpex13month $user
write-host $user.Name
}
}*
Now this is a script I modified that previously removed from one group and adding to another but I thought the changes above would still be ok. I'm also only searching for just 'M' at present as I know there is a user in this section that requires this.
This user isn't even being found...this is an example of response I'm getting for all the users apart from the new ones that were set up after this script was first written.
MLastName, FirstName
Add-adgroupmember : The specified account name is already a member of the group
At \\ServerName\DataUsers$\DKendall\Scripts\Exchange groupremoval.ps1:31 char:1
+ Add-adgroupmember $grpex13month $user
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (EX-Retention 13 Months:ADGroup)[Add-ADGroupMember], ADException
+ FullyQualifiedErrorId : ActiveDirectoryServer:1378,Microsoft.ActiveDirectory.Management.Commands.AddADGroupMember
To explain what Matt is getting at a little more, the issue is with this check:
If ($Check -eq 'False') {
When PowerShell is asked to evaluate a statement it looks at the type of the first object, and attempts to convert the second object to the same type. In this case $Check is [boolean] meaning its value is either $true or $false. When you echo it to the host the PowerShell formatter will convert it to a string to make it user friendly, but that's not what the object actually is.
$Check = $false
$Check.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Boolean System.ValueType
Now, as Matt stated, any string with a length longer than 0 will evaluate as $true when converted to [boolean]. So PowerShell looks at your statement, sees that $Check is [boolean] and tries to convert 'False' to [boolean] to match it. Since 'False' is a string with a length over 0 it converts to $true. Now we know that $Check = $false, and since it converted your string to a [boolean] value of $true the statement reads:
If ($false -eq $true) {
So in effect, your script is doing the exact opposite of what you want it to. Solutions to this include:
If ($Check -eq $false) {
or
If (-Not $Check) {
or the shortened version (what I would use)
If(!$Check){
Related
I'm working on a script to implement MFA deployment through Powershell. I'm connecting to an office365 and running the Get-MsolUser command to grab a list of users from AD (I believe). I'm putting it into an array which I'm then running through a ForEach loop. I'm not sure if this is even functional yet, but I'm trying to figure out how to exclude certain users from this loop as I don't want to activate MFA for domain admins.
Connect-MsolService
$array = #(Get-MsolUser | Select UserPrincipalName)
ForEach ($users in $array)
{
$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = "*"
$st.State = "Enabled"
$sta = #($st)
Set-MsolUser -UserPrincipalName $users -StrongAuthenticationRequirements $sta
}
So I guess the 3 questions I have are:
How can I exclude users with names matching a certain string such as "Admin, Administrator" in the Array?
Is there anyway to take user input and apply it to the username/password fields for Connect-MsolService?
3)Is this code even functional as it stands or am I totally off the mark?
As commented, there are some enhancements to be made in your code.
Try:
Starting with your question 2)
Connect-MsolService has a -Credential parameter and the easiest way to obtain that is by using the Get-Credential cmdlet:
# ask for credentials to make the connection
$cred = Get-Credential -Message 'Please enter your credentials to connect to Azure Active Directory'
Connect-MsolService -Credential $cred
Next, you want to define a list of users to exclude from being affected.
$excludeTheseUsers = 'admin', 'user1', 'user2' # etc.
# for using the regex `-notmatch` operator later, you need to combine the entries with the regex OR sign ('|'),
# but you need to make sure to escape special characters some names may contain
$excludes = ($excludeTheseUsers | ForEach-Object { [regex]::Escape($_) }) -join '|'
# create the StrongAuthenticationRequirement object just once, to use on all users
$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = "*"
$st.State = "Enabled"
$sta = #($st)
# get an array of UserPrincipalNames
$array = (Get-MsolUser | Where-Object { $_.DisplayName -notmatch $excludes }).UserPrincipalName
foreach ($user in $array) {
Set-MsolUser -UserPrincipalName $user -StrongAuthenticationRequirements $sta
}
I'm writing a powershell script that searches for users inside an Active Directory OU and allows me to reset passwords by choosing matches from a list. I found a Tutorial that uses the System.DirectoryServices.DirectoryEntry and System.DirectoryServices.DirectorySearcher, and modified it like so:
$objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP:\\[REDACTED]")
##ReadSTDIN
$strSearch = Read-Host -Prompt "Search"
$strCat = "(&(objectCategory=User)(Name=*" + $strSearch + "*))"
## Search Object
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strCat
$objSearcher.SearchScope = "Subtree"
#Load Required Properties into the dynObjLink
$objSearcher.PropertiesToLoad.Add("name")
$objSearcher.PropertiesToLoad.Add("userPrincipalName")
$objSearcher.PropertiesToLoad.Add("SamAccountName")
##Magical Search Function
$colResults = $objSearcher.FindAll()
$colResults.PropertiesLoaded
#for every returned userID add them to a table
ForEach ($objResult in $colResults)
{$a++
$objResult.count
$objItem = $objResult.Properties
$objItem.name
$objItem.userPrincipalName
$results.Add($a, $objItem.name + $objItem.userPrincipalName + $objItem.SamAccountName)
}
#Print Table
$results | Format-Table -AutoSize
This works well enough, but when it prints data I can only get the "first name" value of anything that comes back. Everything else becomes NULL and I can't figure out why.
Name Value
---- -----
3 {James3 [REDACTED], $null, $null}
2 {James2 [REDACTED], $null, $null}
1 {James1 [REDACTED], $null, $null}
I've tried different kinds of authentication and manipulating values, but the DirectorySearcher object only seems to collect the "name" value of any record it returns, no matter what I load into it. Help?
Here's a bit shorter (and PowerShell v2-compatible) way of doing this:
#requires -version 2
param(
[Parameter(Mandatory=$true)]
[String] $SearchPattern
)
$searcher = [ADSISearcher] "(&(objectClass=user)(name=$SearchPattern))"
$searcher.PageSize = 1000
$searcher.PropertiesToLoad.AddRange(#("name","samAccountName","userPrincipalName"))
$searchResults = $searcher.FindAll()
if ( $searchResults.Count -gt 0 ) {
foreach ( $searchResult in $searchResults ) {
$properties = $searchResult.Properties
$searchResult | Select-Object `
#{Name = "name"; Expression = {$properties["name"][0]}},
#{Name = "sAMAccountName"; Expression = {$properties["samaccountname"][0]}},
#{Name = "userPrincipalName"; Expression = {$properties["userprincipalname"][0]}}
}
}
$searchResults.Dispose()
Note that there's no need to build a list and output afterwards. Just output each search result. Put this code in a script file and call it:
PS C:\Scripts> .\Searcher.ps1 "*dyer*"
If you omit the parameter, PowerShell will prompt you for it (because the parameter is marked as mandatory).
try using Properties matching to the PropertiesToLoad
$entry = new-object -typename system.directoryservices.directoryentry -ArgumentList $LDAPServer, "ldap", "esildap"
$entry.Path="LDAP://OU=childOU,OU=parentOU,DC=dc1,DC=dc2"
$searcher = new-object -typename system.directoryservices.directorysearcher -ArgumentList $entry
$searcher.PropertiesToLoad.Add('samaccountname')
$searcher.PropertiesToLoad.Add('mail')
$searcher.PropertiesToLoad.Add('displayname')
$objs = $searcher.findall()
foreach($data in $objs)
{
$samaccountname = $data.properties['samaccountname'][0] + ''
$mail = $data.properties['mail'][0] + ''
$displayname = $data.properties['displayname'][0] + ''
}
when accessing the properties of the resultset you get a System.DirectoryServices.ResultPropertyValueCollection type for each property
to get a string value for passing to a database the property value access the zero index of the object
I found this function I'd like to use in a script I'm writing, but it keeps coming back $false when I can see an account is in a group and I can't figure out why?
function Check-IsGroupMember{
Param($user,$grp)
$strFilter = "(&(objectClass=Group)(name=" + $grp +"))"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
$colResults = $objSearcher.FindOne()
$objItem = $colResults.Properties
([string]$objItem.member).contains($user)
}
Usage:
Check-IsGroupMember "name of user" "DomainAdmins"
$objItem.member contains the DistinguishedName value of each principal who is a member of the group.
Even though the proper name of a person might be John Doe, the common name of the user account object may still be Doe, John, John G. Doe or anything else. This means that Contains() check (which is just a simple substring search) is not guaranteed to work as you expect.
The only real way to check is to either run another search for the user to find his/her DistinguishedName.
Personally, I would go for the AD PowerShell module from RSAT, rather than using a DirectorySearcher:
function Test-GroupMembership
{
Param(
[string]$UserName,
[string]$GroupName
)
$User = Get-ADUser -Identity $UserName
$Group = Get-ADGroup -Identity $GroupName -Properties member
$Group.member -contains $User.DistinguishedName
}
If size limit is your problem, you can use the DirectoryServer to retrieve a ranged result of the member attribute:
function Test-GroupMembership
{
[CmdletBinding()]
Param(
[string]$UserName,
[string]$GroupName
)
# Fetch User
$User = Get-ADUser -Identity $UserName
# return on failure
if(-not $User){
Write-Error -Message ('User "{0}" not found' -f $GroupName)
return $false
}
# Use DirectorySearcher to retrieve ranged member attribute
$GroupSearcher = '' -as [adsisearcher]
$GroupSearcher.Filter = '(&(objectClass=group)(name={0}))' -f $GroupName
$GroupSearcher.SearchScope = 'Subtree'
$GroupSearcher.SearchRoot = '' -as [adsi]
# AD reponds with at least 1500 values per multi-value attribute since Windows Server 2003
$Start = 1
$Range = 1500
$GroupMembers = #()
$HasMoreMembers = $false
# Keep retrieving member values until we've got them all
do{
# Use range operator to "page" values
# Ref: https://msdn.microsoft.com/en-us/library/aa367017(v=vs.85).aspx
$RangedMember = 'member;range={0}-{1}' -f $Start,$($Start + $Range - 1)
$GroupSearcher.PropertiesToLoad.Add($RangedMember) | Out-Null
# Retrieve group
$Group = $GroupSearcher.FindOne()
# return on failure
if(-not $Group) {
Write-Error -Message ('Group "{0}" not found' -f $GroupName)
return $false
}
# If we've reached the end of the member list,
# AD will return a property where the upper range
# value is *, so it might not be the same property
# name we specified in PropertiesToLoad
$ReturnedMember = #($Group.Properties.PropertyNames) -like 'member;*'
# Add all members to the $GroupMembers variable
foreach($member in $Group.Properties."$ReturnedMember") {
# Test if user is in the member list
if($member -eq $User.DistinguishedName){
return $true
}
}
# If we've reached the end, exit the loop
if($ReturnedMember -eq $RangedPropertyName){
$HasMoreMembers = $true
}
} while ($HasMoreMembers)
# User wasn't found
return $false
}
To provide a bit of consistency in user experience, please use Approved Verbs for command names in PowerShell (eg. Test-* instead of Check-*)
[adsisearcher] is a type accelerator for the DirectorySearcher class
I am running into an issue where about 10% of computers on my network are throwing a very strange errors when processing. The error I get is "Where-Object : A Parameter cannot be found that matches paramter name 'Property'" the code I'm using is as follows.
#Create ADSI Search object to query Active Directory for usernames
#Start-Transcript -Path "$env:userprofile\Desktop\log.txt"
$strFilter = "objectCategory=user"
$objDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=SD25;DC=DC;DC=DC")
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 100000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
#Populate ADSI with the extra fields of samaccountname which is the username, and memberof which gives you roughly which groups they are a memberof
$colProplist = "samaccountname", "memberof"
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
#Run the Search
$colResults = $objSearcher.FindAll()
#$colResults
$resultsarray = #()
#The way ADSI returns results, it populates all an array of every username listed within the scope, I then use this foreach recursive loop to find the name I need
foreach ($objResult in $colResults)
{
#Here I am taking each of the users, and finding the one which has the samaccountname of the user that is currently logged in
$objItem = $objResult.Properties | Where-Object -Property memberof -like ALL
#$groups = $objItem.memberof
#This is for diagnostics, if you output a logfile it will tell you the name and groups it is a member of
$objitem
}
#This is the beginnings of searching for a computer container in active directory.
$compFilter = "objectCategory=computer"
$compDomain = New-Object System.DirectoryServices.DirectoryEntry("LDAP://OU=OU;DC=DC;DC=DC")
$compSearcher = New-Object System.DirectoryServices.DirectorySearcher
$compSearcher.SearchRoot = $objDomain
$compSearcher.PageSize = 100000
$compSearcher.Filter = $strFilter
$compSearcher.SearchScope = "Subtree"
$compProplist = "name"
foreach ($i in $compPropList){$compSearcher.PropertiesToLoad.Add($i)}
$compResults = $compSearcher.FindAll()
foreach ($compR in $compResults)
{
}
#Stop-Transcript
IIRC -Property was introduced to Where-Object with PowerShell 3.0. Could you script be running on PowerShell 2.0?
Responding to comment
You need to create a filter script in the form of a scriptblock (i.e. PowerShell code in a set of braces) instead of using the comparison operator parameters they added for 3.0.
Try using
Where-Object { $_.memberof -like "ALL" }
or something like that. $_ refers to the current object in the pipeline. I couldn't find the docs for version 2.0 but I found Using the Where-Object Cmdlet for version 1.0 which was relevant for 2.0 AFAIK and should help you.
This function should run on Windows Server 2003 and 2008 R2
Using the command line to execute it line by line is SUCCESSFULL! Execution by script fails.
function addUser2Group([string]$user,[string]$group)
{
$cname = gc env:computername
$objUser = [ADSI]("WinNT://$user")
$objGroup = [ADSI]("WinNT://$cname/$group,group")
$members = $objGroup.PSBase.Invoke('Members')
$found = $false
foreach($m in $members)
{
if($m.GetType().InvokeMember('Name', 'GetProperty', $null, $m, $null) -eq $user)
{
$found = $true
}
}
if(-not $found)
{
$objGroup.PSBase.Invoke('Add',$objUser.PSBase.Path)
}
$members = $objGroup.PSBase.Invoke('Members')
$found = $false
foreach($m in $members)
{
if($m.GetType().InvokeMember('Name', 'GetProperty', $null, $m, $null) -eq $user)
{
$found = $true
}
}
return $found
}
addUser2Group('MyGlobalMonitoringUser',"SomeDBGroup")
It should add a user to a local group. But it only gives me the following error:
Exception calling "Invoke" with "2" argument(s): "Unknown error (0x80005000)"
+ $members = #($objGroup.PSBase.Invoke <<<< ("Members"))
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Edit: the error message that occurs with /add is
The following exception occurred while retrieving member "Add": "Unknown error (0x80005000)"
Code is:
function addUser2Group([string]$user,[string]$group)
{
$cname = gc env:computername
try
{
([adsi]"WinNT://$cname/$group,group").Add("WinNT://$cname/$user,user")
}
catch
{
write2log($_)
return $false
}
return $true
}
Why go through the pain of reflection when PowerShell will do it for you? Example:
$group = [ADSI]"WinNT://./Power Users,group"
$group.Add("WinNT://SYSTEM,user")
The above adds the SYSTEM local account to the local Power Users group. I am not sure why you are getting the specific error above, you might get it with this abbreviated syntax as well. The particular COM interface that is being used is IADsGroup - reference here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa706021.aspx
Note: Because you are actually consuming COM objects wrapped in .NET objects, it is a good idea to call the Dispose method on any ADSI objects that are created when you are finished with them.
Why wouldn't you use net localgroup /add in your script instead of all that nasty looking WMI? PowerShell is a shell, not an operating system :)
Observations and assumptions
There are a couple of assumptions that are made based on the code.
You only passed ONE parameter object with two values. Use the Param statement
Call the function differently addUser2Group -user 'MyGlobalMonitoringUser' -group "SomeDBGroup"
Validate the parameters passed. They should be checked for empty/null at least.
This would only work if you had the $group variable assigned a value before the script ran.
Based on the fact that the incorrect parameter passing the value of the $group variable remains empty. It then caused the rest of the code to fail always returning the value of $False.
Proposed solution
Recommended Reading:
Simplify your PowerShell Script with Parameter Validation
Include the Param "switch" in the function
Change the way you call the function.
Here's a copy of the code that works.
function addUser2Group
{
# Added the Param Switch
Param(
[string]$user,
[string]$group
)
$cname = gc env:computername
$objUser = [ADSI]("WinNT://$user")
$objGroup = [ADSI]("WinNT://$cname/$group,group")
#$members = $objGroup.Invoke('Members')
$found = $false
foreach($m in $members)
{
if($m.GetType().InvokeMember('Name', 'GetProperty', $null, $m, $null) -eq $user)
{
$found = $true
}
}
if(-not $found)
{
$objGroup.PSBase.Invoke('Add',$objUser.PSBase.Path)
}
$members = $objGroup.PSBase.Invoke('Members')
$found = $false
foreach($m in $members)
{
if($m.GetType().InvokeMember('Name', 'GetProperty', $null, $m, $null) -eq $user)
{
$found = $true
}
}
return $found
}
addUser2Group -user 'testing' -group "Administrators"
•0x80005000 ("The specified directory service attribute or value does not exist").
Parameter binding or Environmental culprit perhaps?
As for the issue with net localgroup: examine the error message carefully:
The following exception occurred while retrieving member "Add"
Evidentally the /add flag is not being properly set as it is being interpreted as a member name, but since no code is provided, can't say why.
Just to add a more uptodate approach roughly five years later:
$group = Get-LocalGroup SomeDBGroup
$user = Get-LocalUser MyGlobalMonitoringUser
Add-LocalGroupMember $group $user