Bulk remove of services and local user accounts

Showing results for 
Search instead for 
Do you mean 
Reply
Contributor I
Posts: 12
Registered: ‎04-24-2018
#1 of 6 2,664
Accepted Solution

Bulk remove of services and local user accounts

I'm trying to remove systems that were discovered using the ./Delete_Resources.ps1 script that is found here:

https://centrify.force.com/support/Article/KB-8371-Script-to-bulk-delete-systems-from-CPS

 

This works great other than the fact that if there is a system that was discovered that has a service or local user that was discovered with it then I have to go into the portal and manually delete the service and/or local user. Since I can't select multiple services or local users this kind of defeats the purpose of having an automated script. A large share of systems have services and/or local users assigned to them.

 

Is there a Powershell script to remove services or users? 

 

I also noticed for the Delete_Resources.ps1 script there is a -ErrorAction variable that can be called. Is there something I can enter for that which will make the script skip systems with services and/or users?

Centrify Guru I
Posts: 2,430
Registered: ‎07-26-2012
#2 of 6 2,642

Re: Bulk remove of services and local user accounts

@MM572072,

 

Welcome back to the Centrify forums.

 

The key here is to modify your script to find out if there are any local accounts or services associated with the system, have those deleted, then delete the system.  The overall structure looks like this:

 

  1. Authenticate (via Cert, OAUTH2 or Interactively) to obtain a token.
  2. Query the system(s) in question.
  3. Query the system(s) in question for any local accounts or services (in the case of Windows).
  4. Delete the services or systems.
    Note that deleting the service or local accounts, can potentially cause issues, so I would recommend that you save in an encrypted matter.
  5. Delete the system in question.

I will reply with a modified version of the script (if my limited PowerShell powers allow me to do it).

 

R.P

Want to learn more about practical Centrify examples? Check out my blog at http://centrifying.blogspot.com
Follow Centrify:
Contributor I
Posts: 12
Registered: ‎04-24-2018
#3 of 6 2,636

Re: Bulk remove of services and local user accounts

Thanks. It's my first time working with Centrify in Powershell so that would be awesome to take a look at.

Centrify Guru I
Posts: 2,430
Registered: ‎07-26-2012
#4 of 6 2,621

Re: Bulk remove of services and local user accounts

[ Edited ]

Let's take a shot at this.  I'm going to type this in a journal style for those who (like me) rely on posts like this later to "refresh" my mind.

 

Problem

Bulk-deletion of discovered systems that may have services (Windows services, scheduled tasks or IIS application pool accounts) and/or local accounts under management.

Challenge:

  • The provided script does not take services and/or local accounts into consideration and it can't delete the parent object without the child objects (or associated objects) being deleted.
  • The existing local account passwords may be needed (perhaps the local Administrator account of a stand-alone system is vaulted).

Script Inspection

Let's first inspect delete_resources.ps1.  Looks like the script's main flow is like this:

  1. Authenticates interactively.
  2. Asks the user for the system discovered date (this then gets normalized by querying the local time for when the system was discovered).
  3. Uses a function called Query that leverages the /redrock/query endpoint find out all systems that were discovered the date specified.  The results are put in an array called  MyComputers.
  4. Asks the user if they'd like to continue with the deletion (otherwise it exits).
  5. Uses a foreach iteration of the list that uses the the DeleteResource function to delete the systems discovered.
  6. Performs housekeeping (Iogout and unload the module).

We need to be able to delete any local accounts or services associated with a system prior to deletion.  Our project basically needs to:

a) Query: be able to query local users and services for a particular server (we'll feed that to the /redrock/query call).

b) Actions: be able to delete the objects above (we need to find out if we have all functions we need).
This implies iterating through any child objects (accounts, services) before deleting the parent object (system).

c) Script Logic:  modify the main script logic to accomodate the additional operations.
The script needs to handle more than one operation.

 

Querying

  • We are looking for 2-child objects that may exist under a system:  a local account or a service (internally called subscription). 
    Note: In this version, subscriptions are Windows services (services, scheduled tasks and IIS pool accounts).
  • Resource:  The report builder is probably the best solution for this problem, especially (because I'm SQL challenged Smiley Very Happy).
    rep-build.PNG
    Here are the two queries needed:
    Determines Services running in a system
    
    SELECT Subscriptions.ID, Subscriptions.IsActive, Subscriptions.WindowsServiceName 
    FROM Server
    JOIN Subscriptions ON Server.ID = Subscriptions.ComputerID
    WHERE Subscriptions.ComputerID = '$ID' Determines local users in a system SELECT VaultAccount.Host, VaultAccount.ID, VaultAccount.IsManaged, VaultAccount.User
    FROM Server
    JOIN VaultAccount ON Server.ID = VaultAccount.Host
    WHERE VaultAccount.Host = '$ID'
  • The best way to go about this is to create a function.  The idea is for this function to be given the ID of a system and it returns two arrays of objects corresponding to any system local accounts or services.  The function looks like this:
    function CheckSystemObjects {
        param(
            [Parameter(Mandatory=$true)]
            $endpoint,
            [Parameter(Mandatory=$true)]
            $bearerToken,
            [Parameter(Mandatory=$true)]
            $ID
        )
    	
        $restArg = @{}
        $restArg.ID = $ID
        $services = @()
        $accounts = @()
    	
        $queryaccts = "SELECT VaultAccount.Host, VaultAccount.ID, VaultAccount.IsManaged, VaultAccount.User FROM Server JOIN VaultAccount ON Server.ID = VaultAccount.Host WHERE VaultAccount.Host = '$ID'" 
        $queryservices = "SELECT Subscriptions.ID, Subscriptions.IsActive, Subscriptions.WindowsServiceName FROM Server JOIN Subscriptions ON Server.ID = Subscriptions.ComputerID WHERE Subscriptions.ComputerID = '$ID' " 
        
        $services = Query -Endpoint $token.Endpoint -BearerToken $token.BearerToken -Query $queryservices 
        $accounts = Query -Endpoint $token.Endpoint -BearerToken $token.BearerToken -Query $queryaccts 
    
        return $services.results, $accounts.results		    
    }
    
    Notice that all it does is call the Query function with the SQL queries as the payload.   I copied the body from an existing function and modified it to my needs.  This will be copied under the functions folder and it will be called CheckSystemObjects.  Notice the two different arrays that it returns.

Actions

  • In the Centrify PowerShell Samples master, there's already a DeleteAccount function.  This makes our life much easier.
    A note about the version I used:  there is a tiny typo in this function in line 29 where it's missing the leading "/" in front of ServerManage/DeleteAccount.
  • There is no function to delete Services (subscriptions), but after spending some time in the Developer Site and reviewing the browser's developer tools, I was able to find the API I'm looking for.
    The API that deletes services is called /Subscriptions/DeleteSubscription and it takes the subscription ID as the only parameter needed.  This means that I need to build a function for this as well:
    function DeleteService {
        param(
            [Parameter(Mandatory=$true)]
            $endpoint,
            [Parameter(Mandatory=$true)]
            $bearerToken,
            [Parameter(Mandatory=$true)]
            $ID
    
        )
    	
        $restArg = @{}
        $restArg.ID = $ID
    
        $restResult = Centrify-InvokeREST -Method "/Subscriptions/DeleteSubscription" -Endpoint $endpoint -Token $bearerToken -ObjectContent $restArg -Verbose:$enableVerbose
        if($restResult.success -ne $true)
        {
            throw "Server error: $($restResult.Message)"
        }     
        
        return $restResult.Result		    
    }
    Note that the function has the same structure as the previous one (since I'm copying the body from the code that already exist in the /functions folder.  In this case, I changed the line with the Centrify-InvokeRest to use the /Subscriptions/DeleteSubscription function.
    To finish this task, I copied both of my newly-created functions under the /functions folder.  I used the same naming convention.

 

Script Logic

Since our script will be able to do several new things, we can look at our main functionality, variables and modifying our loops.

 

Function Includes

# Import sample function definitions for CPS
. $exampleRootDir\functions\Centrify.Samples.PowerShell.CPS.DeleteResource.ps1
. $exampleRootDir\functions\Centrify.Samples.PowerShell.CPS.DeleteAccount.ps1
. $exampleRootDir\functions\Centrify.Samples.PowerShell.CPS.CheckSystemObjects.ps1
. $exampleRootDir\functions\Centrify.Samples.PowerShell.CPS.DeleteService.ps1

I ended-up addign the DeleteAccount.ps1 from the existing functions and the newly-created functions: CheckSystemObjects.ps1 and DeleteService.ps1.

 

Housekeeping:  TLS 1.2

Let's add this line to the script to make sure future changes in protocol don't cause the infamous "The underlying connection was closed: An unexpected error occurred on a send" error.

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12

Variables:

  • $deleteComputersWithChildren (array) - holds an array of the systems that have local accounts or services.
  • $del (integer) - holds the count of computers to be deleted.
  • $delwc (integer) - holds the count of computers with child objects to be deleted.

Querying (foreach)

Modified the first foreach, to notify the user of systems with child objects.   Now two independent arrays are beign populated; one for the systems without child objects, another for the systems with local accounts and services.

foreach($comp in $myComputers)
    {
         if($comp.Row.DiscoveredTime -ne $null )
         
         {
            $localtime =  get-date  $comp.Row.DiscoveredTime.ToLocalTime()
            if((get-date -format d $localtime) -eq (get-date -format d $discoveredOn))
             {
                Write-Host "Resource Name: " $comp.Row.name " ID: " $comp.Row.ID " DiscoveredDate: " $comp.Row.DiscoveredTime
                $obj = CheckSystemObjects -endpoint $endpoint -bearerToken $token -ID $comp.Row.ID
                
                If ($obj[0].count -gt 0 -or $obj[1].count -gt 0) {
                    $deleteComputersWithChildren += $comp.Row
                    Write-Host 'WARNING: System '$comp.Row.name' has '$obj[1].count' account(s) and '$obj[0].count' service(s) that have to be deleted first.'  -ForegroundColor Yellow
                } Else {
                    $deleteComputers += $comp.Row
                }
                   
             }
         }
    }

 

Interacting with the User

Since this script can have now 3 options:  Delete systems without objects, Delete systems with objects or Exit the program, we are now using a switch statement:

 switch(Read-Host "Select a menu item: [1] Delete Systems without child objects($del); [2] Delete Systems with child objects ($delwc); [3] Exit")  {

    1 {
        foreach($comp in $deleteComputers)
        {
            Write-Host "Deleting  " $comp.Name
            $deleteResult = DeleteResource -Endpoint $token.Endpoint -BearerToken $token.BearerToken -ID $comp.ID
            if (!$deleteResult )
            {
                Write-Host "Failed to Delete  " $comp.Name
            }
        } 
      }

    2 {
        foreach($comp in $deleteComputersWithChildren)
        {
            $obj = CheckSystemObjects -endpoint $endpoint -bearerToken $token -ID $deleteComputersWithChildren.ID
            
            If ($obj[1].count -gt 0) {
            
                Write-Host 'Deleting '$obj[1].count' account(s) on' $comp.Name
            
                foreach($acct in $obj[1].Row)
                 {
                    Write-Host "Deleting Account:" $acct.User "from " $comp.Name "..."
                    $deleteAccount = DeleteAccount -Endpoint $token.Endpoint -BearerToken $token.BearerToken -ID $acct.ID
                 }
            }

            If ($obj[0].count -gt 0) {
            
                Write-Host 'Deleting '$obj[0].count' service(s) on' $comp.Name
            
                foreach($svc in $obj[0].Row)
                 {
                    Write-Host "Deleting Service:" $svc.WindowsServiceName "from " $comp.Name "..."
                    $deleteService = DeleteService -Endpoint $token.Endpoint -BearerToken $token.BearerToken -ID $svc.ID
                 }
            
            }
         }

         $deleteResult = DeleteResource -Endpoint $token.Endpoint -BearerToken $token.BearerToken -ID $comp.ID
         
         if (!$deleteResult )
          {
             Write-Host "Failed to Delete  " $comp.Name
          }
        }
    
    3 {
      Return
      }
   } 

 This results in this menu:
options.PNG

 

Afterwards, we can keep the same code (logoff and unload the module).  Here are some videos to see it in action.

 

Videos

Script Modifications

See it in action

 

Conclusion

There are many more improvements to be made:

  • Improve script flow and error logic.
  • Saving the password of a deleted account or the definition of a service.
  • Adding parameters to pick which action (or actions to perform).

See attachment for a modified version of modified_Delete_Resources.zip and the additional functions outlined above.  To make use of this:

  1. Download and unzip the Centrify PowerShell Samples from GitHub.
    https://github.com/centrify/centrify-samples-powershell
  2. Copy the modified_Delete_Resources.ps1 in the root of the unzipped folder.
  3. Copy the functions:
    Centrify.Samples.PowerShell.CPS.CheckSystemObjects.ps1,
    Centrify.Samples.PowerShell.CPS.DeleteService.ps1 and
    Centrify.Samples.PowerShell.CPS.DeleteAccount.ps1 to the functions folder.
  4. Call the modified_Delete_Resources.ps1 with this usage:
    .\modified_Delete_Resources.ps1 -endpoint https://your-tenant.centrify.com -username user@your-suffix -discoveredOn dd/mm/yyyy

Sample run:

 

PS C:\samples> .\modified_Delete_Resources.ps1 -endpoint https://your-tenant.centrify.com -username user@your-suffix -discoveredOn 06/02/2018
Mechanism 0 => Mobile Authenticator
Mechanism 1 => Email... @example.com
Mechanism 2 => SMS... XXX-5309
Mechanism 3 => Security Question
Mechanism 4 => Phone Call... XXX-5309
Choose mechanism: 3
Answer security question 'Are double-challenges aligned with NIST 800-53?':: ******
Mechanism 0 => Password
Enter Password: ***********
Current user:  user@your-suffix
Query resulted in  6  results, first row is:  @{Name=ip-172-31-33-82.us-west-2.compute.internal; ID=13387aa8-0e88-41e1-83cc-f26055617140; DiscoveredTime=6/3/2018 12:42:05 AM}
The following computers will be deleted
Resource Name:  ip-172-31-33-82.us-west-2.compute.internal  ID:  13387aa8-0e88-41e1-83cc-f26055617140  DiscoveredDate:  6/3/2018 12:42:05 AM
Resource Name:  ip-172-31-40-159.us-west-2.compute.internal  ID:  30148870-2322-4eb7-a903-a2ee9d99cf6e  DiscoveredDate:  6/3/2018 12:42:04 AM
Resource Name:  ip-172-31-32-66.us-west-2.compute.internal  ID:  338fa800-74a4-4ae6-b241-a1576972aecc  DiscoveredDate:  6/3/2018 12:42:04 AM
Resource Name:  ip-172-31-40-192.us-west-2.compute.internal  ID:  61a9cb57-5089-4c81-b67a-9d20bf86ca1b  DiscoveredDate:  6/3/2018 12:42:06 AM
Resource Name:  ip-172-31-20-115.us-west-2.compute.internal  ID:  898d217e-299f-41e5-89ba-c0c67af17054  DiscoveredDate:  6/3/2018 12:42:05 AM
Resource Name:  ip-172-31-29-175.us-west-2.compute.internal  ID:  a12b7796-e5e6-4314-940e-f39d52e8d1c9  DiscoveredDate:  6/3/2018 12:42:03 AM
Select an Action:
[1] Delete Systems without child objects (6);
[2] Delete Systems with child objects (0);
[3] Exit
Your Option: 

 

Want to learn more about practical Centrify examples? Check out my blog at http://centrifying.blogspot.com
Follow Centrify:
Contributor I
Posts: 12
Registered: ‎04-24-2018
#5 of 6 2,593

Re: Bulk remove of services and local user accounts

Perfect. Thank you so much. 

 

I'm so new to Centrify that this will help not only get the job done, in this case, but will help me learn how to do this, moving forward.

Centrify Guru I
Posts: 2,430
Registered: ‎07-26-2012
#6 of 6 2,591

Re: Bulk remove of services and local user accounts

[ Edited ]

@MM572072,

 

We are here to help.

Please don't hesitate to contact or provide feedback.

 

There are very cool plans to make this feature. Stay tuned.

 

Tip: As your do your debugging, keep in mind that the "DiscoveredOn" that you type in, is compared against the system's actual discovery date. 

 

R.P

Want to learn more about practical Centrify examples? Check out my blog at http://centrifying.blogspot.com
Follow Centrify: