Batch deleting users based on a role

2764
3
09-05-2017 07:00 PM
RichStokes
New Contributor II

Hi,

I'm trying to convert an old portalpy/excalibur script that 'wipes' an ArcGIS Online organisation based on a role, but I'm struggling to recreate it in Python API.  Every week or so my current script seems to fail or present a new error, so I figure now is the time to update it.

The steps the script takes are as follows:

  1. Sign in as an administrator for the organisation
  2. Get a list of all users who have a particular role
  3. Remove any licenses that the users have assigned
  4. Remove any delete-protection on items
  5. Delete any content (including folders, and then groups)
  6. Delete the users
  7. Recreate the users (based on a csv list)
  8. Re-invite the user to predetermined groups

I've been using notebooks so far as it fits my limited knowledge of code and allows me to see where things are going wrong.  Trouble is, it's going wrong everywhere!

I can get a list of users, and then find and delete their content, but I can't seem to work out how to delete their folders and any groups that they're a member of.  It's possibly a very simple answer, but I figure I may as well ask in case it's also useful for anyone else.

Does anyone have an example script (using the Python API) or any tips to fully deleting users?

Thanks,

Rich

3 Replies
RichStokes
New Contributor II

On the off chance that anyone does have need of anything like this, I'm half way there.  Tips on making it look pretty rather than my fudged together code below are appreciated.

Edit: Finished the script as much as my patience allows for, I've copied it below (and attached the Python notebook) in case anyone finds it useful.

For further context I work as a trainer for a distributor, and we need to empty our ArcGIS Online training account once a week, so it can be used as fresh the next.  This is possibly very niche code...

 

Connect to Org

# import gis and connect to orgfrom arcgis.gis import GISgis = GIS("https://www.arcgis.com", "<admin_username", "admin_password")

Some examples of searching for users:

studentlist = gis.users.search('username:Student_001') # select individualstudentlist = gis.users.search('username:{Student_001 TO Student_020') # select range of studentsstudentlist = gis.users.search('role:0JEz0BZ5jCrbjbwu') # select all students
# get accounts# clean up anyone who was invited/set to user, making them a student role againstudentlistreset = gis.users.search('role:org_user')for user in studentlistreset:    user.update_role(role = '0JEz0BZ5jCrbjbwu')if len(studentlistreset) > 0:    print(str(len(studentlistreset))+" users converted")    studentlist = gis.users.search('role:0JEz0BZ5jCrbjbwu') # select all studentstrainerlist = gis.users.search('role:VMWmtlUSUiDq6xQv') # select all trainers    print("trainers: "+str(len(trainerlist))+" - students: "+str(len(studentlist)))

Process trainers

Caution: please move any demos or content over to the organisation admin user before running the next step - if you wish to keep them!

# delete trainer itemsprint("deleting trainer items...")for user in trainerlist:    traineritems = user.items()    try:        for item in traineritems:            print(str(item['owner'])+" - "+str(item['title'])+" - "+str(item['type']))            item.protect(enable = False)            item.delete()    except:        passprint("finished")
# delete trainer foldersprint("deleting trainer folders...")for user in trainerlist:    trainerfolders = user.folders    try:        for folder in trainerfolders:            print(str(item['owner'])+" - "+str(item['title']))            gis.content.delete_folder(folder['title'],owner=user)    except:        passprint("finished")
# delete trainer groupstrainergroups = gis.groups.search('owner:ea_trainer*')print("deleting trainer groups...")for group in trainergroups:    try:        print(str(group['owner'])+"\t"+str(group['title']))        group.delete()    except:        passprint("finished")

Process students

# delete student itemsprint("Deleting student items...")for user in studentlist:    useritems = user.items()    if len(useritems) > 0:        try:            for item in useritems:                print("Deleted "+str(item['owner'])+"\t"+str(item['title'])+" - "+str(item['type']))                item.protect(enable = False)                item.delete()        except:            passprint("finished")   
# delete student foldersprint("Deleting student folders...")for user in studentlist:    userfolders = user.folders    for folder in userfolders:        print(""+str(folder['title']))        gis.content.delete_folder(folder['title'],owner=user)print("finished")        
# delete student groupsprint("Deleting student groups...")usergroups = gis.groups.search(query='owner:{Student_000 TO Student_081}')for group in usergroups:    print(""+str(group['title']))    group.delete()print("finished")    
# Return licenses to pool# note that future apps will need to be added to this listtrainerlic = [trainer.get('username') for trainer in gis.users.search('role:VMWmtlUSUiDq6xQv')] # create list of trainers# find users who have pro licensesprint("Finding ArcGIS Pro licenses...")pro_lic = gis.admin.license.get('ArcGIS Pro')pro_users = pro_lic.all()# if the Pro user isn't a trainer, revoke the licenseprint("Returning ArcGIS Pro licenses...")for user in pro_users:    user_name = user['username']    if user_name not in trainerlic:        try:              pro_lic.revoke(username=user_name,entitlements='*')            print("removing Pro license: "+user_name)        except:            pass# Drone2Map        print("Finding Drone2Map licenses...")d2m_lic = gis.admin.license.get('Drone2Map for ArcGIS')d2m_users = d2m_lic.all()print("Returning Drone2Map licenses...")for user in d2m_users:    user_name = user['username']    if user_name not in trainerlic:        try:              d2m_lic.revoke(username=user_name,entitlements='*')            print("removing Drone2Map license: "+user_name)        except:            pass# GeoPlannerprint("Finding GeoPlanner licenses...")geo_lic = gis.admin.license.get('GeoPlanner for ArcGIS')geo_users = geo_lic.all()print("Returning GeoPlanner licenses...")for user in geo_users:    user_name = user['username']    if user_name not in trainerlic:        try:              geo_lic.revoke(username=user_name,entitlements='*')            print("removing GeoPlanner license: "+user_name)        except:            pass# AppStudio Standard       print("Finding AppStudio licenses...")app_lic = gis.admin.license.get('AppStudio for ArcGIS Standard')app_users = app_lic.all()print("Returning AppStudio licenses...")for user in app_users:    user_name = user['username']    if user_name not in trainerlic:        try:              app_lic.revoke(username=user_name,entitlements='*')            print("removing GeoPlanner license: "+user_name)        except:            pass# Community Analystprint("Finding Community Analyst licenses...")com_lic = gis.admin.license.get('ArcGIS Community Analyst')com_users = com_lic.all()print("Returning Community Analyst licenses...")for user in com_users:    user_name = user['username']    if user_name not in trainerlic:        try:              com_lic.revoke(username=user_name,entitlements='*')            print("removing GeoPlanner license: "+user_name)        except:            pass# Navigatorprint("Finding Navigator licenses...")nav_lic = gis.admin.license.get('Navigator for ArcGIS')nav_users = nav_lic.all()print("Returning Navigator licenses...")for user in nav_users:    user_name = user['username']    if user_name not in trainerlic:        try:              nav_lic.revoke(username=user_name,entitlements='*')            print("removing GeoPlanner license: "+user_name)        except:            pass# Business Analystprint("Finding Business Analyst licenses...")bus_lic = gis.admin.license.get('ArcGIS Business Analyst')bus_users = bus_lic.all()print("Returning Business Analyst licenses...")for user in bus_users:    user_name = user['username']    if user_name not in trainerlic:        try:              bus_lic.revoke(username=user_name,entitlements='*')            print("removing GeoPlanner license: "+user_name)        except:            pass        print("finished")
# delete students, or disable them otherwisefor user in studentlist:    try: # can't delete them if they have a license taken out        user.delete()        print(str(user['username'])+" deleted")    except:        user.disable() # if the user can't be deleted, try disabling it.        print(str(user['username'])+" disabled")print("finished")

Recreate Users

for user in studentlist:    try:        user_name = user.username        print("adding "+str(user['username']))        gis.users.create(username = user_name,                         password = '<password>',                         firstname = '<firstname>',                         lastname = '<lastname>',                         email = '<email>',                         description = 'Student account',                         role = 'org_user',                         provider = 'arcgis'                        )        user.update_role(role = '0JEz0BZ5jCrbjbwu')        user.update(thumbnail=r'http://www.arcgis.com/sharing/rest//community/users/Esri_Curator_Historical/info/esri_150.jpg')    except:        passprint("finished")

Add users to groups

# Groupsea_groups = gis.groups.search('tags:ea_org') # You can add a tag to any mandatory or desired groupsprint("Target groups:")for group in ea_groups:    print(str(group['title']))print("Adding users to groups...")for user in studentlist:    user_name = user.username    for group in ea_groups:        group.add_users([user_name])print("finished")

Hopefully, by this stage, nothing has crashed or set on fire, and your organisation is scrubbed and ready!

RichStokes
New Contributor II

As an addendum, if anyone fancies adding to this or improving it any way, I'm quite happy to throw the code on Github to make it easier.

MichaelKelly
Occasional Contributor III

Nice job Rich - really handy. I've made a slight variation (mainly simplified) to cater for what we want. Instead of deleting the user, we just change the password. And we disable the user when the course is finished. We also have a role which means students can't create groups so deleting these is not necessary.

  • Deletes any content created by students
  • Deletes any folders created by students
  • Disables student accounts
  • Verifies the correct role is applied to the user
  • Changes password

DeleteUserContent = True
DeleteUserFolders = True
DisableAccount = True
VerifyRole = True
RoleId = 'WlnpXFdbXk9EktYp'
ChangeUserPassword = True
OldPassword = 'MyOldPassword'
NewPassword = 'MyNewPassword'

PortalUsername = 'MyUsername'
PortalPassword = 'MyPassword'

Users = ['StudentUsernameOne', 'StudentUsernameTwo']

from arcgis.gis import GIS
from IPython.display import display, HTML

gis = GIS('https://www.arcgis.com', PortalUsername, PortalPassword)

for User in Users:
    display(HTML('<h1>User: {}</h1>'.format(User)))
    UserObject = gis.users.get('{}'.format(User))

    #DELETE USER CONTENT ITEMS
    if DeleteUserContent == True:
        display(HTML('<h3>Deleting User Content Items...</h3>'))
        
        Items = gis.content.search(query='owner:{}'.format(User), max_items=99999)
        print('{} Item(s) Returned'.format(len(Items)))
        try:
            for Item in Items:
                print('Deleting Item: {} - {} - {}'.format(str(Item['owner']), str(Item['title']), str(Item['type'])))
                display(Item)
                Item.protect(enable = False)
                Item.delete()
        except:
            pass
    else:
        display(HTML('<h3>DeleteUserContent not set to True</h3>'))

    #DELETE USER FOLDERS
    if DeleteUserFolders == True:
        display(HTML('<h3>Deleting User Folders...</h3>'))

        Folders = UserObject.folders
        print('{} Folder(s) Returned'.format(len(Folders)))
        
        try:
            for Folder in Folders:
                FolderDeletedResult = gis.content.delete_folder(Folder['title'], owner=User)
                print('Folder "{}" Deleted: {}'.format(Folder['title'], FolderDeletedResult))
        except:
            pass
    else:
        display(HTML('<h3>DeleteUserFolders not set to True</h3>'))
    
    #DISABLE ACCOUNT
    if DisableAccount == True:
        display(HTML('<h3>Account being disabled</h3>'))
        DisableAccountResult = UserObject.disable()
        print('Account Disabled: {}'.format(DisableAccountResult))
    else:
        display(HTML('<h3>DisableAccount not set to True - Ensuring account is not disabled</h3>'))
        DisableAccountResult = UserObject.enable()
        print('Account Enabled: {}'.format(DisableAccountResult))
    
    #VERIFY ROLE
    if VerifyRole == True:
        display(HTML('<h3>Role being Verified</h3>'))
        UpdateRoleResult = UserObject.update_role(role = RoleId)
        print('Specified Role Applied: {}'.format(UpdateRoleResult))
    else:
        display(HTML('<h3>VerifyRole not set to True</h3>'))

    #CHANGE PASSWORD
    if ChangeUserPassword == True:
        display(HTML('<h3>Password being changed</h3>'))
        ChangeUserPasswordResult = UserObject.reset(OldPassword, new_password = NewPassword)
        print('Password Changed: {}'.format(ChangeUserPasswordResult))
    else:
        display(HTML('<h3>ChangeUserPassword not set to True</h3>'))‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍