Search

Service Request Purging – Easily delete Service Requests

A colleague of mine recently asked if I knew a good way to delete a subset of Service Requests (based around BMC Service Request Management module) so that they could go live without any test service requests that had accumulated over the past few weeks leading up to the upcoming go-live.  Normally, cleaning out test transactional data like Incidents, Changes, etc. now days is pretty easy as in recent versions BMC had added the correct OnDelete workflow that if you deleted the parent entry (e.g. HPD:Help Desk entry), it would delete all the related child data.  I was surprised to find that SRM did not follow this behavior and actually had workflow explicitly stopping you from doing so which was a bit of a pain.

After a bit of search it was clear that this has been like this for SRM from the start and others have run into similar situations, wanting to basically start from scratch with Service requests or at minimum be able to delete a subset of the service requests that may have been created for testing purposes requires a bit of elbow greese.  Here are some of the BMC Community links that talk about this:

https://communities.bmc.com/message/267841

https://communities.bmc.com/message/269781

https://communities.bmc.com/message/225416

In one of those posts, somebody actually went through the trouble of identifying all the related records that get created when you submit a service request and even went as far as detailing out their relationship to the parent SRM:Request entry which is the basis for this post.

Based on the information in those posts, I’ve put together a little utility that automates the deletion of either all or a subset of service requests along with their related data.  I’ve also added in version 2.0 the ability to specify related transactional data such as incidents, work orders, changes, etc as well as AIFs related to the service requests being deleted.

Here is a screenshot of it in action deleting the single cancelled status service request I had in my own system:

ServiceRequestPurge

You’ll notice it is a command line tool with a simple parameters list that basically just needs the info to connect to your AR Server as well as an optional -q parameter for specifying a query against the SRM:Request form for optionally specifying which Service Requests to delete.  If you don’t include the -q it will simply delete all service requests.  The thing to remember about the -q is in order to pass it in as a parameter on the command line, you need to escape things like double-quotes like I do in the example screenshot above.  For a listing of all the parameters you can just pass in -h to receive help info.

Because there really isn’t anything secretive about this and that the target forms and their relationships were outlined already in the community posts, I’ve also posted the code behind this utility for anybody to leverage/extend to their liking (the posting is a couple of years old so in newer versions of SRM their may be additional forms, if anybody knows of other areas that should be cleaned, please comment below and I will add them).  You’ll notice that this will also take care of the Filter that normally stops you from doing a Delete against the SRM:Request form by overlaying it, disabling it, running the delete jobs and then when all done, deleting the overlay of that filter to restore it to OOTB.

/**
 * Copyright (c) 2014 Curtis Gallant <cgallant@soleauthority.net>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package net.soleauthority.arsys.utils;

/**
 * ServiceRequestPurge - This program will allow you to delete BMC Service Request entries with all their supporting data
 */

import com.bmc.arsys.api.ARException;
import com.bmc.arsys.api.ARServerUser;
import com.bmc.arsys.api.Constants;
import com.bmc.arsys.api.Entry;
import com.bmc.arsys.api.EntryListInfo;
import com.bmc.arsys.api.Filter;
import com.bmc.arsys.api.OverlaidInfo;
import com.bmc.arsys.api.QualifierInfo;
import com.bmc.arsys.api.Value;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import java.util.ArrayList;
import java.util.List;

public class ServiceRequestPurge {
    private static final ARServerUser ctx = new ARServerUser();
    private static String serverName = null;
    private static String userName = null;
    private static String password = null;
    private static int port = 0;
    private static String query = null;
    private static boolean isTransactionalDelete = false;
    private static boolean isAIFDelete = false;
    private static List<String> childSchemas = new ArrayList<String>();
    private static String[] possibleChildSchemas = {"AP:Detail", "AP:Signature",
            "APR:Non-ApprovalNotifications", "AST:CMDB Associations",
            "CHG:Associations", "HPD:Associations", "NTE:Notifier Log",
            "PBM:Investigation Associations", "PBM:Known Error Associations",
            "PBM:Solution DB Associations", "SLM:EventSchedule", "SLM:Measurement",
            "SLM:MilestoneLogging", "SRM:AppInstanceBridge", "SRM:AppInstanceFlow",
            "SRM:Associations", "SRM:QuestionResponse", "SRM:SR_AuditLog",
            "SRM:Survey", "SRM:WorkInfo", "WOI:Associations", "HPD:Help Desk",
            "WOI:WorkOrder", "CHG:Infrastructure Change"};


    public static void main(String[] args) {
        System.out.println("\nService Request Purge v2.0 - Created by Curtis Gallant (cgallant@soleauthority.net)");
        parseArguments(args);
        ctx.setServer(serverName);
        ctx.setUser(userName);
        ctx.setPassword(password);
        ctx.setPort(port);
        try {
            ctx.login();
            ctx.setBaseOverlayFlag(false);
            ctx.setOverlayFlag(true);
            ctx.setOverlayGroup(String.valueOf(Constants.AR_GROUP_ID_ADMINISTRATOR));
            ctx.setDesignOverlayGroup(String.valueOf(Constants.AR_GROUP_ID_ADMINISTRATOR));
            toggleDeleteSafetyFilter("DISABLE");
            getListOfFormsToPurge();
            delServiceRequests(query);
            toggleDeleteSafetyFilter("ENABLE");
            ctx.logout();
        } catch (ARException e) {
            System.out.println("Can't login: " + e.getMessage());
        }
    }

    @SuppressWarnings("static-access")
    private static void parseArguments(String[] args) {
        // create the command line parser
        CommandLineParser parser = new GnuParser();
        HelpFormatter formatter = new HelpFormatter();

        Options options = new Options();
        options.addOption(OptionBuilder.withArgName("ServerName")
                .hasArg()
                .isRequired()
                .withDescription("Please provide the AR System Server Name")
                .create("x"));
        options.addOption(OptionBuilder.withArgName("UserName")
                .hasArg()
                .isRequired()
                .withDescription("Enter the Remedy administrator user name")
                .create("u"));
        options.addOption(OptionBuilder.withArgName("Password")
                .hasArg()
                .isRequired()
                .withDescription("Enter the Remedy administrator password")
                .create("p"));
        options.addOption(OptionBuilder.withArgName("Port")
                .hasArg()
                .withDescription("Enter the TCP Port for the Remedy Server")
                .create("t"));
        options.addOption(OptionBuilder.withArgName("Query")
                .hasArg()
                .withDescription("Enter the query for Service Requests to be deleted, use DB field names only and " +
                        "remember to escape, e.g \"'7' = \\\"\"Cancelled\\\"\"")
                .create("q"));
        options.addOption("h", false, "Print usage details");
        options.addOption("trans", false, "Delete directly created transactional records, e.g. Incidents, Work Orders, etc.");
        options.addOption("aif", false, "Delete AIFs associated with target Server Request records");

        try {
            CommandLine line = parser.parse(options, args);
            if (line.hasOption("h")) {
                formatter.printHelp("ServiceRequestPurge", options, true);
                System.exit(0);
            }
            if (line.hasOption("x")) {
                serverName = line.getOptionValue("x");
            }
            if (line.hasOption("u")) {
                userName = line.getOptionValue("u");
            }
            if (line.hasOption("p")) {
                password = line.getOptionValue("p");
            }
            if (line.hasOption("t")) {
                port = Integer.parseInt(line.getOptionValue("t"));
            }
            if (line.hasOption("q")) {
                query = line.getOptionValue("q");
            }
            if (line.hasOption("trans")) {
                isTransactionalDelete = true;
            }
            if (line.hasOption("aif")) {
                isAIFDelete = true;
            }
        } catch (ParseException exp) {
            System.out.println(exp.getMessage());
            formatter.printHelp("ServiceRequestPurge", options, true);
            System.exit(1);
        }
    }

    private static void toggleDeleteSafetyFilter(String cmd) {
        String deleteFilterName = "SRM:SHR:Permission_001_DeleteError";
        try {
            Filter filter = ctx.getFilter(deleteFilterName);
            Value overlayProperty = filter.getProperties().get(Constants.AR_SMOPROP_OVERLAY_PROPERTY);
            if (cmd.equals("DISABLE")) {
                if (overlayProperty == null) {
                    createOverlayForObject(Constants.AR_STRUCT_ITEM_FILTER, filter.getName());
                }
                Thread.sleep(2500);
                Filter overlaidFilter = ctx.getFilter(deleteFilterName);
                overlaidFilter.setEnable(false);
                ctx.setFilter(overlaidFilter);
                Thread.sleep(2500);
            } else if (cmd.equals("ENABLE")) {
                ctx.deleteFilter(deleteFilterName, Constants.AR_DEFAULT_DELETE_OPTION);
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }

    private static void createOverlayForObject(int objectStructType, String object) {
        OverlaidInfo overlaidInfo = new OverlaidInfo();
        overlaidInfo.setName(object);
        overlaidInfo.setObjType(objectStructType);
        try {
            ctx.createOverlay(overlaidInfo);
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }

    private static void getListOfFormsToPurge() {
        try {
            List<String> formsList = ctx.getListForm(0, Constants.AR_LIST_SCHEMA_ALL_WITH_DATA | Constants.AR_HIDDEN_INCREMENT);
            for (String form : formsList) {
                for (String childForm : possibleChildSchemas) {
                    if (form.equals(childForm)) {
                        childSchemas.add(form);
                    }
                }
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }

    private static void delServiceRequests(String query) {
        List<Entry> serviceRequests;
        QualifierInfo qual;
        int[] fields = {1, 1000000829, 179, 303503400, 302825900};
        try {
            qual = ctx.parseQualification("SRM:Request", query);
            serviceRequests = ctx.getListEntryObjects("SRM:Request", qual, 0, 999999999, null, fields, false, null);
            for (Entry serviceRequest : serviceRequests) {
                System.out.println(" \nDeleting Service Request: " + serviceRequest.get(1000000829));
                if (isAIFDelete) {
                    if (serviceRequest.get(303503400) != null) {
                        if (serviceRequest.get(303503400).toString().equals("2000")) {
                            int[] AIFFormNameField = {302826200};
                            String qualStr = "'179' = \"" + serviceRequest.get(302825900) + "\"";
                            QualifierInfo AIFFormQual = ctx.parseQualification("SRS:CFGAdvancedInterface", qualStr);
                            List<EntryListInfo> AIFForms = ctx.getListEntry("SRS:CFGAdvancedInterface", AIFFormQual, 0, 1, null, null, false, null);
                            for (EntryListInfo AIFFormEntryInfo : AIFForms) {
                                Entry AIFFormEntry = ctx.getEntry("SRS:CFGAdvancedInterface", AIFFormEntryInfo.getEntryID(), AIFFormNameField);
                                String AIFFormName = AIFFormEntry.get(302826200).toString();
                                childSchemas.add(AIFFormName);
                            }
                        }
                    }
                }
                for (String childSchema : childSchemas) {
                    try {
                        delChildRecordManager(childSchema, serviceRequest);
                    } catch (Exception ignored) {
                    }
                }
                ctx.deleteEntry("SRM:Request", serviceRequest.getEntryId(), 0);
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }

    private static void delChildRecordManager(String childSchema, Entry entry) {
        if (childSchema.equals("AP:Detail") || childSchema.equals("AP:Signature")) {
            String qualStr = "'8' = \"" + entry.get(1) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SRM:SR_AuditLog")) {
            String qualStr = "'450' = \"" + entry.get(1) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("NTE:Notifier Log")) {
            String qualStr = "'1000000205' = \"" + entry.get(1000000829) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("AST:CMDB Associations") || childSchema.equals("CHG:Associations") ||
                childSchema.equals("PBM:Investigation Associations") || childSchema.equals("PBM:Known Error Associations") ||
                childSchema.equals("PBM:Solution DB Associations") || childSchema.equals("WOI:Associations") ||
                childSchema.equals("HPD:Associations")) {
            String qualStr = "'1000000204' = \"" + entry.get(1000000829) + "\" AND '1000000211' = 23000";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SRM:Survey")) {
            String qualStr = "'301693900' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SRM:WorkInfo")) {
            String qualStr = "'10001821' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SRM:QuestionResponse")) {
            String qualStr = "'301368700' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SRM:Associations")) {
            String qualStr = "'302774000' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SRM:AppInstanceFlow")) {
            String qualStr = "'301373600' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SRM:AppInstanceBridge")) {
            String qualStr = "'301368700' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SLM:MilestoneLogging") || childSchema.equals("SLM:Measurement")) {
            String qualStr = "'490009000' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("SLM:EventSchedule")) {
            String qualStr = "'400030500' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("APR:Non-ApprovalNotifications")) {
            String qualStr = "'300717400' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("HPD:Help Desk") && isTransactionalDelete) {
            String qualStr = "'301368700' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("WOI:WorkOrder") && isTransactionalDelete) {
            String qualStr = "'301368700' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else if (childSchema.equals("CHG:Infrastructure Change") && isTransactionalDelete) {
            String qualStr = "'301368700' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        } else { // Dynamic AIF Schema
            String qualStr = "'301368700' = \"" + entry.get(179) + "\"";
            delChildRecord(childSchema, qualStr);
        }
    }

    private static void delChildRecord(String childSchema, String qualStr) {
        try {
            QualifierInfo qual = ctx.parseQualification(childSchema, qualStr);
            List<EntryListInfo> entriesToDelete = ctx.getListEntry(childSchema, qual, 0, 999999999, null, null, false, null);
            for (EntryListInfo entryListInfo : entriesToDelete) {
                System.out.println("   Deleting entry " + entryListInfo.getEntryID() + " from: " + childSchema);
                ctx.deleteEntry(childSchema, entryListInfo.getEntryID(), 0);
            }
        } catch (ARException e) {
            System.out.println(e.getMessage());
        }
    }
}

I hope this is useful to anybody in a similar situation where the need to delete Service Requests arise.  You can download the utility and code below and as always, happy coding 🙂

Version 2.0

  • Added new arguments for specifying to optionally transactional data and aif data related to the service requests being deleted. The options are -trans and -aif.
  • Added 2.5 second delay after making overlay change to SRM Delete filter to allow slower systems to catch up and another 2.5 seconds after saving it in disabled state

Version 1.1

  • Added the method for only attempting to delete from froms that actually exists on the target server to reduce “form does not exist” error noise

Version 1.0

  • Initial release

Service Request Purge

Icon

Service Request Purge - Source 15.39 KB 319 downloads

...
Icon

Service Request Purge - Utility 7.34 MB 326 downloads

...

    3 thoughts on “Service Request Purging – Easily delete Service Requests

    Leave a Reply to Curtis Gallant Cancel reply

    Your email address will not be published. Required fields are marked *