├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── bgiparser.py └── bgiparser_foundation.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.md text eol=lf 4 | .git* text eol=lf 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ._* 2 | .DS_Store 3 | .history/ 4 | .vscode/ 5 | ccl_bplist.py 6 | __pycache__/ 7 | sample/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bgiparser (backgrounditems.btm/BackgroundItems-v*.btm parser) 2 | 3 | The entries of "Login Items" are stored in "~/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm" since macOS 10.13 High Sierra. This tool can parse it and output pairs of entry name and application path as JSON data. 4 | 5 | Apple has changed the location of the file that records Login Items in macOS 13 Ventura and the format has also been changed. This script supports the new format too. 6 | New file location is "/private/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v*.btm". 7 | 8 | By the way, you can also download another parsing tool from [here](https://github.com/objective-see/DumpBTM). Or just run `sfltool dumpbtm` on macOS 13. 9 | 10 | ## Requirement 11 | 12 | - Python 3.5 or later 13 | - [nska_deserialize](https://github.com/ydkhatri/nska_deserialize) 1.3.2 or later 14 | 15 | ## Usage 16 | 17 | ```bash 18 | $ python3 ./bgiparser.py -h 19 | usage: bgiparser.py [-h] [-f FILE] [-o OUT] [-c] [--force] [--debug] 20 | 21 | Parses backgrounditems.btm file. 22 | 23 | optional arguments: 24 | -h, --help show this help message and exit 25 | -f FILE, --file FILE Path to backgrounditems.btm (default: User's file that 26 | runs this script.) 27 | -o OUT, --out OUT Path to output filename 28 | -c, --console Output a result to console 29 | --force Enable to overwrite existing data. 30 | --debug Enable debug mode. 31 | ``` 32 | 33 | ## Installation 34 | 35 | ```bash 36 | $ git clone https://github.com/mnrkbys/bgiparser.git 37 | $ pip install nska_deserialize 38 | ``` 39 | 40 | ## Execution example 1 41 | 42 | ```bash 43 | $ python3 ./bgiparser.py -f ./sample/sample_mymac/backgrounditems.btm -c 44 | [ 45 | { 46 | "name": "duet.app", 47 | "path": "/Applications/duet.app" 48 | }, 49 | { 50 | "name": "", 51 | "path": "/Applications/Display Menu.app/Contents/Library/LoginItems/Display Menu Helper.app" 52 | }, 53 | { 54 | "name": "", 55 | "path": "/Applications/KeepingYouAwake.app/Contents/Library/LoginItems/KeepingYouAwake Launcher.app" 56 | }, 57 | { 58 | "name": "", 59 | "path": "/Applications/⌘英かな.app/Contents/Library/LoginItems/cmd-eikana-helper.app" 60 | }, 61 | { 62 | "name": "", 63 | "path": "/Applications/Wallcat.app/Contents/Library/LoginItems/StartAtLoginHelperApp.app" 64 | }, 65 | { 66 | "name": "V-CUBE ミーティング 5.app", 67 | "path": "/Applications/vrms5lite-helper.app" 68 | }, 69 | { 70 | "name": "", 71 | "path": "/Applications/OneDrive.app" 72 | }, 73 | { 74 | "name": "Box Sync", 75 | "path": "/Applications/Box Sync.app" 76 | }, 77 | { 78 | "name": "", 79 | "path": "/Applications/Evernote.app/Contents/Library/LoginItems/EvernoteHelper.app" 80 | }, 81 | { 82 | "name": "", 83 | "path": "/Applications/1Password 7.app/Contents/Library/LoginItems/1Password Launcher.app" 84 | }, 85 | { 86 | "name": "", 87 | "path": "/Applications/Day One.localized/Day One.app/Contents/Library/LoginItems/Day One Helper.app" 88 | } 89 | ] 90 | ``` 91 | 92 | ## Execution example 2 93 | 94 | ```bash 95 | % python ./bgiparser.py -c -f /private/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v8.btm 96 | { 97 | "D91E3B5A-648E-4747-92E7-F6F07E8EC600": [ 98 | { 99 | "uuid": "1975D13A-A95B-471A-A8AD-FE8BCCE818E0", 100 | "teamIdentifier": "", 101 | "disposition": 10, 102 | "generation": 1, 103 | "modificationDate": 0.0, 104 | "associatedBundleIdentifiers": "", 105 | "url": "", 106 | "bundleIdentifier": "", 107 | "type": 32, 108 | "identifier": "Objective-See, LLC", 109 | "executablePath": "", 110 | "container": "", 111 | "developerName": "Objective-See, LLC", 112 | "executableModificationDate": 0.0, 113 | "items": [ 114 | "com.objective-see.blockblock" 115 | ], 116 | "name": "Objective-See, LLC" 117 | }, 118 | { 119 | "uuid": "D6E56BFF-5FE6-451E-9FA0-498699654772", 120 | "teamIdentifier": "", 121 | "disposition": 10, 122 | "generation": 2, 123 | "modificationDate": 0.0, 124 | "associatedBundleIdentifiers": "", 125 | "url": "", 126 | "bundleIdentifier": "", 127 | "type": 32, 128 | "identifier": "Logitech Inc.", 129 | "executablePath": "", 130 | "container": "", 131 | "developerName": "Logitech Inc.", 132 | "executableModificationDate": 0.0, 133 | "items": [ 134 | "com.logi.optionsplus.updater" 135 | ], 136 | "name": "Logitech Inc." 137 | }, 138 | { 139 | "uuid": "3F33A859-4972-4CDE-8175-B93EDD106649", 140 | "teamIdentifier": "", 141 | "disposition": 10, 142 | "generation": 1, 143 | "modificationDate": 0.0, 144 | "associatedBundleIdentifiers": "", 145 | "url": "", 146 | ... 147 | ``` 148 | 149 | ## Limitation 150 | 151 | BackgroundItems-v*.btm only has user UUIDs, not user IDs or usernames. 152 | You can get the UUID corresponding to a username on localhost with the following command. 153 | In other words, if you parse BackgroundItems-v*.btm obtained from other host, you cannot reliably identify the username. 154 | ```bash 155 | % sudo dscl . read /Users/root generateduid 156 | dsAttrTypeNative:generateduid: FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000 157 | ``` 158 | 159 | ## TODO 160 | 161 | - [ ] Confirm condition of each entry (enable or disable) 162 | - [X] Support macOS 13 163 | 164 | ## Author 165 | 166 | [Minoru Kobayashi](https://twitter.com/unkn0wnbit) 167 | 168 | ## License 169 | 170 | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 171 | -------------------------------------------------------------------------------- /bgiparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # bgiparser.py 4 | # Parses backgrounditems.btm file to get items that runs when an user logs in. 5 | # 6 | # Copyright 2020-2023 Minoru Kobayashi (@unkn0wnbit) 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | import argparse 21 | import datetime 22 | import json 23 | import os 24 | import struct 25 | import sys 26 | 27 | import nska_deserialize as nd 28 | 29 | # global variables 30 | VERSION = '20240821' 31 | debug_mode = False 32 | 33 | 34 | # setup arguments 35 | def parse_arguments() -> argparse.Namespace: 36 | parser = argparse.ArgumentParser(description='Parses backgrounditems.btm file.') 37 | parser.add_argument('-f', '--file', action='store', 38 | default='{}/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm'.format(os.environ['HOME']), 39 | help='Path to backgrounditems.btm (default: User\'s file that runs this script.)') 40 | parser.add_argument('-o', '--out', action='store', default=None, 41 | help='Path to output filename') 42 | parser.add_argument('-c', '--console', action='store_true', default=False, 43 | help='Output a result to console') 44 | parser.add_argument('--force', action='store_true', default=False, 45 | help='Enable to overwrite existing data.') 46 | parser.add_argument('--debug', action='store_true', default=False, help='Enable debug mode.') 47 | parser.add_argument('-v', '--version', action='version', version=f"%(prog)s {VERSION}", help='Show version.') 48 | return parser.parse_args() 49 | 50 | 51 | def dbg_print(msg: str) -> None: 52 | if debug_mode: 53 | print(msg) 54 | 55 | 56 | def convert_cfabsolute_time(abs_time: float) -> datetime.datetime: 57 | try: 58 | return datetime.datetime(2001, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(seconds=abs_time) 59 | except (ValueError, OverflowError): 60 | return datetime.datetime(2001, 1, 1, tzinfo=datetime.timezone.utc) 61 | 62 | 63 | def parse_bookmark_data(data: bytes) -> dict[str, str] | bool: 64 | # Parses data according to the url below: 65 | # https://michaellynn.github.io/2015/10/24/apples-bookmarkdata-exposed/ 66 | 67 | name = '' 68 | path = '' 69 | 70 | # BookmarkData Header 71 | magic, data_length, version, data_offset = struct.unpack_from('<4sIII', data, 0) 72 | dbg_print('-' * 50) 73 | 74 | if magic != b'book': 75 | dbg_print('[Error] magic is not \'book\': {}'.format(magic.decode('utf-8'))) 76 | return False 77 | 78 | dbg_print('Magic: {}'.format(magic.decode('utf-8'))) 79 | dbg_print(f'Data Length: {hex(data_length)}') 80 | dbg_print(f'Version: {hex(version)}') 81 | dbg_print(f'Data Offset: {hex(data_offset)}') 82 | 83 | # BookmarkData Data 84 | toc_offset = struct.unpack_from(' 0: 144 | toc_offset = next_toc_offset 145 | elif name or path: 146 | return {'name': name, 'path': path} 147 | else: 148 | return False 149 | 150 | 151 | def parse_btm(btm_path: str) -> list | dict: 152 | with open(btm_path, 'rb') as f: 153 | plist = nd.deserialize_plist(f) 154 | dbg_print(f"Opened {btm_path}") 155 | 156 | # >= macOS 10.13 and <= macOS 12 157 | if isinstance(plist, dict) and plist['version'] == 2: 158 | dbg_print("Detected version: {}".format(plist['version'])) 159 | entries = [] 160 | login_item_entries = plist.get('backgroundItems', {}).get('allContainers', {}) 161 | for item_num in list(range(len(login_item_entries))): 162 | bm_data = login_item_entries[item_num]['internalItems'][0]['bookmark']['data'] 163 | login_item = {} 164 | if isinstance(bm_data, bytes): 165 | login_item = parse_bookmark_data(bm_data) 166 | elif isinstance(bm_data, dict): 167 | login_item = parse_bookmark_data(bm_data['NS.data']) 168 | 169 | if login_item: 170 | entries.append(login_item) 171 | 172 | # >= macOS 13 173 | elif isinstance(plist, list) and plist[0].get('version', 0) >= 3: 174 | dbg_print("Detected version: {}".format(plist[0]['version'])) 175 | entries = {} 176 | for uuid in plist[1]['store']['itemsByUserIdentifier'].keys(): 177 | dbg_print("=" * 50) 178 | dbg_print(f"User UUID: {uuid}") 179 | dbg_print("=" * 50) 180 | login_item = {uuid: [{}]} 181 | for item_number in list(range(len(plist[1]['store']['itemsByUserIdentifier'][uuid]))): 182 | dbg_print("-" * 20 + f" Item: {item_number} " + "-" * 20) 183 | item = {} 184 | for k, v in plist[1]['store']['itemsByUserIdentifier'][uuid][item_number].items(): 185 | if k not in ('lightweightRequirement', 'bookmark'): # These keys contain bytes data. 186 | if k in ('type', ): 187 | dbg_print(f"{k}: 0x{v:X}") 188 | elif k in ('modificationDate', 'executableModificationDate'): 189 | dbg_print(f"{k}: {v} ({convert_cfabsolute_time(v).strftime('%Y-%m-%d %H:%M:%S.%f')})") 190 | else: 191 | dbg_print(f"{k}: {v}") 192 | 193 | if k in ('sha256',) and isinstance(v, bytes): 194 | v = v.hex() 195 | item.update({k: v}) 196 | 197 | login_item[uuid].append(item) 198 | 199 | if login_item[uuid]: 200 | entries.update(login_item) 201 | 202 | else: 203 | sys.exit(f'Unsupported btm file: {btm_path}') 204 | 205 | return entries 206 | 207 | 208 | # main 209 | def main() -> None: 210 | global debug_mode 211 | login_items = [] 212 | out_file = None 213 | 214 | args = parse_arguments() 215 | debug_mode = args.debug 216 | 217 | btm_file = os.path.abspath(args.file) 218 | if not (os.path.exists(btm_file) or os.path.isfile(btm_file)): 219 | sys.exit(f'btm file is not found or is not a file: {btm_file}') 220 | 221 | if args.out: 222 | out_file = os.path.abspath(args.out) 223 | if os.path.exists(out_file) and not args.force: 224 | sys.exit(f'output file already exists: {out_file}') 225 | 226 | login_items = parse_btm(btm_file) 227 | 228 | if args.console or debug_mode: 229 | print(json.dumps(login_items, ensure_ascii=False, indent=4)) 230 | 231 | if out_file: 232 | try: 233 | with open(out_file, "w") as out_fp: 234 | json.dump(login_items, out_fp, ensure_ascii=False, indent=4) 235 | except OSError: 236 | sys.exit(1) 237 | 238 | 239 | if __name__ == "__main__": 240 | if sys.version_info[0:2] < (3, 10): 241 | sys.exit("This script needs greater than or equal to Python 3.5") 242 | 243 | main() 244 | -------------------------------------------------------------------------------- /bgiparser_foundation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # bgiparser_foundation.py 4 | # Parses backgrounditems.btm file to get items that runs when an user logs in. 5 | # 6 | # Copyright 2020 Minoru Kobayashi (@unkn0wnbit) 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | 21 | import os 22 | import sys 23 | import argparse 24 | import json 25 | 26 | try: 27 | import Foundation 28 | except ImportError: 29 | sys.exit('Import Error: PyObjC is not installed.\nGet it from https://bitbucket.org/ronaldoussoren/pyobjc or from pip.') 30 | 31 | 32 | # setup arguments 33 | def parse_arguments(): 34 | parser = argparse.ArgumentParser(description='Parses backgrounditems.btm file.') 35 | parser.add_argument('-f', '--file', action='store', 36 | default='{}/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm'.format(os.environ['HOME']), 37 | help='Path to backgrounditems.btm (default: User\'s file that runs this script.)') 38 | parser.add_argument('-o', '--out', action='store', default=None, 39 | help='Path to output filename') 40 | parser.add_argument('-c', '--console', action='store_true', default=False, 41 | help='Output a result to console') 42 | parser.add_argument('--force', action='store_true', default=False, 43 | help='Enable to overwrite existing data.') 44 | parser.add_argument('--debug', action='store_true', default=False, help='Enable debug mode.') 45 | args = parser.parse_args() 46 | 47 | return args 48 | 49 | 50 | # main 51 | def main(): 52 | login_items = list() 53 | 54 | args = parse_arguments() 55 | 56 | btm_file = os.path.abspath(args.file) 57 | if not (os.path.exists(btm_file) or os.path.isfile(btm_file)): 58 | sys.exit('btm file is not found or is not a file: {}'.format(btm_file)) 59 | 60 | if args.out: 61 | out_file = os.path.abspath(args.out) 62 | if os.path.exists(out_file) and not args.force: 63 | sys.exit('output file already exists: {}'.format(out_file)) 64 | 65 | data = Foundation.NSDictionary.dictionaryWithContentsOfFile_(btm_file) 66 | for obj in data['$objects']: 67 | properties = None 68 | 69 | if obj.isKindOfClass_(Foundation.NSClassFromString('NSData')): 70 | properties = Foundation.NSURL.resourceValuesForKeys_fromBookmarkData_(['NSURLBookmarkAllPropertiesKey'], obj) 71 | elif obj.isKindOfClass_(Foundation.NSClassFromString("NSDictionary")): 72 | if 'NS.data' in obj: 73 | properties = Foundation.NSURL.resourceValuesForKeys_fromBookmarkData_(['NSURLBookmarkAllPropertiesKey'], obj['NS.data']) 74 | 75 | if properties: 76 | name = properties['NSURLBookmarkAllPropertiesKey']['NSURLLocalizedNameKey'] 77 | path = properties['NSURLBookmarkAllPropertiesKey']['_NSURLPathKey'] 78 | login_items.append({'name': name, 'path': path}) 79 | 80 | if args.console or args.debug: 81 | print(json.dumps(login_items, indent=4)) 82 | 83 | if args.out: 84 | try: 85 | with open(out_file, 'wt') as out_fp: 86 | json.dump(login_items, out_fp, indent=4) 87 | except OSError as err: 88 | sys.exit(err) 89 | 90 | 91 | if __name__ == "__main__": 92 | if sys.version_info[0:2] < (3, 5): 93 | sys.exit("This script needs greater than or equal to Python 3.5") 94 | 95 | main() 96 | --------------------------------------------------------------------------------