Search code examples
pythonwindowssystem-administration

Python Scan for ICACL


I am working to solve an issue that my company is currently having, We have an extremely large share drive that is over 20 years old. There are so many security groups attached to some of these folder as well as individual users that no longer exist and appear as scrambled text.

We would like to scan through the entire directory and output all of the permissions to an excel file.

I have come up with this solution, however, I don't believe I have done it as efficiently as I believe it can be.

Here is the entire script:

import os
import subprocess
import xlsxwriter
import win32com.client as win32

# *** IMPORTANT ****
# MUST INSTALL XLSXWRITER AND PYWIN32 IN VENV
# PIP INSTALL XLSXWRITER
# PIP INSTALL PYWIN32


def CheckPerms(t, save_dir):

    global firstDir

    # LOOP THROUGH ALL PATHS IN ROOT DIRECTORY
    for path in os.listdir(t):

        # VARIABLE HOLDS FULL PATH OF THE ROOT DIRECTORY
        full_root_path = os.path.join(t, path)

        # CHECKS IF PATH IS A FILE, IF IT IS A FILE, SKIP AND CONTINUE WITH SCAN
        if not os.path.isfile(full_root_path):

            # VARIABLE HOLDS ROOT NAME OF FULL ROOT PATH
            # EXAMPLE: INPUT - C:\TEST, RETURNS - C:\
            firstDir = os.path.split(full_root_path)

            # PRINTS CURRENT DIRECTORY THAT IS BEING SCANNED
            print("Working on: " + firstDir[1])

            # PREPARE EXCEL WORKBOOK IN SPECIFIED LOCATION
            # SAVES AS NAME OF DIR BEING SCANNED
            workbook = xlsxwriter.Workbook(f'{save_dir}\\{firstDir[1]}.xlsx')
            worksheet = workbook.add_worksheet()
            worksheet.write("A1", "Directory Path")
            worksheet.write("B1", "Security Groups")
            row = 1
            col = 0

            # LOOP THOUGH A WALK OF EACH ROOT PATH
            # THIS WILL SCAN FRONT TO BACK, TOP TO BOTTOM OF THE ENTIRE TREE
            for r, d, f in os.walk(full_root_path):

                # CAPTURE THE OUTPUT OF THE SYSTEM CMD ICALCS COMMAND ON DIRECTORY BEING SCANNED
                sub_return = subprocess.check_output(["icacls", r])
                try:
                    # TRY TO DECODE OUTPUT AS UTF-8
                    sub_return = sub_return.decode('utf-8')
                except:
                    # SOMETIMES CANNOT DECODE, THIS WILL CATCH ERROR AND CONTINUE
                    print("Decode Error: Skipping a line")
                    continue

                # SPLIT THE LINES OF THE RETURNED STRING
                split_icacl_lines = sub_return.splitlines()

                # ICACLS RETURNS A STATUS LINE AFTER COMPLETE
                # THIS WILL REMOVE THE LAST LINE, EXAMPLE:
                # "Successfully processed 1 files; Failed processing 0 files"
                del split_icacl_lines[-1:]

                # FIRST LINE OF ICACLS INCLUDES THE DIRECTORY AS WELL AS FIRST LINE OF ICACLS
                # THIS WILL REVERSE SPLIT BY FIRST EMPTY SPACE TO SEPARATE THE LINES
                # AND DELETE IT FROM THE ORIGINAL LIST
                firstLine = split_icacl_lines[0].rsplit(" ", 1)
                del split_icacl_lines[0]

                # THERE HAPPENS TO BE AN EMPTY LINE, SO WE REMOVE IT HERE
                del split_icacl_lines[-1:]

                # ADD THE FIRST ELEMENT OF THE FIRST LINE BACK INTO THE BEGINNING OF THE LIST
                split_icacl_lines.insert(0, firstLine[0].lstrip())
                # APPEND THE SECOND ELEMENT OF THE FIRST LINE TO END OF LIST
                split_icacl_lines.append(firstLine[1].lstrip())

                # FIRST ELEMENT OF LIST IS THE TARGET DIRECTORY
                target_directory = split_icacl_lines[0]

                # DELETE TARGET DIRECTORY FROM LIST
                del split_icacl_lines[0]

                # ADD TARGET DIRECTORY TO EXCEL FILE
                worksheet.write(row, col, target_directory)
                # MOVE OVER EXCEL COLUMN BY 1
                col += 1

                # LOOP THROUGH EACH LINE IN THE FINAL ICACL DATA AND
                # OUTPUT IT TO EXCEL FILE NEXT TO THE DIRECTORY IT
                # BELONGS TO
                for lines in split_icacl_lines:
                    # STRIP LINES OF ALL ABNORMAL CHARACTERS
                    output = lines.lstrip()
                    # INSERT LINE INTO WORKBOOK
                    worksheet.write(row, col, output)
                    row += 1
                # EMPTY LINE BETWEEN EACH SCAN OUTPUT
                row += 1
                # RESET COLUMN TO 0
                col = 0
            # CLOSE WORKBOOK, SAVING IT
            workbook.close()

        # OPEN WORKBOOK IN WIN32, AUTO-FIT EACH COLUMN AND SAVE IT
        excel = win32.gencache.EnsureDispatch('Excel.Application')
        wb = excel.Workbooks.Open(f'C:\\Test\\{firstDir[1]}.xlsx')
        ws = wb.Worksheets("Sheet1")
        ws.Columns.AutoFit()
        wb.Save()
        excel.Application.Quit()

        # REPEAT ALL ABOVE FOR EACH DIRECTORY


CheckPerms("C:\\", "C:\\Test")

The problem I have, is when ICACLS is run in windows, the first line it returns includes the directory as well as the first permission, example:

C:\Users\Michael>icacls C:\\
C:\\ BUILTIN\Administrators:(OI)(CI)(F)
     NT AUTHORITY\SYSTEM:(OI)(CI)(F)
     BUILTIN\Users:(OI)(CI)(RX)
     NT AUTHORITY\Authenticated Users:(OI)(CI)(IO)(M)
     NT AUTHORITY\Authenticated Users:(AD)
     Mandatory Label\High Mandatory Level:(OI)(NP)(IO)(NW)

This is why I had to do some weird stuff with the output after I split it into a list.

After splitting the original output, I split the first line of that output by an empty space and added each element back into the list, however, for some files such as in the directory "Program Files" I get strange outputs like this:

Image of excel output file

Can anyone suggest a better way to do this?

It would be very much appreciated.


Solution

  • Too long for a comment. Eliminate all the complicated manipulation with the first line (see the ## comments in the following code snippet) and try if it works.

    Mainly see the following lines:

    • split_icacl_lines[0] = split_icacl_lines[0].replace( r, '', 1)
    • target_directory = r

    The code:

            # LOOP THOUGH A WALK OF EACH ROOT PATH
            # THIS WILL SCAN FRONT TO BACK, TOP TO BOTTOM OF THE ENTIRE TREE
            for r, d, f in os.walk(full_root_path):
    
                # CAPTURE THE OUTPUT OF THE SYSTEM CMD ICALCS COMMAND ON DIRECTORY BEING SCANNED
                sub_return = subprocess.check_output(["icacls", r])
                try:
                    # TRY TO DECODE OUTPUT AS UTF-8
                    sub_return = sub_return.decode('utf-8')
                except:
                    # SOMETIMES CANNOT DECODE, THIS WILL CATCH ERROR AND CONTINUE
                    print("Decode Error: Skipping a line")
                    continue
    
                # SPLIT THE LINES OF THE RETURNED STRING
                split_icacl_lines = sub_return.splitlines()
    
                # ICACLS RETURNS A STATUS LINE AFTER COMPLETE
                # THIS WILL REMOVE THE LAST LINE, EXAMPLE:
                # "Successfully processed 1 files; Failed processing 0 files"
                del split_icacl_lines[-1:]
    
                # FIRST LINE OF ICACLS INCLUDES THE DIRECTORY AS WELL AS FIRST LINE OF ICACLS
                # THIS WILL REVERSE SPLIT BY FIRST EMPTY SPACE TO SEPARATE THE LINES
                # AND DELETE IT FROM THE ORIGINAL LIST
                ## firstLine = split_icacl_lines[0].rsplit(" ", 1)
                ## del split_icacl_lines[0]
                split_icacl_lines[0] = split_icacl_lines[0].replace( r, '', 1)
    
                # THERE HAPPENS TO BE AN EMPTY LINE, SO WE REMOVE IT HERE
                del split_icacl_lines[-1:]
    
                # ADD THE FIRST ELEMENT OF THE FIRST LINE BACK INTO THE BEGINNING OF THE LIST
                ## split_icacl_lines.insert(0, firstLine[0].lstrip())
                # APPEND THE SECOND ELEMENT OF THE FIRST LINE TO END OF LIST
                ## split_icacl_lines.append(firstLine[1].lstrip())
    
                # FIRST ELEMENT OF LIST IS THE TARGET DIRECTORY
                ## target_directory = split_icacl_lines[0]
                target_directory = r
    
                # DELETE TARGET DIRECTORY FROM LIST
                ## del split_icacl_lines[0]
    
                # ADD TARGET DIRECTORY TO EXCEL FILE
                worksheet.write(row, col, target_directory)
                # MOVE OVER EXCEL COLUMN BY 1
                col += 1
    
                # LOOP THROUGH EACH LINE IN THE FINAL ICACL DATA AND
                # OUTPUT IT TO EXCEL FILE NEXT TO THE DIRECTORY IT
                # BELONGS TO
                for lines in split_icacl_lines:
                    # STRIP LINES OF ALL ABNORMAL CHARACTERS
                    output = lines.lstrip()
                    # INSERT LINE INTO WORKBOOK
                    worksheet.write(row, col, output)
                    row += 1
                # EMPTY LINE BETWEEN EACH SCAN OUTPUT
                row += 1
                # RESET COLUMN TO 0
                col = 0