{"id":52853,"date":"2009-07-28T03:01:00","date_gmt":"2009-07-28T03:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2009\/07\/28\/hey-scripting-guy-how-do-i-remove-all-group-members-in-active-directory\/"},"modified":"2009-07-28T03:01:00","modified_gmt":"2009-07-28T03:01:00","slug":"hey-scripting-guy-how-do-i-remove-all-group-members-in-active-directory","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/hey-scripting-guy-how-do-i-remove-all-group-members-in-active-directory\/","title":{"rendered":"Hey, Scripting Guy! How Do I Remove All Group Members in Active Directory?"},"content":{"rendered":"<p class=\"MsoNormal\">\n<p class=\"MsoNormal\"><span lang=\"EN\"><img decoding=\"async\" class=\"nearGraphic\" title=\"Hey, Scripting Guy! Question\" border=\"0\" alt=\"Hey, Scripting Guy! Question\" align=\"left\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/q-for-powertip.jpg\" width=\"34\" height=\"34\"><\/span><\/p>\n<p class=\"MsoNormal\">Hey Scripting Guy! I have several groups created in Active Directory whose membership has changed dramatically. Rather than go through a long list of users and try to manually clean up the list, I would like to just delete all the users from the group so that I can later add the newly approved members to the group. I have checked around, and there does not seem to be a <b>deleteall<\/b> method from a directory entry object in Windows PowerShell. Am I missing it?<\/p>\n<\/p>\n<p class=\"MsoNormal\">&#8212; JD<\/p>\n<p class=\"MsoNormal\">&nbsp;<\/p>\n<p class=\"MsoNormal\"><img decoding=\"async\" class=\"nearGraphic\" title=\"Hey, Scripting Guy! Answer\" border=\"0\" alt=\"Hey, Scripting Guy! Answer\" align=\"left\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/a-for-powertip.jpg\" width=\"34\" height=\"34\"><\/p>\n<p class=\"MsoNormal\">Hello JD, <\/p>\n<p class=\"MsoNormal\">This week I (Ed) am speaking at Microsoft TechReady in Seattle. TechReady is kind of like Tech<span>&#8729;<\/span>Ed except it is for Microsoft employees only. I have been attending lots of sessions, completing hands-on labs, and am generally fried<span>&mdash;<\/span>or maybe broiled (it&rsquo;s very hot here right now). The bad things about conferences such as this is that it always seems that there are two sessions that both sound good that are scheduled at the same time. As a result I end up missing some really cool session that sounds great. Of course, all the sessions are recorded, and all the Microsoft PowerPoint decks are posted on a SharePoint site, but it is not quite the same as attending the live session. When I gather around a lunch table and chat with friends and co-workers, I always ask about the sessions they attended. At times my choice is confirmed, but occasionally I feel as if I really missed out on something. <\/p>\n<p class=\"MsoNormal\">JD, I do not want you to miss out on anything, so I wrote the Remove-AllGroupMembers.ps1 script for you. One thing you will notice is there is no <b>deleteall<\/b> method, so you did not overlook a method. In fact, we pulled out a method we used in VBScript (<b>putex<\/b>) to perform the magical deletion you required. The complete Remove-AllGroupMembers.ps1 script is seen here. <\/p>\n<p class=\"CodeBlockScreenedHead\"><strong>Remove-AllGroupMembers.ps1<\/p>\n<p><\/strong><\/p>\n<p class=\"CodeBlockScreened\"><font size=\"1\"><font><font face=\"Lucida Sans Typewriter\">Param(<br><span>&nbsp;&nbsp; <\/span>[string]$group,<br><span>&nbsp;&nbsp; <\/span>[string]$ou,<br><span>&nbsp;&nbsp; <\/span>[string]$domain,<br><span>&nbsp;&nbsp; <\/span>[switch]$whatif,<br><span>&nbsp;&nbsp; <\/span>[switch]$help<br>) #end param<\/p>\n<p>Function Get-ScriptHelp<br>{<br><span>&nbsp;<\/span>&#8220;Remove-AllGroupMembers.ps1 removes all members of a group&#8221;<br><span>&nbsp;<\/span>&#8220;Remove-AllGroupMembers.ps1 -group cn=mygroup -ou ou=myou -domain &#8216;dc=nwtraders,dc=com'&#8221;<br><span>&nbsp;<\/span>&#8220;Remove-AllGroupMembers.ps1 -group cn=mygroup -ou ou=myou -domain &#8216;dc=nwtraders,dc=com&#8217; -whatif&#8221;<br>} # end function Get-ScriptHelp<\/p>\n<p>Function Remove-AllGroupMembers<br>{<br><span>&nbsp;<\/span>Param(<br><span>&nbsp;&nbsp; <\/span>[string]$group,<br><span>&nbsp;&nbsp; <\/span>[string]$ou,<br><span>&nbsp;&nbsp; <\/span>[string]$domain<br><span>&nbsp;<\/span>) #end param<br><span>&nbsp;<\/span>$ads_Property_Clear = 1<br><span>&nbsp;<\/span>$de = [adsi]&#8221;LDAP:\/\/$group,$ou,$domain&#8221;<br><span>&nbsp;<\/span>$de.putex($ads_Property_Clear,&#8221;member&#8221;,$null)<br><span>&nbsp;<\/span>$de.SetInfo()<br>} # end function Remove-AllGroupMembers<\/p>\n<p>Function Get-Whatif<br>{<br><span>&nbsp; <\/span>Param(<br><span>&nbsp;&nbsp; <\/span>[string]$group,<br><span>&nbsp;&nbsp; <\/span>[string]$ou,<br><span>&nbsp;&nbsp; <\/span>[string]$domain<br><span>&nbsp;<\/span>) #end param<br><span>&nbsp;<\/span>&#8220;WHATIF: Remove all members from $group,$ou,$domain&#8221; <br>} #end function Get-Whatif<\/p>\n<p># *** Entry Point to script ***<\/p>\n<p>if(-not($group -and $ou -and $domain)) <br><span>&nbsp; <\/span>{ throw (&#8220;group ou and domain required&#8221;) }<br>if($whatif) { Get-Whatif -group $group -ou $ou -domain $domain ; exit }<br>if($help) { Get-Scripthelp ; exit }<br>&#8220;Removing all members from $group,$ou,$domain&#8221;<br>Remove-AllGroupMembers -group $group -ou $ou -domain $domain<\/p>\n<p><\/font><\/font><\/font><\/p>\n<p class=\"MsoNormal\">The Remove-AllGroupMembers.ps1 script begins by creating several command-line parameters. To do this, the <b>Param<\/b> statement is used. The first three parameters are strings: the group name, the organizational unit name, and the domain name. These will need to be supplied to the script in the form of <b>cn=groupname<\/b>. You could modify the script to change this requirement, but it rapidly becomes rather complicated in trying to figure out how many levels down a group may reside. The last two parameters are switched parameters: the <b>whatif<\/b> and the <b>help<\/b> parameters. The <b>Param<\/b> section of the script is seen here: <\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>Param(<br><span>&nbsp;&nbsp; <\/span>[string]$group,<br><span>&nbsp;&nbsp; <\/span>[string]$ou,<br><span>&nbsp;&nbsp; <\/span>[string]$domain,<br><span>&nbsp;&nbsp; <\/span>[switch]$whatif,<br><span>&nbsp;&nbsp; <\/span>[switch]$help<br>) #end param<\/span><\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">I consider it a best practice when a script requires command-line parameters to include help information to illustrate how to actually use the script. This is useful not only for the users of your script, but also for you as well when you approach a script several months after it has been written. In Windows PowerShell 2.0, script help integrates with the <b>Get-Help<\/b> cmdlet, and can be created using stylized tags. But in Windows PowerShell 1.0, you are limited to displaying strings to the command line. By creating minimal help for your scripts now, you are preparing yourself to migrate the script to the more robust script help available in Windows PowerShell 2.0. <\/p>\n<p class=\"MsoNormal\">In the <b>Get-ScriptHelp<\/b> function, a short description of the script is displayed, as well as two representative command-line examples: <\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>Function Get-ScriptHelp<br>{<br><span>&nbsp;<\/span>&#8220;Remove-AllGroupMembers.ps1 removes all members of a group&#8221;<br><span>&nbsp;<\/span>&#8220;Remove-AllGroupMembers.ps1 -group cn=mygroup -ou ou=myou -domain &#8216;dc=nwtraders,dc=com'&#8221;<br><span>&nbsp;<\/span>&#8220;Remove-AllGroupMembers.ps1 -group cn=mygroup -ou ou=myou -domain &#8216;dc=nwtraders,dc=com&#8217; -whatif&#8221;<br>} # end function Get-ScriptHelp<\/span><\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">To remove all members of a group you can use the <b>Remove-AllGroupMembers<\/b> function. Members of a group are seen here: <\/p>\n<p class=\"Fig-Graphic\">\n<p><img decoding=\"async\" title=\"Image of akk members of a group\" alt=\"Image of akk members of a group\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/july\/hey0728\/hsg-07-28-09-01.jpg\" width=\"407\" height=\"448\"><a href=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/july\/hey0728\/hsg-07-28-09-01.jpg\"><\/a><\/p>\n<\/p>\n<p class=\"Num-Caption\">\n<p>&nbsp;<\/p>\n<p class=\"MsoNormal\">The function begins with three parameters: <b>group<\/b>, <b>ou<\/b>, and <b>domain<\/b>. Instead of listing these parameters in a long line in the <b>Function<\/b> declaration statement, I decided to move them inside the function script block and use the <b>Param<\/b> statement. This is exactly the same thing, but I consider it a best practice when declaring more than one or two parameters. This is because it makes it easier to read the script. In addition, it will set you up to make an easier migration to Windows PowerShell 2.0 because of the rich parameter statements that become available. The <b>Function<\/b> statement and the <b>Param<\/b> statement for the <b>Remove-AllgroupMembers<\/b> function is seen here: <\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>Function Remove-AllGroupMembers<br>{<br><span>&nbsp;<\/span>Param(<br><span>&nbsp;&nbsp; <\/span>[string]$group,<br><span>&nbsp;&nbsp; <\/span>[string]$ou,<br><span>&nbsp;&nbsp; <\/span>[string]$domain<br><span>&nbsp;<\/span>) #end param<\/span><\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">It is time get to the nuts and bolts of the <b>Remove-AllGroupMembers<\/b> function. First, a variable named <b>$ads_Property_Clear<\/b> is created and set to a value of 1. The number 1 is the value from the <span><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aa772282(VS.85).aspx\"><font face=\"Segoe\">ADS_PROPERTY_OPERATION_ENUM<\/font><\/a> <\/span><b>ADS_PROPERTY_CLEAR<\/b> constant. When this is set to 1, it instructs the directory service to remove all property values from the object:<\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span><span>$ads_Property_Clear = 1<\/span><\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">When you need to work with an object in Active Directory, it generally involves connecting to the object first. To do this, you use the <b>[adsi]<\/b> type accelerator, specify the protocol (LDAP), and supply the path to the object. This is seen here:<span>&nbsp; <\/span><\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$de = [adsi]&#8221;LDAP:\/\/$group,$ou,$domain&#8221;<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">To remove the members of the group, you use the <b>putex<\/b> method, which uses one of the <b>ADS_PROPERTY_OPERATION_ENUM<\/b> values seen in Table 1 in the first position of the method call. <\/p>\n<p class=\"TableNum-Title\"><strong>Table 1 ADS_PROPERTY_OPERATION_ENUM Values<\/strong><\/p>\n<table class=\"MsoNormalTable\" border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td valign=\"top\" width=\"270\">\n<div>\n<p class=\"TableHead\">Enumeration name<\/p>\n<\/div>\n<\/td>\n<td valign=\"top\" width=\"283\">\n<div>\n<p class=\"TableHead\">Enumeration value<\/p>\n<\/div>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"270\">\n<div>\n<p class=\"TableText\"><span>&nbsp; <\/span>ADS_PROPERTY_CLEAR<span>&nbsp;&nbsp;&nbsp; <\/span><\/p>\n<\/div>\n<\/td>\n<td valign=\"top\" width=\"283\">\n<div>\n<p class=\"TableText\"><span>&nbsp;<\/span>1<\/p>\n<\/div>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"270\">\n<div>\n<p class=\"TableText\"><span>&nbsp; <\/span>ADS_PROPERTY_UPDATE<span>&nbsp;&nbsp; <\/span><\/p>\n<\/div>\n<\/td>\n<td valign=\"top\" width=\"283\">\n<div>\n<p class=\"TableText\"><span>&nbsp;<\/span>2<\/p>\n<\/div>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"270\">\n<div>\n<p class=\"TableText\"><span>&nbsp; <\/span>ADS_PROPERTY_APPEND<span>&nbsp;&nbsp; <\/span><\/p>\n<\/div>\n<\/td>\n<td valign=\"top\" width=\"283\">\n<div>\n<p class=\"TableText\"><span>&nbsp;<\/span>3<\/p>\n<\/div>\n<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\" width=\"270\">\n<div>\n<p class=\"TableText\"><span>&nbsp; <\/span>ADS_PROPERTY_DELETE<span>&nbsp;&nbsp; <\/span><\/p>\n<\/div>\n<\/td>\n<td valign=\"top\" width=\"283\">\n<div>\n<p class=\"TableText\"><span>&nbsp;<\/span>4 <\/p>\n<\/div>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p class=\"MsoNormal\">\n<p>&nbsp;<\/p>\n<\/p>\n<p class=\"MsoNormal\">In the second position of the <b>putex<\/b> method call, you list the property you wish to work with. The third position must be filled out, but is ignored when you are clearing the contents of the property. After the changes have been made, you call the <b>SetInfo<\/b> method. This is all seen here: <\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>&nbsp;<\/span>$de.putex($ads_Property_Clear,&#8221;member&#8221;,$null)<br><span>&nbsp;<\/span>$de.SetInfo()<br>} # end function Remove-AllGroupMembers<\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">When you have a script that deletes things, it is a best practice to implement a <b>whatif<\/b> function. To do this, you create use the same parameters that are supplied to the <b>Remove-AllGroupMembers<\/b> function and display the string that gets put together. This is helpful in that it provides confirmation information about what the script is attempting to accomplish. The <b>Get-Whatif<\/b> function is seen here:<span>&nbsp; <\/span><span>&nbsp;<\/span><\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>Function Get-Whatif<br>{<br><span>&nbsp; <\/span>Param(<br><span>&nbsp;&nbsp; <\/span>[string]$group,<br><span>&nbsp;&nbsp; <\/span>[string]$ou,<br><span>&nbsp;&nbsp; <\/span>[string]$domain<br><span>&nbsp;<\/span>) #end param<br><span>&nbsp;<\/span>&#8220;WHATIF: Remove all members from $group,$ou,$domain&#8221; <br>} #end function Get-Whatif<\/span><\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">You now arrive at the entry point to the script. Because the script will fail if it does not receive the group name, ou name, and domain name, use an <b>if<\/b> statement to check for the presence of the values from the command line. To do this, you use the <b>not<\/b> operator and the <b>and<\/b> operator as seen here. If the values are not available, the <b>throw<\/b> command is used to raise an error. This is seen here: <\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>if(-not($group -and $ou -and $domain)) <br><span>&nbsp; <\/span>{ throw (&#8220;group ou and domain required&#8221;) }<\/span><\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">After checking for the group, ou and domain names, you next need to see if the script was run with the <b>whatif<\/b> switch. If it was, the <b>Get-Whatif<\/b> function is called and then the script exits. This is seen here: <\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span>if($whatif) { Get-Whatif -group $group -ou $ou -domain $domain ; exit }<\/span><\/p>\n<p><\/font><\/font><\/p>\n<p class=\"MsoNormal\">If the script is run with the <b>help<\/b> switch, it calls the <b>Get-ScriptHelp<\/b> function and exits: <\/p>\n<p class=\"CodeBlock\"><font size=\"1\"><font face=\"Lucida Sans Typewriter\"><span><\/span><\/font><\/font><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey Scripting Guy! I have several groups created in Active Directory whose membership has changed dramatically. Rather than go through a long list of users and try to manually clean up the list, I would like to just delete all the users from the group so that I can later add the newly approved members [&hellip;]<\/p>\n","protected":false},"author":595,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[7,44,3,45],"class_list":["post-52853","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-active-directory","tag-groups","tag-scripting-guy","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Hey Scripting Guy! I have several groups created in Active Directory whose membership has changed dramatically. Rather than go through a long list of users and try to manually clean up the list, I would like to just delete all the users from the group so that I can later add the newly approved members [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/52853","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/595"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=52853"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/52853\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=52853"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=52853"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=52853"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}