raganmd
4/2/2018 - 6:49 PM

windows-utilities.py

# matthew ragan

import os
import subprocess

class WindowsUtilities:
    '''The WindowsUtilities class is used to easily access a number of utility functions for Windows 10.

    Robocopy
        Address the need to sync folders between machines or from one location to another.
        This method exits as a single Popen call allowing the user to use these in a for loop
        and effectivly start multiple simultanious copies frome one machine to many.

        Use cases:
            * syncing media or software directories to multiple machines
            * copying media or software from an external drive to a local drive
            * copying media or software from a local drive to an external drive

    Shutdown
        Addresses the need to shutdown or restart a machine. This could be used on a local
        or remote machine.

        Use cases:
            * Regular machine resets
            * Scheduled maintenace machine shutdown or restarts
            * Ability to shutdown or reboot a remote machine

    StartStopService
        Addresses the need to start or stop a windows service on a remote machine.

    RestartService
        Addreses the need to restart a windows service on a remote machine.
    '''
    def __init__(self):

        self.WindowsProjectFolder                   = project.folder
        self.ProjectLogs                            = "{}/logs".format( project.folder )
        self.ResetStopDelay                         = 500
        self.ResetStartDelay                        = 3000

        print("WindowsUtilities Init")

        return


    def Robocopy( self, targetUri, logName, sourceFolder, destinationFolder, xDir = '', xFile ='', debug=False ):
        '''Start a generic robocopy job on Windows to syncronize the contents of folders

        Notes
        ---------
        

        Args
        ---------
        targetUri (str) : the target URI is a string used to format the log file, you may choose to make this
            the machine name, or ip-address for a given target. This parameter is used to ensure you have 
            reliable and parsable logs for each copy event you start. 
        
        sourceFolder (directory) : this is the target source folder your are copying from. This can be located
            on a local machine, a network machine, or external drive. Paths are correctly converted to windows 
            os style formatting, so you're free to use "c:/someFolder/" as your format. This means you 
            can also use project.folder() if you so choose.
            
            Expected format:
            network path    - "\\server\c$\someFolder"
            local path      - "D:/001-source"
        
        destinationFolder (directory) : this is the destination folder you are copying to. You can use a network
            path to target remote machines. You may want to use the administrator share, formatted 
            as: "\\server\c$\someFolder". For both source and destination folder no additional special 
            formatting should be necessary.

            Expected format:
            network path    - "\\server\c$\someFolder"
            local path      - "D:/001-source"
        
        xDir (str) : the path to any directories you would like to exclude formated as a string. This should
            be relative to the source folder. 
        
        xFile (str) : the string name of a file with file extension that you wish to exclude from the copy
            process.
        
        logName (str) : this is the log type name that will be used to create a directory for your log files.
            This allows the reuse of this function to create logs for any number of copy processes.

        Returns
        ---------
        copyProcess (Popen Object) : this is the Popen object that's running the copy process. This can be
            used to check or kill the process if necessary.
        '''

        # checks for network file paths in source and destination paths
        if "\\" in sourceFolder[:1]:
            sourceFolder            = "\\" + sourceFolder

        else:
            pass   

        if "\\" in destinationFolder[:1]:
            destinationFolder       = "\\" + destinationFolder

        else:
            pass

        # assemble all of the necessary pieces for a copy
        windowsProjectFolder        = (self.WindowsProjectFolder).replace('/', '\\')    
        source                      = (sourceFolder).replace('/', '\\')
        dest                        = (destinationFolder).replace('/', '\\')
        exFile                      = (xFile).replace('/', '\\')
        exDir                       = (xDir).replace('/', '\\')
        logs                        = '{projectLogs}/robocopy/{name}/'.format( projectLogs = self.ProjectLogs, name = logName )
        logs                        = (logs).replace('/', '\\')
        logFile                     = '{log}{uri}.txt'.format(log = logs, uri = targetUri)
        log                         = '/LOG+:{logF}'.format(logF = logFile)
        mirFlag                     = '/MIR'
        mtFlag                      = '/MT:12'
        rFlag                       = '/R:0'
        wFlag                       = '/W:1'
        xfFlag                      = xFile
        xdFlag                      = xDir  


        # format the command list for Popen
        copyCommand                 = [ "ROBOCOPY",
                                        source, 
                                        dest, 
                                        log, 
                                        mirFlag,
                                        mtFlag,
                                        rFlag,
                                        wFlag,
                                        '/XF',
                                        xfFlag,
                                        '/XD',
                                        xdFlag]

        # create a directory for logs if none exists
        if not os.path.exists(logs):
            os.makedirs(logs)

        # delete old log files
        if os.path.isfile(logFile):
            os.remove(logFile)
        else:
            pass

        # if debugging, print out all the args passed to the command line
        if debug:
            print(copyCommand)
        else:
            pass

        # use subprocess to start a copy command
        copyProcess     = subprocess.Popen(copyCommand)

        return copyProcess

    def Shutdown(self):
        return

    def StartStopService(self, targetServerIp = None, serviceName = None, running=False, reset=False):
        '''Used start and stop services on remote machines.

        Notes
        ---------
        Used to issue start and stop commmands for monsvc on remote machines without running
        and external CMD or BAT file. Similar to the robocopy commands these issue system
        level commands via Windows. This will start or stop monsvc on available remote machines
        briging much of the monsvc functionality into single UI deployments. Be advsied that
        using this will stop / start all of the processes associated with a given monsvc config.

        As a note reset is a reserved argument for future devleopment and is not currently in use.

        # sudo code:
        call(['sc', 'server_target', 'stop', 'monsvc'])
        

        Args
        ---------
        location (str)  : a string entry that determins which server list is used to issue commands
        running (bool)  : a boolean that describes if the issued monsvc command should be start or stop
        reset (bool)    : a boolean that will first issue a stop command followed by a start command

        Returns
        ---------
        none
        '''
        machine_ip      = op.Project.fetch('local_config')['ip_address']
        svcCmd          = "start" if running else "stop"

        # error handling
        if targetServerIp == None:
            print("No Server Specified - Not issueing Service Control Commands")    
            pass

        else:
            # resetting the service will first issue stop commands to the service
            # followed by a start command 
            if reset:
                self.RestartService(targetServerIp, serviceName)

            # in the case that reset is False, just issue a stop or start command
            # to the target server, with the target service name
            else:
                cmdList     = [ "sc",
                                '\\\{ip}'.format(ip = server),
                                svcCmd,
                                serviceName]            
                
                subprocess.Popen(monsvc_command)
        

        return

    def RestartService(self, targetServerIp, serviceName):
        '''Used to stop and start touch on a single server.

        Notes
        ---------
        Used to issue start and stop commmands for monsvc on remote machines without running
        and external CMD or BAT file. Similar to the robocopy commands these issue system
        level commands via Windows. This will start or stop monsvc on available remote machines
        briging much of the monsvc functionality into single UI deployments.

        # sudo code:
        call(['sc', 'server_target', 'stop', 'monsvc'])
        
        Args
        ---------
        target_server (str)     : a string entry that determins which server list is used 
                                    to issue commands

        Returns
        ---------
        none
        '''
        stopCommand            = [ "sc",
                                    '\\\{ip}'.format(ip = targetServerIp),
                                    "stop",
                                    serviceName]
        startCommand           = [ "sc",
                                    '\\\{ip}'.format(ip = targetServerIp),
                                    "start",
                                    serviceName]
        delayScript            = '''
import subprocess
subprocess.Popen(args[0])'''

        run(delayScript, stopCommand, delayMilliSeconds = self.ResetStopDelay)
        run(delayScript, startCommand, delayMilliSeconds = self.ResetStartDelay)

        return