Performing NET LOCALGROUP on users/groups more than 20 Chars with BigFix

We are using the appendfile command in conjunction with the NET LOCALGROUP Administrators command as provided in this example:

appendfile {“net localgroup administrators %22” & (concatenation " " of (members of local group “administrators” as string as lowercase) whose (it is not contained by “|” & computer name as lowercase & “\LocalAccount|” & computer name as lowercase & “\LocalAccount|” & computer name as lowercase & “\LocalAccount|domain\group|domain\group with spaces|domain\group”)) & “%22 /delete”}

Which works great, unless the user or group exceeds 20 characters. We didn’t know this was the case until today. So we’re working to find a another way to perform this function that can support long names.

Can anyone make a recommendation and/or provide code that will perform the same function? I am not sure if BigFix action script can do so, or if we need to use Powershell (if even supported).

I greatly appreciate any assistance!

Matt

Looks like this particular issue is outlined here: https://support.microsoft.com/en-us/kb/324639

Apparently you can write a vbscript that uses the API to perform this function

1 Like

Another option would be to use a createfile actionscript instead of appendfile.

Then in the createfile, you actually use the relevance substitution to build the commands that will be run, one command per item that will be deleted.

See this related relevance example: https://bigfix.me/relevance/details/2999306

Here is the actual fixlet/task that uses the relevance: https://bigfix.me/fixlet/details/3856

I guess I’m confused as to how that would help if the limitation is with net group. The executable that they are trying to run doesn’t accept group names with more than twenty characters.

It won’t work no matter how they write out the file on the system if the limitation is with net group itself

2 Likes

I guess I didn’t understand. I thought the issue was the number of users they were trying to delete at once in the same command line and if that line was too long, then it would fail. I didn’t realize that the issue was the length of a single user’s name or a single group’s name. In that case then something that uses an API like PowerShell / VBS / etc… like you mention is probably the only option.

1 Like

If you have a working vbscript or powershell to manage the group members, you can download or recreate the script using a createfile command, then execute your script in an Action.

If you don’t have a script already for doing this, let me know and I’ll post the one I’m using.

1 Like

Jason,

That would be great if you could post the one you’re using.

Thank you,
Matt

My script is probably way overkill, but it’s my generic group membership management script that I’ve used from several different tools including BigFix. You can put this entire content into a createfile, rename it to localgroup.vbs, then call it via cscript.

The script depends on WMI, and if you are adding Domain members it depends on the ADSI COM interface for vbscript (which should be present by default on Win2000 or higher); if used in a non-domain environment ensure you specify the Member as “.\username” to avoid trying to lookup the domain name.

createfile until EOF_EOF_EOF

strUsage="Usage: cscript " & WScript.ScriptFullName & " [/GROUP:<groupname>] [/member:<member>] [/operation:<add|remove|check|create|delete]> [/DEBUG]" _
 & VbCrLf & "Group must be a group on this computer" _
 & VbCrLf & "Member assumes current machine domain; override with MYDOMAIN\user or ""NT AUTHORITY\user""" _
 & VbCrLf & "Success or already correct add/delete returns 0; successful check returns 0" _
 & VbCrLf & vbTab & "add/remove/check: add,remove, or check MEMBER from GROUP" _
 & VbCrLf & vbTab & "create/delete: Create or delete the GROUP as a whole" _
 & VbCrLf & " Some member definitions are senstive.  There are distinctions that may require forms like:" _
 & VbCrLf & vbTab & " BUILTIN\Users    ""NT AUTHORITY\SYSTEM""  DOMAIN\username  .\groupname" _
 & VbCrLf & "/DEBUG: Show extra debugging output" _
 & VbCrLf & VbCrLf _
 & "Examples:" _
 & VbCrLf & vbTab & " cscript //nologo " & WScript.ScriptFullName & " /Group:Administrators /member:User1 /operation:add" _
 & VbCrLf & vbTab & " cscript //nologo " & WScript.ScriptFullName & " /Group:Administrators /member:.\User1 /operation:add" _
 & VbCrLf & vbTab & " cscript //nologo " & WScript.ScriptFullName & " /Group:Group2 /member:MYDOMAIN\User1 /operation:add" _
 & VbCrLf & vbTab & " cscript //nologo " & WScript.ScriptFullName & " /Group:Group2 /member:""NT AUTHORITY\NETWORK SERVICE"" /operation:add" _
 & VbCrLf & vbTab & " cscript //nologo " & WScript.ScriptFullName & " /Group:Users /member:S-1-5-21-123456789-123456789-123456789 /operation:remove" 
 
 
If WScript.Arguments.Named.exists("?") Then 
	Die strUsage,1
End If

blnDebug=WScript.Arguments.Named.Exists("Debug")

DebugMsg "Debug output enabled"

'Define Variables
Dim strOperation,strGroup,strMember,blnMemberRequired,strUsage,Mydomain,oNet,sComputer,vbCaseInsensitive,colAccounts,strGroupPath
Dim oLocalGroup, tmpDomain

vbCaseInsensitive=1

strOperation=Null
strGroup=Null
strMember=Null
blnMemberRequired=False

' Implicit argument handlers - "group" "member" "operation"
If WScript.Arguments.Unnamed.Count > 0 Then strGroup=WScript.Arguments.Unnamed(0)
If WScript.Arguments.Unnamed.Count > 1 Then strMember=WScript.Arguments.Unnamed(1)
If WScript.Arguments.Unnamed.Count > 2 Then strOperation=WScript.Arguments.Unnamed(2)

' Implicit argument handlers - /add, /remove, /check, /create, /delete
If WScript.Arguments.Named.exists("add") Then strOperation="add"
If WScript.Arguments.Named.exists("remove") Then strOperation="remove"
If WScript.Arguments.Named.exists("check") Then strOperation="check"
If WScript.Arguments.Named.exists("create") Then strOperation="create"
If WScript.Arguments.Named.exists("delete") Then strOperation="delete"
If WScript.Arguments.Named.exists("list") Then strOperation="list"

' Explicit argument handlers
If WScript.Arguments.Named.Exists("Operation") Then strOperation=WScript.Arguments.Named("Operation")
If WScript.Arguments.Named.Exists("Group") Then strGroup=WScript.Arguments.Named("Group")
If WScript.Arguments.Named.Exists("Member") Then strMember=WScript.Arguments.Named("Member")

If isNull(strOperation) Then Die strUsage,1
If isNull(strGroup) Then Die strUsage,1

If StrComp(strOperation,"add",vbCaseInsensitive)=0 Then blnMemberRequired=True
If StrComp(strOperation,"remove",vbCaseInsensitive)=0 Then blnMemberRequired=True
If StrComp(strOperation,"check",vbCaseInsensitive)=0 Then blnMemberRequired=True

DebugMsg "strGroup is " & strGroup
DebugMsg "strMember is " & strMember
DebugMsg "strOperation is " & strOperation

On Error Resume Next
DebugMsg "Connecting to WScript.Network object"
Set oNet = WScript.CreateObject("WScript.Network")
If Not IsObject(oNet) Then Set oNet=Nothing
If oNet Is Nothing Then Die "Error:Could not bind to Network object, cannot continue",1
sComputer = oNet.ComputerName
DebugMsg "sComputer is " & sComputer

DebugMsg "Connecting to WinNT://" & sComputer
Set colAccounts = GetObject("WinNT://" & sComputer)
If Err.Number<>0 Then Die "Could not bind to accounts using WinNT://" & sComputer,2

If blnMemberRequired Then
	DebugMsg "...this operation requires a Member defined"	
	DebugMsg "starting with strMember " & strMember
	' Needs to have sComputer defined
	If isNull(strMember) Then Die strUsage,1
	' Parse strMember into a WMI Path String

	DebugMsg "Determining member domain name"
	'strMember MAY include DOMAIN\
	DebugMsg "Comparing to WinNT: " & strcomp(Left(strMember,8),"WinNT://",1) 
	if (strcomp(Left(strMember,8),"WinNT://",1) = 0) Then
		' No need to manipulate strMember at all
		' This allows for things like SIDs using WinNT://S-1-5-21-xxxxx
		DebugMsg "Member supplied in form WinNT://, no manipulation necessary"
		myDomain=""
		
	elseif (strcomp(Left(strMember,9),"S-1-5-21-",1) = 0) Then
		' This allows for things like SIDs using WinNT://S-1-5-21-xxxxx
		DebugMsg "Member supplied in form of a SID, prepending WinNT://"
		myDomain=""
		strMember="WinNT://" & strMember
	Else
		If InStr(strMember,"\") Then
			DebugMsg "Determining domain name based on parameter entry"
			Mydomain=Left(strMember,InStr(strMember,"\")-1)
			strMember=Mid(strMember,InStr(strMember,"\")+1)
		Else
			' If the machine is not a member of an AD domain, GetDomainName will fail
			DebugMsg "Domain of member not specified, assuming computer domain"
			On Error Resume Next
			Mydomain=GetDomainName("NT4")
			If Err.number<>0 Then
				Die "Error looking up domain name; try supplying one in the form DOMAIN\user",2
			End If
		End If
		
		' If the domain is specified as ".\" or "COMPUTERNAME\", then *try* to convert it to fully-qualified name form
		'  i.e. WinNT://DOMAIN/computername/username
		'  - That's needed for group membership comparisons later as it's the form in which Membership is tracked
		' If we are NOT on a domain, this will fail, hopefully silently
		If (Mydomain=".") Or (StrComp(Mydomain,sComputer,vbCaseInsensitive)=0)  Then 
			On Error Resume Next
			tmpDomain=GetDomainName("NT4")
			If Err.number=0 Then 
				Mydomain=tmpDomain & "/" & sComputer 
			Else
				Err.Clear
			End If
		End If
		' Need to have faith in the user's selection for member; can't bind to dynamic members like "NT AUTHORITY\Interactive"
		strMember="WinNT://" & Mydomain & "/" & strMember
	end if
	
	DebugMsg "Member is " & strMember
End If





On Error goto 0
' Convert Group into a WMI Path String
strGroupPath="WinNT://" & sComputer & "/" & strGroup

DebugMsg "Group path is " & strGroupPath
' Attempt to bind to the Local Group; it may not exist (yet)
On Error Resume Next
Set oLocalGroup = GetObject(strGroupPath & ",group")
If Err.number<>0 Then Set oLocalgroup = Nothing
Err.Clear

On Error Resume Next
Select Case lcase(strOperation)
	Case "list"
		DebugMsg "Attempting to list members of local group"
		if oLocalGroup Is Nothing Then 
			Die "Could not bind to local group " & strGroup & " : " _
			& err.number & " : " & Err.Description, 2
		end if
		
		set oMembers=oLocalGroup.Members
				for each Member in oMembers
			wscript.echo Member.Name & ";" & Member.Class & ";" & Member.ADSPath
		Next
	
	Case "add"
		DebugMsg "Attempting to add member to local group"
		if oLocalGroup Is Nothing Then 
			DebugMsg "Local group does not exist, attempting to create it"
			Set oLocalGroup = CreateGroup(strGroup,colAccounts)
		End If
		If Err.number<>0 Then
	    		Die "Could not bind to or create group " & strGroup & " : " _
				& Err.Number & " : " & Err.Description, 2
	    End If
	    	    
	    DebugMsg "Bound to local group, checking existing membership"
	    
		if oLocalGroup.IsMember(strMember) Then
			Die "True: " & strMember & " is already in group " & oLocalGroup.adsPath, 0
		Else ' Group is bound, but user is not a member of it
			On Error Resume Next
			oLocalGroup.Add(strMember)
			If Err.number<>0 Then
				Die "Error: failed to add " & strMember & " to group " & oLocalGroup.adsPath & " : " _
				& Err.Number & " : " & Err.Description, 2
			Else
				Die "Success: added " & strMember & " to group " & oLocalGroup.adsPath, 0
			End if
		End If
		
	Case "remove"
		If oLocalGroup Is Nothing Then Die "Success: local group " & strGroup & " does not exist",0
		If oLocalGroup.IsMember(strMember) Then
			On Error Resume next
			oLocalGroup.Remove(strMember)
			If Err.number<>0 Then
				Die "Error: failed to delete " & strMember & " from group " & oLocalGroup.adsPath & " : " _
				& Err.Number & " : " & Err.Description, 2
			Else
				Die "Success: deleted " & strMember & " from group " & oLocalGroup.adsPath, 0
			End if
		Else
			Die "Success: " & strMember & " is already not a member of " & oLocalGroup.adsPath, 0
		End If
	Case "check"
		If oLocalGroup Is Nothing Then Die "False: Local group " & strGroup & " does not exist",1
		if oLocalGroup.IsMember(strMember) Then
			Die "True: " & strMember & " is in group " & oLocalGroup.adsPath, 0
		Else
			Die "False: " & strMember & " is not in group " & oLocalGroup.adsPath, 1
		End If
		
	Case "create"
		On Error Resume Next
		If Not oLocalGroup Is Nothing Then Die "Success: local group " & strGroup & " already exists",0
		
		' Set oLocalGroup = colAccounts.Create("group", strGroup)
' 		oLocalGroup.SetInfo
		Set oLocalGroup=CreateGroup(strGroup,colAccounts)
	    If Err.number=0 Then
	    	Die "Success: " & strGroup & " created",0
	    	Else
	    	Die "Error: Could not create group " & strGroup,2
	    End If			
	
	Case "delete"
		On Error Resume Next
		If oLocalGroup Is Nothing Then Die "Success: group " & oLocalGroup & " already does not exist",0
		colAccounts.Delete "group",strGroup
		If Err.number=0 Then
	    	Die "Success: " & strGroup & " deleted",0
	    	Else
	    	Die "Error: Could not delete group " & strGroup,2
	    End If			
		
	Case Else
		Die "Invalid OPERATION specified, cannot continue",1
End Select

Function CreateGroup(strGroup,colAccounts)
	On Error Resume Next
	DebugMsg "Entered CreateGroup()"
	If strGroup="" Then
		DebugMsg "CreateGroup: strGroup is blank"
		Exit Function
	End If
	
	If Not IsObject(colAccounts) Then
		DebugMsg "CreateGroup: colAccounts is not an object"
		Exit Function
	End If
	
	DebugMsg "CreateGroup: Creating group " & strGroup
	Set CreateGroup = colAccounts.Create("group", strGroup)
	If Err.number<>0 Then
	    		DebugMsg "CreateGroup:Could not bind to or create group " & strGroup & " : " _
				& Err.Number & " : " & Err.Description
	    End If
	CreateGroup.SetInfo
	
	If Err.number<>0 Then
	    		DebugMsg "CreateGroup:Could not SetInfo on new group " & strGroup & " : " _
				& Err.Number & " : " & Err.Description
	    End If
End Function

Function Die (strString, iErrorCode)
	WScript.Echo strString
	WScript.Quit iErrorCode
End Function

Function GetDomainName(strType)
	' NameTranslate constants
	Const ADS_NAME_TYPE_1779 = 1
	Const ADS_NAME_TYPE_CANONICAL = 2
	Const ADS_NAME_TYPE_NT4 = 3
	Const ADS_NAME_TYPE_DISPLAY = 4
	Const ADS_NAME_TYPE_DOMAIN_SIMPLE = 5
	Const ADS_NAME_TYPE_ENTERPRISE_SIMPLE = 6
	Const ADS_NAME_TYPE_GUID = 7
	Const ADS_NAME_TYPE_UNKNOWN = 8
	Const ADS_NAME_TYPE_USER_PRINCIPAL_NAME = 9
	Const ADS_NAME_TYPE_CANONICAL_EX = 10
	Const ADS_NAME_TYPE_SERVICE_PRINCIPAL_NAME = 11
	Const ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME = 12
	Const ADS_NAME_INITTYPE_GC = 3

	Dim strName

	set objRootDSE=GetObject("LDAP://RootDSE")
	defaultNC=objRootDSE.Get("defaultNamingContext")

	Set objTrans=CreateObject("NameTranslate")
	objTrans.Init ADS_NAME_INITTYPE_GC, ""
	objTrans.Set ADS_NAME_TYPE_1779, defaultNC
  
	Select Case UCASE(strType)
  		Case "DNS","ADS_NAME_TYPE_CANONICAL_EX"
  			strName=objTrans.Get(ADS_NAME_TYPE_CANONICAL_EX)
  			strName=Left(strName,Len(strName)-1)  		
  		Case "LDAP","DN"
  			strName=defaultNC
  		Case "GUID"
  			strName=objTrans.Get(ADS_NAME_TYPE_GUID)
		Case "NETBIOS","NT4","NT"
			strName=objTrans.Get(ADS_NAME_TYPE_NT4)
			strName=Left(strName, Len(strName) -1)
  		Case Else
  			On Error Resume Next
  			For i=1 To 12
  				WScript.Echo i & " """ & objTrans.Get(i) & """"
     			strName=strName & i & ":" & objTrans.Get(i)
    		Next
    		On Error goto 0
  	End Select
	GetDomainName=strName
End Function

Function DebugMsg(strString)
	If blnDebug Then
		If IsObject(WScript.StdErr) Then 
			WScript.StdErr.WriteLine strString
		Else
			WScript.Echo strString
		End If
	End If
End Function
EOF_EOF_EOF

delete localgroup.vbs
move __createfile localgroup.vbs
waithidden cscript //nologo localgroup.vbs /group:Administrators /member:"MYDOMAIN\account_with_a_really_really_long_name" /operation:remove
2 Likes

Thanks Jason, you thought of everything in that code. We went with a smaller code, but I really appreciate the fact you shared yours with us.

Thanks,
Matt

We were able to resolve this. I want to thank everyone for their assistance on this thread. I also wanted to share a redacted version of our code so that others that face this challenge could use it.

This code:

  1. Performs a function of deleting the appendfile (to start fresh)
  2. Inspects the members of the local group Administrators for members that don’t match those contained in between the | marks.
    2a. This can contain local users and domain users/groups
    2b. Replace LocalUser with the local user account name (repeat as necessary)
    2c. Replace domain\DomainGroupOrUser with a domain user or group (repeat as necessary)
    3 The code deletes any previous version of the PowerShell script used in this process (name it whatever you want)
  3. Copies the appendfile over to the named Powershell script using a separate command for each user to remove
  4. Executes the PowerShell script with an bypass execution policy (ensure your environment allows this)

Here is the code:
delete __appendfile

appendfile {concatenation “%0d%0a” of ("([ADSI]%22WinNT://" & computer name & “/Administrators,group%22).remove(%22WinNT://” & preceding text of first “” of it & “/” & following text of first “” of it & “%22)”) of (members of local group “administrators” as string as lowercase) whose (it is not contained by “|” & computer name as lowercase & “\LocalUser|” & computer name as lowercase & “LocalUser|” & computer name as lowercase & “\LocalUser|domain\DomainGroupOrUser|domain\DomainGroupOrUser|domain\DomainGroupOrUser”)}

delete adjustadmins.ps1
copy __appendfile adjustadmins.ps1
waithidden powershell.exe -executionpolicy bypass -file “.\adjustadmins.ps1”

I had a good number of people asked me about this at InterConnect 2015 - So I hope this helps!

Thank you,
Matt

1 Like

Yeah, the VBScript is a bit heavy, but it’s survived three different management infrastructures before I started using BigFix, and it’s slowly grown to cover all kinds of use cases over that time.

Agreed, I could see how solid and versatile it would be.