1
\$\begingroup\$

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.

asked Jan 8, 2022 at 0:56
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

when ICACLS is run in windows, the first line it returns includes the directory as well as the first permission

That's because your parsing algorithm is incorrect. About the only way to parse this output is to look at the second line to see how far indented it is.

Otherwise:

  • DON'T SHOUT IN YOUR COMMENTS; THIS IS ESPECIALLY IMPORTANT FOR PIP WHERE IT WILL NOT WORK ON CASE-SENSITIVE OPERATING SYSTEMS UNLESS IT'S LOWER-CASE
  • Don't store firstDir as a global
  • CheckPerms should be check_perms by PEP8
  • Divide this out into more subroutines
  • Prefer pathlib over os where possible
  • Your firstDir[1] index is strange. I assume this is just the stem of the directory.
  • Put your xlsx file path into a variable for reuse.
  • You have inconsistent worksheet indexing, mixing A1 with row/column. Prefer the latter.
  • Do not use single-letter variable names like r, d, f
  • Don't decode yourself; pass encoding to subprocess functions
  • Don't call workbook.close; put it in a with
  • Don't open Excel just for the purpose of column auto-sizing; keep track of the longest string and use that as the column width
  • Add a __main__ guard
  • Never bare except:
  • Strongly consider parsing the result of icacls /T which includes multiple files and will be more efficient. I have not shown this below.

Suggested

import os
import subprocess
from typing import Iterator
import xlsxwriter
from pathlib import Path
# must install xlsxwriter in venv
# pip install xlsxwriter
def parse_icacls(dir_path: str) -> Iterator[str]:
 """
 Example output:
 icacls some_long_file
 some_long_file NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
 BUILTIN\Administrators:(I)(OI)(CI)(F)
 LAPTOP\me:(I)(OI)(CI)(F)
 Successfully processed 1 files; Failed processing 0 files
 """
 sub_return = subprocess.check_output(('icacls', dir_path), encoding='utf-8')
 lines = sub_return.splitlines()
 lead = len(lines[1]) - len(lines[1].lstrip())
 for line in lines:
 if not line:
 break
 yield line[lead:]
def write_workbook(full_root_path: Path, saved_to: Path) -> None:
 with xlsxwriter.Workbook(saved_to) as workbook:
 worksheet = workbook.add_worksheet()
 headers = ('Directory Path', 'Security Groups')
 row = 0
 for col, header in enumerate(headers):
 worksheet.write(row, col, header)
 row += 1
 longest_dir = len(headers[0])
 longest_group = len(headers[1])
 for dir_path, dir_names, file_names in os.walk(full_root_path):
 try:
 groups = parse_icacls(dir_path)
 except UnicodeDecodeError:
 print('Decode Error: Skipping a line')
 continue
 col = 0
 worksheet.write(row, col, dir_path)
 longest_dir = max(longest_dir, len(dir_path))
 col += 1
 for group in groups:
 worksheet.write(row, col, group)
 longest_group = max(longest_group, len(group))
 row += 1
 row += 1
 worksheet.set_column(first_col=0, last_col=0, width=longest_dir)
 worksheet.set_column(first_col=1, last_col=1, width=longest_group)
def check_perms(top: Path, save_dir: Path) -> None:
 save_dir.mkdir(exist_ok=True)
 for full_root_path in top.iterdir():
 if full_root_path.is_dir():
 saved_to = (save_dir / full_root_path.stem).with_suffix('.xlsx')
 print(f'Working on: {full_root_path}; saving to {saved_to}')
 write_workbook(full_root_path, saved_to)
if __name__ == '__main__':
 check_perms(Path('.'), Path('Test'))
answered Jan 11, 2022 at 18:26
\$\endgroup\$
1
  • 1
    \$\begingroup\$ It runs a lot faster, I thank you for your tips. I am trying to learn more and this helps a ton. Much appreciated. \$\endgroup\$ Commented Jan 14, 2022 at 2:42

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.