├── .gitignore ├── LICENSE ├── ProvisioningProfileCleaner.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── ProvisioningProfileCleaner.xccheckout │ └── xcuserdata │ │ └── kevinbradley.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── kevinbradley.xcuserdatad │ └── xcschemes │ ├── ProvisioningProfileCleaner.xcscheme │ ├── XCProfileCleaner.xcscheme │ └── xcschememanagement.plist ├── ProvisioningProfileCleaner ├── KBProfileHelper.h ├── KBProfileHelper.m ├── ProvisioningProfileCleaner-Prefix.pch ├── ProvisioningProfileCleaner.1 └── main.m ├── README.md └── XCProfileCleaner ├── Defines.h ├── JRSwizzle.h ├── JRSwizzle.m ├── NSData+Split.h ├── NSData+Split.m ├── XCPCModel.h ├── XCPCModel.m ├── XCPCProjectSetting.h ├── XCPCProjectSetting.m ├── XCPCWindowController.h ├── XCPCWindowController.m ├── XCPCWindowController.xib ├── XCProfileCleaner-Info.plist ├── XCProfileCleaner-Prefix.pch ├── XCProfileCleaner.h ├── XCProfileCleaner.m └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | # CocoaPods 2 | # 3 | # We recommend against adding the Pods directory to your .gitignore. However 4 | # you should judge for yourself, the pros and cons are mentioned at: 5 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 6 | # 7 | # Pods/ 8 | 9 | *.DS_Store* 10 | *DS_Store* 11 | .Trashes 12 | *.swp 13 | *.lock 14 | *~.nib 15 | 16 | *.pbxuser 17 | *.mode1v3 18 | *.mode2v3 19 | *.perspectivev3 20 | !default.pbxuser 21 | !default.mode1v3 22 | !default.mode2v3 23 | !default.perspectivev3 24 | *.moved-aside 25 | 26 | # Build products 27 | build/ 28 | *.o 29 | *.LinkFileList 30 | *.hmap 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Kevin Bradley 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 323932D31993CF9F00BD549E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 323932D21993CF9F00BD549E /* Foundation.framework */; }; 11 | 323932D61993CFA000BD549E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 323932D51993CFA000BD549E /* main.m */; }; 12 | 323932DA1993CFA000BD549E /* ProvisioningProfileCleaner.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 323932D91993CFA000BD549E /* ProvisioningProfileCleaner.1 */; }; 13 | 323932E21993CFC000BD549E /* KBProfileHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 323932E11993CFC000BD549E /* KBProfileHelper.m */; }; 14 | 323932E919940A6600BD549E /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 323932E819940A6600BD549E /* AppKit.framework */; }; 15 | 323932EA19940A6600BD549E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 323932D21993CF9F00BD549E /* Foundation.framework */; }; 16 | 323932F019940A6600BD549E /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 323932EE19940A6600BD549E /* InfoPlist.strings */; }; 17 | 323932F319940A6600BD549E /* XCProfileCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 323932F219940A6600BD549E /* XCProfileCleaner.m */; }; 18 | 323932F819940C4800BD549E /* KBProfileHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 323932E11993CFC000BD549E /* KBProfileHelper.m */; }; 19 | 327422D71995743B00527711 /* XCPCModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 327422D61995743B00527711 /* XCPCModel.m */; }; 20 | 327422DA1995747E00527711 /* XCPCProjectSetting.m in Sources */ = {isa = PBXBuildFile; fileRef = 327422D91995747E00527711 /* XCPCProjectSetting.m */; }; 21 | 327422DD1995751300527711 /* NSData+Split.m in Sources */ = {isa = PBXBuildFile; fileRef = 327422DC1995751300527711 /* NSData+Split.m */; }; 22 | 32D113D3199C0BE000D3F7F3 /* XCPCWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D113D1199C0BE000D3F7F3 /* XCPCWindowController.m */; }; 23 | 32D113D4199C0BE000D3F7F3 /* XCPCWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32D113D2199C0BE000D3F7F3 /* XCPCWindowController.xib */; }; 24 | 32FAA1D3199541120041ECDB /* JRSwizzle.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FAA1D2199541120041ECDB /* JRSwizzle.m */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXCopyFilesBuildPhase section */ 28 | 323932CD1993CF9F00BD549E /* CopyFiles */ = { 29 | isa = PBXCopyFilesBuildPhase; 30 | buildActionMask = 2147483647; 31 | dstPath = /usr/share/man/man1/; 32 | dstSubfolderSpec = 0; 33 | files = ( 34 | 323932DA1993CFA000BD549E /* ProvisioningProfileCleaner.1 in CopyFiles */, 35 | ); 36 | runOnlyForDeploymentPostprocessing = 1; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 3225FF1D199C0FA200F6DC98 /* Defines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Defines.h; sourceTree = ""; }; 42 | 323932CF1993CF9F00BD549E /* ProvisioningProfileCleaner */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ProvisioningProfileCleaner; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 323932D21993CF9F00BD549E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 44 | 323932D51993CFA000BD549E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 323932D81993CFA000BD549E /* ProvisioningProfileCleaner-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ProvisioningProfileCleaner-Prefix.pch"; sourceTree = ""; }; 46 | 323932D91993CFA000BD549E /* ProvisioningProfileCleaner.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = ProvisioningProfileCleaner.1; sourceTree = ""; }; 47 | 323932E01993CFC000BD549E /* KBProfileHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = KBProfileHelper.h; sourceTree = ""; }; 48 | 323932E11993CFC000BD549E /* KBProfileHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KBProfileHelper.m; sourceTree = ""; }; 49 | 323932E719940A6600BD549E /* XCProfileCleaner.xcplugin */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XCProfileCleaner.xcplugin; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 323932E819940A6600BD549E /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 51 | 323932ED19940A6600BD549E /* XCProfileCleaner-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "XCProfileCleaner-Info.plist"; sourceTree = ""; }; 52 | 323932EF19940A6600BD549E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 53 | 323932F119940A6600BD549E /* XCProfileCleaner.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = XCProfileCleaner.h; sourceTree = ""; }; 54 | 323932F219940A6600BD549E /* XCProfileCleaner.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = XCProfileCleaner.m; sourceTree = ""; }; 55 | 323932F419940A6600BD549E /* XCProfileCleaner-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "XCProfileCleaner-Prefix.pch"; sourceTree = ""; }; 56 | 327422D51995743B00527711 /* XCPCModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCPCModel.h; sourceTree = ""; }; 57 | 327422D61995743B00527711 /* XCPCModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCPCModel.m; sourceTree = ""; }; 58 | 327422D81995747E00527711 /* XCPCProjectSetting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCPCProjectSetting.h; sourceTree = ""; }; 59 | 327422D91995747E00527711 /* XCPCProjectSetting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCPCProjectSetting.m; sourceTree = ""; }; 60 | 327422DB1995751300527711 /* NSData+Split.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Split.h"; sourceTree = ""; }; 61 | 327422DC1995751300527711 /* NSData+Split.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Split.m"; sourceTree = ""; }; 62 | 32D113D0199C0BE000D3F7F3 /* XCPCWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XCPCWindowController.h; sourceTree = ""; }; 63 | 32D113D1199C0BE000D3F7F3 /* XCPCWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XCPCWindowController.m; sourceTree = ""; }; 64 | 32D113D2199C0BE000D3F7F3 /* XCPCWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = XCPCWindowController.xib; sourceTree = ""; }; 65 | 32FAA1D1199541120041ECDB /* JRSwizzle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = JRSwizzle.h; path = XCProfileCleaner/JRSwizzle.h; sourceTree = SOURCE_ROOT; }; 66 | 32FAA1D2199541120041ECDB /* JRSwizzle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = JRSwizzle.m; path = XCProfileCleaner/JRSwizzle.m; sourceTree = SOURCE_ROOT; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 323932CC1993CF9F00BD549E /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 323932D31993CF9F00BD549E /* Foundation.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 323932E419940A6600BD549E /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 323932E919940A6600BD549E /* AppKit.framework in Frameworks */, 83 | 323932EA19940A6600BD549E /* Foundation.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 323932C61993CF9F00BD549E = { 91 | isa = PBXGroup; 92 | children = ( 93 | 323932D41993CF9F00BD549E /* ProvisioningProfileCleaner */, 94 | 323932EB19940A6600BD549E /* XCProfileCleaner */, 95 | 323932D11993CF9F00BD549E /* Frameworks */, 96 | 323932D01993CF9F00BD549E /* Products */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 323932D01993CF9F00BD549E /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 323932CF1993CF9F00BD549E /* ProvisioningProfileCleaner */, 104 | 323932E719940A6600BD549E /* XCProfileCleaner.xcplugin */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 323932D11993CF9F00BD549E /* Frameworks */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 323932D21993CF9F00BD549E /* Foundation.framework */, 113 | 323932E819940A6600BD549E /* AppKit.framework */, 114 | ); 115 | name = Frameworks; 116 | sourceTree = ""; 117 | }; 118 | 323932D41993CF9F00BD549E /* ProvisioningProfileCleaner */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 323932D51993CFA000BD549E /* main.m */, 122 | 323932E01993CFC000BD549E /* KBProfileHelper.h */, 123 | 323932E11993CFC000BD549E /* KBProfileHelper.m */, 124 | 323932D91993CFA000BD549E /* ProvisioningProfileCleaner.1 */, 125 | 323932D71993CFA000BD549E /* Supporting Files */, 126 | ); 127 | path = ProvisioningProfileCleaner; 128 | sourceTree = ""; 129 | }; 130 | 323932D71993CFA000BD549E /* Supporting Files */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 323932D81993CFA000BD549E /* ProvisioningProfileCleaner-Prefix.pch */, 134 | ); 135 | name = "Supporting Files"; 136 | sourceTree = ""; 137 | }; 138 | 323932EB19940A6600BD549E /* XCProfileCleaner */ = { 139 | isa = PBXGroup; 140 | children = ( 141 | 32D113CF199C0BD300D3F7F3 /* Window Group */, 142 | 32952BD6199AD9530051914F /* Categories and Helpers */, 143 | 32952BD7199AD9670051914F /* Xcode Model */, 144 | 323932F119940A6600BD549E /* XCProfileCleaner.h */, 145 | 323932F219940A6600BD549E /* XCProfileCleaner.m */, 146 | 3225FF1D199C0FA200F6DC98 /* Defines.h */, 147 | 323932EC19940A6600BD549E /* Supporting Files */, 148 | ); 149 | path = XCProfileCleaner; 150 | sourceTree = ""; 151 | }; 152 | 323932EC19940A6600BD549E /* Supporting Files */ = { 153 | isa = PBXGroup; 154 | children = ( 155 | 323932ED19940A6600BD549E /* XCProfileCleaner-Info.plist */, 156 | 323932EE19940A6600BD549E /* InfoPlist.strings */, 157 | 323932F419940A6600BD549E /* XCProfileCleaner-Prefix.pch */, 158 | ); 159 | name = "Supporting Files"; 160 | sourceTree = ""; 161 | }; 162 | 32952BD6199AD9530051914F /* Categories and Helpers */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 327422DB1995751300527711 /* NSData+Split.h */, 166 | 327422DC1995751300527711 /* NSData+Split.m */, 167 | 32FAA1D1199541120041ECDB /* JRSwizzle.h */, 168 | 32FAA1D2199541120041ECDB /* JRSwizzle.m */, 169 | ); 170 | name = "Categories and Helpers"; 171 | sourceTree = ""; 172 | }; 173 | 32952BD7199AD9670051914F /* Xcode Model */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | 327422D81995747E00527711 /* XCPCProjectSetting.h */, 177 | 327422D91995747E00527711 /* XCPCProjectSetting.m */, 178 | 327422D51995743B00527711 /* XCPCModel.h */, 179 | 327422D61995743B00527711 /* XCPCModel.m */, 180 | ); 181 | name = "Xcode Model"; 182 | sourceTree = ""; 183 | }; 184 | 32D113CF199C0BD300D3F7F3 /* Window Group */ = { 185 | isa = PBXGroup; 186 | children = ( 187 | 32D113D0199C0BE000D3F7F3 /* XCPCWindowController.h */, 188 | 32D113D1199C0BE000D3F7F3 /* XCPCWindowController.m */, 189 | 32D113D2199C0BE000D3F7F3 /* XCPCWindowController.xib */, 190 | ); 191 | name = "Window Group"; 192 | sourceTree = ""; 193 | }; 194 | /* End PBXGroup section */ 195 | 196 | /* Begin PBXNativeTarget section */ 197 | 323932CE1993CF9F00BD549E /* ProvisioningProfileCleaner */ = { 198 | isa = PBXNativeTarget; 199 | buildConfigurationList = 323932DD1993CFA000BD549E /* Build configuration list for PBXNativeTarget "ProvisioningProfileCleaner" */; 200 | buildPhases = ( 201 | 323932CB1993CF9F00BD549E /* Sources */, 202 | 323932CC1993CF9F00BD549E /* Frameworks */, 203 | 323932CD1993CF9F00BD549E /* CopyFiles */, 204 | ); 205 | buildRules = ( 206 | ); 207 | dependencies = ( 208 | ); 209 | name = ProvisioningProfileCleaner; 210 | productName = ProvisioningProfileCleaner; 211 | productReference = 323932CF1993CF9F00BD549E /* ProvisioningProfileCleaner */; 212 | productType = "com.apple.product-type.tool"; 213 | }; 214 | 323932E619940A6600BD549E /* XCProfileCleaner */ = { 215 | isa = PBXNativeTarget; 216 | buildConfigurationList = 323932F719940A6600BD549E /* Build configuration list for PBXNativeTarget "XCProfileCleaner" */; 217 | buildPhases = ( 218 | 323932E319940A6600BD549E /* Sources */, 219 | 323932E419940A6600BD549E /* Frameworks */, 220 | 323932E519940A6600BD549E /* Resources */, 221 | ); 222 | buildRules = ( 223 | ); 224 | dependencies = ( 225 | ); 226 | name = XCProfileCleaner; 227 | productName = XCProfileCleaner; 228 | productReference = 323932E719940A6600BD549E /* XCProfileCleaner.xcplugin */; 229 | productType = "com.apple.product-type.bundle"; 230 | }; 231 | /* End PBXNativeTarget section */ 232 | 233 | /* Begin PBXProject section */ 234 | 323932C71993CF9F00BD549E /* Project object */ = { 235 | isa = PBXProject; 236 | attributes = { 237 | LastUpgradeCheck = 0510; 238 | ORGANIZATIONNAME = nito; 239 | TargetAttributes = { 240 | 323932CE1993CF9F00BD549E = { 241 | ProvisioningStyle = Manual; 242 | }; 243 | }; 244 | }; 245 | buildConfigurationList = 323932CA1993CF9F00BD549E /* Build configuration list for PBXProject "ProvisioningProfileCleaner" */; 246 | compatibilityVersion = "Xcode 3.2"; 247 | developmentRegion = English; 248 | hasScannedForEncodings = 0; 249 | knownRegions = ( 250 | English, 251 | en, 252 | ); 253 | mainGroup = 323932C61993CF9F00BD549E; 254 | productRefGroup = 323932D01993CF9F00BD549E /* Products */; 255 | projectDirPath = ""; 256 | projectRoot = ""; 257 | targets = ( 258 | 323932E619940A6600BD549E /* XCProfileCleaner */, 259 | 323932CE1993CF9F00BD549E /* ProvisioningProfileCleaner */, 260 | ); 261 | }; 262 | /* End PBXProject section */ 263 | 264 | /* Begin PBXResourcesBuildPhase section */ 265 | 323932E519940A6600BD549E /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | 323932F019940A6600BD549E /* InfoPlist.strings in Resources */, 270 | 32D113D4199C0BE000D3F7F3 /* XCPCWindowController.xib in Resources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXResourcesBuildPhase section */ 275 | 276 | /* Begin PBXSourcesBuildPhase section */ 277 | 323932CB1993CF9F00BD549E /* Sources */ = { 278 | isa = PBXSourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | 323932E21993CFC000BD549E /* KBProfileHelper.m in Sources */, 282 | 323932D61993CFA000BD549E /* main.m in Sources */, 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | 323932E319940A6600BD549E /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 323932F819940C4800BD549E /* KBProfileHelper.m in Sources */, 291 | 32D113D3199C0BE000D3F7F3 /* XCPCWindowController.m in Sources */, 292 | 327422DD1995751300527711 /* NSData+Split.m in Sources */, 293 | 327422DA1995747E00527711 /* XCPCProjectSetting.m in Sources */, 294 | 327422D71995743B00527711 /* XCPCModel.m in Sources */, 295 | 32FAA1D3199541120041ECDB /* JRSwizzle.m in Sources */, 296 | 323932F319940A6600BD549E /* XCProfileCleaner.m in Sources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | /* End PBXSourcesBuildPhase section */ 301 | 302 | /* Begin PBXVariantGroup section */ 303 | 323932EE19940A6600BD549E /* InfoPlist.strings */ = { 304 | isa = PBXVariantGroup; 305 | children = ( 306 | 323932EF19940A6600BD549E /* en */, 307 | ); 308 | name = InfoPlist.strings; 309 | sourceTree = ""; 310 | }; 311 | /* End PBXVariantGroup section */ 312 | 313 | /* Begin XCBuildConfiguration section */ 314 | 323932DB1993CFA000BD549E /* Debug */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ALWAYS_SEARCH_USER_PATHS = NO; 318 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 319 | CLANG_CXX_LIBRARY = "libc++"; 320 | CLANG_ENABLE_MODULES = YES; 321 | CLANG_ENABLE_OBJC_ARC = YES; 322 | CLANG_WARN_BOOL_CONVERSION = YES; 323 | CLANG_WARN_CONSTANT_CONVERSION = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_EMPTY_BODY = YES; 326 | CLANG_WARN_ENUM_CONVERSION = YES; 327 | CLANG_WARN_INT_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 330 | COPY_PHASE_STRIP = NO; 331 | GCC_C_LANGUAGE_STANDARD = gnu99; 332 | GCC_DYNAMIC_NO_PIC = NO; 333 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 334 | GCC_OPTIMIZATION_LEVEL = 0; 335 | GCC_PREPROCESSOR_DEFINITIONS = ( 336 | "DEBUG=1", 337 | "$(inherited)", 338 | ); 339 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | MACOSX_DEPLOYMENT_TARGET = 10.8; 347 | ONLY_ACTIVE_ARCH = YES; 348 | SDKROOT = macosx; 349 | }; 350 | name = Debug; 351 | }; 352 | 323932DC1993CFA000BD549E /* Release */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ALWAYS_SEARCH_USER_PATHS = NO; 356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 357 | CLANG_CXX_LIBRARY = "libc++"; 358 | CLANG_ENABLE_MODULES = YES; 359 | CLANG_ENABLE_OBJC_ARC = YES; 360 | CLANG_WARN_BOOL_CONVERSION = YES; 361 | CLANG_WARN_CONSTANT_CONVERSION = YES; 362 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 363 | CLANG_WARN_EMPTY_BODY = YES; 364 | CLANG_WARN_ENUM_CONVERSION = YES; 365 | CLANG_WARN_INT_CONVERSION = YES; 366 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 368 | COPY_PHASE_STRIP = YES; 369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 370 | ENABLE_NS_ASSERTIONS = NO; 371 | GCC_C_LANGUAGE_STANDARD = gnu99; 372 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 375 | GCC_WARN_UNDECLARED_SELECTOR = YES; 376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 377 | GCC_WARN_UNUSED_FUNCTION = YES; 378 | GCC_WARN_UNUSED_VARIABLE = YES; 379 | MACOSX_DEPLOYMENT_TARGET = 10.8; 380 | SDKROOT = macosx; 381 | }; 382 | name = Release; 383 | }; 384 | 323932DE1993CFA000BD549E /* Debug */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | CODE_SIGN_IDENTITY = "-"; 388 | CODE_SIGN_STYLE = Manual; 389 | DEVELOPMENT_TEAM = ""; 390 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 391 | GCC_PREFIX_HEADER = "ProvisioningProfileCleaner/ProvisioningProfileCleaner-Prefix.pch"; 392 | MACOSX_DEPLOYMENT_TARGET = 10.13; 393 | PRODUCT_NAME = "$(TARGET_NAME)"; 394 | PROVISIONING_PROFILE = "24F22887-39B6-42B7-87BD-59E4F6CE2994"; 395 | PROVISIONING_PROFILE_SPECIFIER = ""; 396 | }; 397 | name = Debug; 398 | }; 399 | 323932DF1993CFA000BD549E /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | CODE_SIGN_STYLE = Manual; 403 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 404 | GCC_PREFIX_HEADER = "ProvisioningProfileCleaner/ProvisioningProfileCleaner-Prefix.pch"; 405 | MACOSX_DEPLOYMENT_TARGET = 10.13; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | }; 408 | name = Release; 409 | }; 410 | 323932F519940A6600BD549E /* Debug */ = { 411 | isa = XCBuildConfiguration; 412 | buildSettings = { 413 | COMBINE_HIDPI_IMAGES = YES; 414 | DEPLOYMENT_LOCATION = YES; 415 | DSTROOT = "$(HOME)"; 416 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 417 | GCC_PREFIX_HEADER = "XCProfileCleaner/XCProfileCleaner-Prefix.pch"; 418 | GCC_PREPROCESSOR_DEFINITIONS = ( 419 | "DEBUG=1", 420 | "$(inherited)", 421 | ); 422 | INFOPLIST_FILE = "XCProfileCleaner/XCProfileCleaner-Info.plist"; 423 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 424 | PRODUCT_NAME = "$(TARGET_NAME)"; 425 | WRAPPER_EXTENSION = xcplugin; 426 | }; 427 | name = Debug; 428 | }; 429 | 323932F619940A6600BD549E /* Release */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | COMBINE_HIDPI_IMAGES = YES; 433 | DEPLOYMENT_LOCATION = YES; 434 | DSTROOT = "$(HOME)"; 435 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 436 | GCC_PREFIX_HEADER = "XCProfileCleaner/XCProfileCleaner-Prefix.pch"; 437 | INFOPLIST_FILE = "XCProfileCleaner/XCProfileCleaner-Info.plist"; 438 | INSTALL_PATH = "/Library/Application Support/Developer/Shared/Xcode/Plug-ins"; 439 | PRODUCT_NAME = "$(TARGET_NAME)"; 440 | WRAPPER_EXTENSION = xcplugin; 441 | }; 442 | name = Release; 443 | }; 444 | /* End XCBuildConfiguration section */ 445 | 446 | /* Begin XCConfigurationList section */ 447 | 323932CA1993CF9F00BD549E /* Build configuration list for PBXProject "ProvisioningProfileCleaner" */ = { 448 | isa = XCConfigurationList; 449 | buildConfigurations = ( 450 | 323932DB1993CFA000BD549E /* Debug */, 451 | 323932DC1993CFA000BD549E /* Release */, 452 | ); 453 | defaultConfigurationIsVisible = 0; 454 | defaultConfigurationName = Release; 455 | }; 456 | 323932DD1993CFA000BD549E /* Build configuration list for PBXNativeTarget "ProvisioningProfileCleaner" */ = { 457 | isa = XCConfigurationList; 458 | buildConfigurations = ( 459 | 323932DE1993CFA000BD549E /* Debug */, 460 | 323932DF1993CFA000BD549E /* Release */, 461 | ); 462 | defaultConfigurationIsVisible = 0; 463 | defaultConfigurationName = Release; 464 | }; 465 | 323932F719940A6600BD549E /* Build configuration list for PBXNativeTarget "XCProfileCleaner" */ = { 466 | isa = XCConfigurationList; 467 | buildConfigurations = ( 468 | 323932F519940A6600BD549E /* Debug */, 469 | 323932F619940A6600BD549E /* Release */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | /* End XCConfigurationList section */ 475 | }; 476 | rootObject = 323932C71993CF9F00BD549E /* Project object */; 477 | } 478 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner.xcodeproj/project.xcworkspace/xcshareddata/ProvisioningProfileCleaner.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 32B276B9-6623-4851-8806-BA6448964F48 9 | IDESourceControlProjectName 10 | project 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | C834FE6AFF26758DD0FA473A0E147D9493903C1F 14 | github.com:lechium/ProvisioningProfileCleaner.git 15 | 16 | IDESourceControlProjectPath 17 | ProvisioningProfileCleaner.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | C834FE6AFF26758DD0FA473A0E147D9493903C1F 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | github.com:lechium/ProvisioningProfileCleaner.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | C834FE6AFF26758DD0FA473A0E147D9493903C1F 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | C834FE6AFF26758DD0FA473A0E147D9493903C1F 36 | IDESourceControlWCCName 37 | ProvisioningProfileCleaner 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner.xcodeproj/project.xcworkspace/xcuserdata/kevinbradley.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lechium/ProvisioningProfileCleaner/0238d9a3d906c5a6dbdf8d9d75f75d9e9015fca4/ProvisioningProfileCleaner.xcodeproj/project.xcworkspace/xcuserdata/kevinbradley.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ProvisioningProfileCleaner.xcodeproj/xcuserdata/kevinbradley.xcuserdatad/xcschemes/ProvisioningProfileCleaner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner.xcodeproj/xcuserdata/kevinbradley.xcuserdatad/xcschemes/XCProfileCleaner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 42 | 43 | 44 | 45 | 51 | 52 | 54 | 55 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner.xcodeproj/xcuserdata/kevinbradley.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ProvisioningProfileCleaner.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | XCProfileCleaner.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 323932CE1993CF9F00BD549E 21 | 22 | primary 23 | 24 | 25 | 323932E619940A6600BD549E 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner/KBProfileHelper.h: -------------------------------------------------------------------------------- 1 | // 2 | // KBProfileHelper.h 3 | // ProvisioningProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 8/7/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface KBProfileHelper : NSObject 12 | { 13 | NSArray *invalidProfiles; 14 | NSArray *expiredProfiles; 15 | NSArray *duplicateProfiles; 16 | } 17 | - (int)processProfiles; 18 | - (int)processProfilesWithOpen:(BOOL)openBool; 19 | + (NSString *)mobileDeviceLog; 20 | + (NSMutableDictionary *)provisioningDictionaryFromFilePath:(NSString *)profilePath; 21 | + (NSArray *)devCertsFull; 22 | + (NSString *)provisioningProfilesPath; 23 | + (NSString *)pathFromUUID:(NSString *)uuid; 24 | + (NSString *)iphoneDeveloperString; 25 | + (NSArray *)validProfilesSlim; 26 | + (NSDictionary *)validProfileForID:(NSString *)appID withTarget:(NSString *)target; 27 | + (NSArray *)validProfilesForID:(NSString *)appID; 28 | @end 29 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner/KBProfileHelper.m: -------------------------------------------------------------------------------- 1 | // 2 | // KBProfileHelper.m 3 | // ProvisioningProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 8/7/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | #import "KBProfileHelper.h" 10 | 11 | #define MAN [NSFileManager defaultManager] 12 | 13 | #define DLog(format, ...) CFShow((__bridge CFStringRef)[NSString stringWithFormat:format, ## __VA_ARGS__]); 14 | 15 | //was used when different files were maintained for each log, easy way to clear out contents of mutable string 16 | 17 | @interface NSMutableString (profileHelper) 18 | 19 | - (void)clearString; 20 | 21 | @end 22 | 23 | @implementation NSMutableString (profileHelper) 24 | 25 | - (void)clearString { 26 | [self deleteCharactersInRange:NSMakeRange(0, self.length)]; 27 | } 28 | 29 | @end 30 | @interface NSString (profileHelper) 31 | - (id)dictionaryFromString; 32 | @end 33 | 34 | @implementation NSString (profileHelper) 35 | 36 | //convert basic XML plist string from the profile and convert it into a mutable nsdictionary 37 | 38 | - (id)dictionaryFromString { 39 | NSString *error = nil; 40 | NSPropertyListFormat format; 41 | NSData *theData = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES]; 42 | id theDict = [NSPropertyListSerialization propertyListFromData:theData 43 | mutabilityOption:NSPropertyListMutableContainersAndLeaves 44 | format:&format 45 | errorDescription:&error]; 46 | return theDict; 47 | } 48 | 49 | @end 50 | 51 | @interface NSArray (profileHelper) 52 | 53 | - (NSArray *)subarrayWithName:(NSString *)theName; 54 | 55 | @end 56 | 57 | @implementation NSArray (profileHelper) 58 | 59 | 60 | //filter subarray based on what contacts have that particular name sorted ascending by creation date. used to easily sort the top most object as far as date created when doing profile comparisons 61 | 62 | - (NSArray *)subarrayWithName:(NSString *)theName { 63 | NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"(Name == %@)", theName]; 64 | NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:@"CreationDate" ascending:TRUE]; 65 | NSArray *filteredArray = [self filteredArrayUsingPredicate:filterPredicate]; 66 | return [filteredArray sortedArrayUsingDescriptors:@[sortDesc]]; 67 | } 68 | 69 | @end 70 | 71 | 72 | @implementation KBProfileHelper 73 | 74 | //super small / lightweight easy replacement for using an NSTask to get data back out of a cli utilility 75 | 76 | + (NSArray *)returnForProcess:(NSString *)call { 77 | if (call==nil) 78 | return 0; 79 | char line[200]; 80 | 81 | FILE* fp = popen([call UTF8String], "r"); 82 | NSMutableArray *lines = [[NSMutableArray alloc]init]; 83 | if (fp) { 84 | while (fgets(line, sizeof line, fp)) { 85 | NSString *s = [NSString stringWithCString:line encoding:NSUTF8StringEncoding]; 86 | s = [s stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 87 | [lines addObject:s]; 88 | } 89 | } 90 | pclose(fp); 91 | return lines; 92 | } 93 | 94 | /* 95 | 96 | TeamName, 97 | ExpirationDate, 98 | TimeToLive, 99 | AppIDName, 100 | CreationDate, 101 | DeveloperCertificates, 102 | ProvisionedDevices, 103 | Name, 104 | ApplicationIdentifierPrefix, 105 | Version, 106 | UUID, 107 | TeamIdentifier, 108 | Entitlements { 109 | application-identifier, 110 | get-task-allow, 111 | } 112 | 113 | 114 | */ 115 | 116 | - (int)processProfiles { 117 | return [self processProfilesWithOpen:TRUE]; 118 | } 119 | 120 | //actually process the expiredProfiles / duplicateProfiles / invalid profiles here, these are set in validProfiles (kind of a hack, but it works) 121 | 122 | - (int)processProfilesWithOpen:(BOOL)openBool { 123 | //not really used for anything other than logging purposes, in the old version of this i would copy the valid profiles to a different folder. 124 | NSArray *validProfiles = [self validProfilePaths]; 125 | DLog(@"Valid Profiles: %@", validProfiles); 126 | NSMutableString *logString = [NSMutableString new]; 127 | //write all the logs to here 128 | NSString *ourLog = [KBProfileHelper mobileDeviceLog]; 129 | 130 | //process expired profiles 131 | 132 | if (expiredProfiles.count > 0) { 133 | // NSString *expiredLog = [[self expiredProvisioningProfiles] stringByAppendingPathComponent:@"Expired.log"]; 134 | [logString appendString:@"The following provisioning profiles have expired:\n---------------------------------------------------\n\n"]; 135 | for (NSDictionary *expiredDict in expiredProfiles) { 136 | NSString *fullpath = expiredDict[@"Path"]; 137 | NSString *baseName = [fullpath lastPathComponent]; 138 | NSString *newPath = [[self expiredProvisioningProfiles] stringByAppendingPathComponent:baseName]; 139 | [MAN moveItemAtPath:fullpath toPath:newPath error:nil]; 140 | NSString *teamName = expiredDict[@"TeamName"]; 141 | NSString *teamId = [expiredDict[@"TeamIdentifier"] lastObject]; 142 | NSDate *expireDate = expiredDict[@"ExpirationDate"]; 143 | NSString *name = expiredDict[@"Name"]; 144 | NSString *appIDName = expiredDict[@"AppIDName"]; 145 | NSString *appID = [expiredDict[@"Entitlements"] objectForKey:@"application-identifier"]; 146 | NSString *fileName = [expiredDict[@"Path"] lastPathComponent]; 147 | NSString *profileInfo = [NSString stringWithFormat:@"Profile %@ expired on %@\nwith team: %@ (%@) profile name: %@\nappID: %@ appIDName: %@\n\n", fileName, expireDate,teamName,teamId, name, appID, appIDName]; 148 | [logString appendString:profileInfo]; 149 | 150 | } 151 | [logString appendString:@"\n\n"]; 152 | [logString writeToFile:ourLog atomically:true encoding:NSUTF8StringEncoding error:nil]; 153 | // [logString clearString]; 154 | } 155 | 156 | //process duplicate profiles 157 | 158 | if (duplicateProfiles.count > 0) { 159 | // NSString *duplicateLog = [[self duplicateProvisioningProfiles] stringByAppendingPathComponent:@"Duplicates.log"]; 160 | [logString appendString:@"The following provisioning profiles have newer duplicates:\n---------------------------------------------------\n\n"]; 161 | for (NSDictionary *duplicateDict in duplicateProfiles) 162 | { 163 | NSString *fullpath = duplicateDict[@"Path"]; 164 | NSString *baseName = [fullpath lastPathComponent]; 165 | NSString *newPath = [[self duplicateProvisioningProfiles] stringByAppendingPathComponent:baseName]; 166 | [MAN moveItemAtPath:fullpath toPath:newPath error:nil]; 167 | NSString *teamName = duplicateDict[@"TeamName"]; 168 | NSString *teamId = [duplicateDict[@"TeamIdentifier"] lastObject]; 169 | NSDate *expireDate = duplicateDict[@"ExpirationDate"]; 170 | NSString *name = duplicateDict[@"Name"]; 171 | NSString *appIDName = duplicateDict[@"AppIDName"]; 172 | NSString *appID = [duplicateDict[@"Entitlements"] objectForKey:@"application-identifier"]; 173 | NSString *fileName = [duplicateDict[@"Path"] lastPathComponent]; 174 | NSString *profileInfo = [NSString stringWithFormat:@"Profile %@ has duplicates!\nit expires on %@ with team: %@ (%@)\nprofile name: %@ \nappID: %@ appIDName: %@\n\n", fileName, expireDate,teamName,teamId, name, appID, appIDName]; 175 | [logString appendString:profileInfo]; 176 | 177 | } 178 | [logString appendString:@"\n\n"]; 179 | [logString writeToFile:ourLog atomically:true encoding:NSUTF8StringEncoding error:nil]; 180 | //[logString clearString]; 181 | } 182 | 183 | //process invalid profiles 184 | 185 | if (invalidProfiles.count > 0) { 186 | // NSString *invalidLog = [[self invalidProvisioningProfiles] stringByAppendingPathComponent:@"Invalid.log"]; 187 | [logString appendString:@"The following provisioning profiles are invalid:\n---------------------------------------------------\n\n"]; 188 | for (NSDictionary *invalidDict in invalidProfiles) 189 | { 190 | NSString *fullpath = invalidDict[@"Path"]; 191 | NSString *baseName = [fullpath lastPathComponent]; 192 | NSString *newPath = [[self invalidProvisioningProfiles] stringByAppendingPathComponent:baseName]; 193 | [MAN moveItemAtPath:fullpath toPath:newPath error:nil]; 194 | NSString *teamName = invalidDict[@"TeamName"]; 195 | NSString *teamId = [invalidDict[@"TeamIdentifier"] lastObject]; 196 | NSDate *expireDate = invalidDict[@"ExpirationDate"]; 197 | NSString *name = invalidDict[@"Name"]; 198 | NSString *appIDName = invalidDict[@"AppIDName"]; 199 | NSString *appID = [invalidDict[@"Entitlements"] objectForKey:@"application-identifier"]; 200 | NSString *fileName = [invalidDict[@"Path"] lastPathComponent]; 201 | NSArray *validCerts = invalidDict[@"CodeSignArray"]; 202 | NSString *profileInfo = [NSString stringWithFormat:@"Profile %@ is invalid (there is no matching valid certificate!)\nit expires on %@ with team: %@ (%@) profile name: %@ \nappID: %@ appIDName: %@ missing expected certs: %@\n\n", fileName, expireDate,teamName,teamId, name, appID, appIDName, validCerts]; 203 | [logString appendString:profileInfo]; 204 | 205 | } 206 | [logString writeToFile:ourLog atomically:true encoding:NSUTF8StringEncoding error:nil]; 207 | } 208 | 209 | if (openBool == TRUE){ 210 | NSString *openPath = [NSString stringWithFormat:@"/usr/bin/open '%@'", [self provisioningProfilesPath]]; 211 | system([openPath UTF8String]); 212 | } 213 | return 0; 214 | } 215 | 216 | + (NSString *)iphoneDeveloperString { 217 | NSArray *devCertsArray = [self devCertsFull]; 218 | for (NSString *devCert in devCertsArray) { 219 | if ([devCert rangeOfString:@"iPhone Developer:"].location != NSNotFound) { 220 | return devCert; 221 | } 222 | } 223 | return nil; 224 | } 225 | 226 | + (NSArray *)devCertsFull { 227 | NSMutableArray *outputArray = [[NSMutableArray alloc ]init]; 228 | NSArray *securityReturn = [KBProfileHelper returnForProcess:@"security find-identity -p codesigning -v"]; 229 | for (NSString *profileLine in securityReturn) { 230 | if (profileLine.length > 0) { 231 | NSArray *clips = [profileLine componentsSeparatedByString:@"\""]; 232 | if ([clips count] > 1) { 233 | NSString *clipIt = [[profileLine componentsSeparatedByString:@"\""] objectAtIndex:1]; 234 | [outputArray addObject:clipIt]; 235 | } 236 | } 237 | } 238 | return outputArray; 239 | } 240 | 241 | - (NSArray *)devCerts { 242 | NSMutableArray *outputArray = [[NSMutableArray alloc ]init]; 243 | NSArray *securityReturn = [KBProfileHelper returnForProcess:@"security find-identity -p codesigning -v"]; 244 | for (NSString *profileLine in securityReturn) { 245 | if (profileLine.length > 0) { 246 | NSArray *clips = [profileLine componentsSeparatedByString:@"\""]; 247 | if ([clips count] > 1) { 248 | NSString *clipIt = [[profileLine componentsSeparatedByString:@"\""] objectAtIndex:1]; 249 | NSArray *certArray = [clipIt componentsSeparatedByString:@"("]; 250 | NSString *certID = [[certArray lastObject] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@") "]]; 251 | // DLog(@"certId: -%@-", certID); 252 | NSString *devName = [[certArray objectAtIndex:0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 253 | if (certID != nil && devName != nil){ 254 | // NSDictionary *certDict = @{@"certID": certID, @"devName": devName}; 255 | [outputArray addObject:certID]; 256 | // DLog(@"%@", clipIt); 257 | 258 | } 259 | } 260 | } 261 | } 262 | return outputArray; 263 | } 264 | 265 | - (NSString *)duplicateProvisioningProfiles { 266 | NSString *theDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/MobileDevice/Duplicate Profiles"]; 267 | if (![MAN fileExistsAtPath:theDir]) { 268 | [MAN createDirectoryAtPath:theDir withIntermediateDirectories:true attributes:nil error:nil]; 269 | } 270 | return theDir; 271 | } 272 | 273 | - (NSString *)expiredProvisioningProfiles { 274 | NSString *theDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/MobileDevice/Expired Profiles"]; 275 | if (![MAN fileExistsAtPath:theDir]) { 276 | [MAN createDirectoryAtPath:theDir withIntermediateDirectories:true attributes:nil error:nil]; 277 | } 278 | return theDir; 279 | } 280 | 281 | - (NSString *)invalidProvisioningProfiles { 282 | NSString *theDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Library/MobileDevice/Invalid Profiles"]; 283 | if (![MAN fileExistsAtPath:theDir]) { 284 | [MAN createDirectoryAtPath:theDir withIntermediateDirectories:true attributes:nil error:nil]; 285 | } 286 | return theDir; 287 | } 288 | 289 | - (NSString *)validProfilePath { 290 | NSString *theDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/ValidProvProfiles"]; 291 | if (![MAN fileExistsAtPath:theDir]) { 292 | [MAN createDirectoryAtPath:theDir withIntermediateDirectories:true attributes:nil error:nil]; 293 | } 294 | return theDir; 295 | } 296 | 297 | - (NSString *)pwd { 298 | NSString *theDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Desktop/ProvisioningProfileDicts"]; 299 | if (![MAN fileExistsAtPath:theDir]) { 300 | [MAN createDirectoryAtPath:theDir withIntermediateDirectories:true attributes:nil error:nil]; 301 | } 302 | return theDir; 303 | } 304 | 305 | + (NSString *)mobileDeviceLog { 306 | return [NSHomeDirectory() stringByAppendingPathComponent:@"Library/MobileDevice/Cleanup.log"]; 307 | } 308 | 309 | + (NSString *)provisioningProfilesPath { 310 | return [NSHomeDirectory() stringByAppendingPathComponent:@"Library/MobileDevice/Provisioning Profiles"]; 311 | } 312 | 313 | - (NSString *)provisioningProfilesPath { 314 | return [NSHomeDirectory() stringByAppendingPathComponent:@"Library/MobileDevice/Provisioning Profiles"]; 315 | } 316 | 317 | /** 318 | 319 | go through DeveloperCertificates NSArray key in the mobileprovision file and loops through them one by one 320 | inside of this loop we run an additional loop through all of our valid cert profiles in our keychain, if we find 321 | the range of our string in the raw data of the cert we return that as the valid id, no need to process any data after 322 | that point. 323 | 324 | */ 325 | 326 | + (NSString *)validIDFromCerts:(NSArray *)devCerts { 327 | NSArray *validDevCerts = [KBProfileHelper devCertsFull]; 328 | for (NSData *devCert in devCerts) { 329 | for (NSString *currentValidCert in validDevCerts) { 330 | NSData *distroData = [currentValidCert dataUsingEncoding:NSUTF8StringEncoding]; 331 | NSRange searchRange = NSMakeRange(0, devCert.length); 332 | NSRange dataRange = [devCert rangeOfData:distroData options:0 range:searchRange]; 333 | if (dataRange.location != NSNotFound) { 334 | //DLog(@"found profile: %@", currentValidCert); 335 | return currentValidCert; 336 | } 337 | } 338 | } 339 | return nil; 340 | } 341 | 342 | /** 343 | 344 | go through DeveloperCertificates NSArray key in the mobileprovision file to determine what valid ID's it contains, this will mainly 345 | be used for logging purposes if we don't find a valid cert 346 | 347 | */ 348 | 349 | + (NSArray *)certIDsFromCerts:(NSArray *)devCerts { 350 | NSMutableArray *certIDs = [NSMutableArray new]; 351 | for (NSData *devCert in devCerts) { 352 | //the origin offset of iPhone Developer: / iPhone Distribution / 3rd Party Mac Developer Application: appears to be the same in ever raw cert 353 | //we grab way too much data on purpose since we have no idea how long the names appended will be. 354 | NSData *userData = [devCert subdataWithRange:NSMakeRange(0x00109, 100)]; 355 | NSString *stringData = [[NSString alloc] initWithData:userData encoding:NSASCIIStringEncoding]; 356 | //grab all the way up to ), split, grab first object, readd ) could probably do a range of string thing here too.. this works tho. 357 | NSString *devName = [[[stringData componentsSeparatedByString:@")"] firstObject] stringByAppendingString:@")"]; 358 | //dont add any repeats 359 | if (![certIDs containsObject:devName]) { 360 | [certIDs addObject:devName]; 361 | } 362 | } 363 | return certIDs; 364 | } 365 | 366 | //#define ESSENTIAL_PREDICATE @"(SELF CONTAINS[c] Essential) OR (Tag contains[c] 'essential') OR (Priority == 'required') or (Package == 'com.science')" 367 | 368 | + (NSDictionary *)validProfileForID:(NSString *)appID withTarget:(NSString *)target { 369 | NSArray *validProfiles = [self validProfilesSlim]; 370 | NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"(applicationIdentifier contains[c] '%@') or (applicationIdentifier contains[c] '*')", appID]; 371 | 372 | NSPredicate *targetPredicate = [NSPredicate predicateWithFormat:@"(Target == %@)", target]; 373 | NSPredicate *thePred = [NSCompoundPredicate andPredicateWithSubpredicates:@[filterPredicate, targetPredicate]]; 374 | NSArray *filterOne = [validProfiles filteredArrayUsingPredicate:thePred]; 375 | //NSArray *filterTwo = [filterOne filteredArrayUsingPredicate:targetPredicate]; 376 | return [filterOne firstObject]; 377 | } 378 | 379 | + (NSArray *)validProfilesForID:(NSString *)appID { 380 | NSMutableArray *bothProfiles = [NSMutableArray new]; 381 | NSArray *validProfiles = [self validProfilesSlim]; 382 | NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:@"(applicationIdentifier contains[c] '%@') or (applicationIdentifier contains[c] '*')", appID]; 383 | NSPredicate *targetPredicate = [NSPredicate predicateWithFormat:@"(Target == %@)", @"Debug"]; 384 | NSPredicate *thePred = [NSCompoundPredicate andPredicateWithSubpredicates:@[filterPredicate, targetPredicate]]; 385 | NSArray *profileFilter = [validProfiles filteredArrayUsingPredicate:thePred]; 386 | [bothProfiles addObject:[profileFilter firstObject]]; 387 | targetPredicate = [NSPredicate predicateWithFormat:@"(Target == %@)", @"Release"]; 388 | thePred = [NSCompoundPredicate andPredicateWithSubpredicates:@[filterPredicate, targetPredicate]]; 389 | profileFilter = [validProfiles filteredArrayUsingPredicate:thePred]; 390 | [bothProfiles addObject:[profileFilter firstObject]]; 391 | return bothProfiles; 392 | 393 | } 394 | 395 | 396 | + (NSArray *)validProfilesSlim { 397 | NSMutableArray *profileArray = [NSMutableArray new]; 398 | NSMutableArray *profileNames = [NSMutableArray new]; 399 | 400 | NSString *profileDir = [self provisioningProfilesPath]; 401 | NSArray *fileArray = [MAN contentsOfDirectoryAtPath:profileDir error:nil]; 402 | for (NSString *theObject in fileArray) { 403 | if ([[[theObject pathExtension] lowercaseString] isEqualToString:@"mobileprovision"] || 404 | [[[theObject pathExtension] lowercaseString] isEqualToString:@"provisionprofile"]) { 405 | NSString *fullPath = [profileDir stringByAppendingPathComponent:theObject]; 406 | NSMutableDictionary *provisionDict = [KBProfileHelper provisioningDictionaryFromFilePath: 407 | [profileDir stringByAppendingPathComponent:theObject]]; 408 | 409 | NSString *csid = provisionDict[@"CODE_SIGN_IDENTITY"]; 410 | NSDate *expireDate = provisionDict[@"ExpirationDate"]; 411 | NSDate *createdDate = provisionDict[@"CreationDate"]; 412 | NSString *name = provisionDict[@"Name"]; 413 | [provisionDict setObject:fullPath forKey:@"Path"]; 414 | BOOL expired = FALSE; 415 | 416 | if ([expireDate isGreaterThan:[NSDate date]]) { 417 | // DLog(@"not expired: %@", expireDate); 418 | } else { 419 | //its expired, who cares about any of the other details. add it to the expired list. 420 | DLog(@"expired: %@\n", expireDate); 421 | expired = TRUE; 422 | } 423 | 424 | //check to see if our valid non expired certificates in our keychain are referenced by the profile, or if its expired 425 | 426 | if (csid == nil || expired == TRUE) { 427 | 428 | //trimmed 429 | 430 | } else { //we got this far the profile is not expired and can be compared against other potential duplicates 431 | 432 | if ([profileNames containsObject:name]) //we have this profile already, is ours newer or is the one already in our collection newer? 433 | { 434 | NSDictionary *otherDict = [[profileArray subarrayWithName:name] objectAtIndex:0]; 435 | NSDate *previousCreationDate = otherDict[@"CreationDate"]; 436 | if ([previousCreationDate isGreaterThan:createdDate]) { 437 | DLog(@"found repeat name, but we're older: %@ vs: %@\n", createdDate, previousCreationDate); 438 | 439 | } else { 440 | 441 | DLog(@"found a newer profile: %@ replace the old one: %@\n", createdDate, previousCreationDate); 442 | [profileArray removeObject:otherDict]; 443 | [profileArray addObject:provisionDict]; 444 | } 445 | 446 | } else { 447 | 448 | //we dont have this name on record and it should be a valid profile! 449 | 450 | [profileArray addObject:provisionDict]; 451 | [profileNames addObject:name]; 452 | } 453 | } 454 | } 455 | } 456 | return profileArray; 457 | } 458 | 459 | //this is where all the arrays are created of who is valid, invalid, duplicate etc... 460 | 461 | - (NSArray *)validProfiles { 462 | NSMutableArray *profileArray = [NSMutableArray new]; 463 | NSMutableArray *profileNames = [NSMutableArray new]; 464 | NSMutableArray *_invalids = [NSMutableArray new]; 465 | NSMutableArray *_expired = [NSMutableArray new]; 466 | NSMutableArray *_duplicates = [NSMutableArray new]; 467 | // NSArray *devCert = [self devCerts]; 468 | NSString *profileDir = [self provisioningProfilesPath]; 469 | NSArray *fileArray = [MAN contentsOfDirectoryAtPath:profileDir error:nil]; 470 | for (NSString *theObject in fileArray) { 471 | if ([[[theObject pathExtension] lowercaseString] isEqualToString:@"mobileprovision"] || 472 | [[[theObject pathExtension] lowercaseString] isEqualToString:@"provisionprofile"]) { 473 | NSString *fullPath = [profileDir stringByAppendingPathComponent:theObject]; 474 | NSMutableDictionary *provisionDict = [KBProfileHelper provisioningDictionaryFromFilePath: 475 | [profileDir stringByAppendingPathComponent:theObject]]; 476 | 477 | NSString *csid = provisionDict[@"CODE_SIGN_IDENTITY"]; 478 | // NSString *teamId = [provisionDict[@"TeamIdentifier"] lastObject]; 479 | NSDate *expireDate = provisionDict[@"ExpirationDate"]; 480 | NSDate *createdDate = provisionDict[@"CreationDate"]; 481 | NSString *name = provisionDict[@"Name"]; 482 | [provisionDict setObject:fullPath forKey:@"Path"]; 483 | BOOL expired = FALSE; 484 | DLog(@"processing profile named: %@ filename: %@", name, theObject); 485 | if ([expireDate isGreaterThan:[NSDate date]]) { 486 | // DLog(@"not expired: %@", expireDate); 487 | 488 | } else { 489 | 490 | //its expired, who cares about any of the other details. add it to the expired list. 491 | 492 | DLog(@"expired: %@\n", expireDate); 493 | [_expired addObject:provisionDict]; 494 | expired = TRUE; 495 | } 496 | 497 | //check to see if our valid non expired certificates in our keychain are referenced by the profile, or if its expired 498 | 499 | if (csid == nil || expired == TRUE) { 500 | if (![_expired containsObject:provisionDict]) { 501 | [_invalids addObject:provisionDict]; 502 | } 503 | if (csid == nil) { 504 | 505 | DLog(@"No valid codesigning identities found!!"); 506 | 507 | } else { 508 | 509 | DLog(@"invalid or expired cert: %@\n", theObject ); 510 | 511 | } 512 | } else { //we got this far the profile is not expired and can be compared against other potential duplicates 513 | 514 | if ([profileNames containsObject:name]) //we have this profile already, is ours newer or is the one already in our collection newer? 515 | { 516 | NSDictionary *otherDict = [[profileArray subarrayWithName:name] objectAtIndex:0]; 517 | NSDate *previousCreationDate = otherDict[@"CreationDate"]; 518 | if ([previousCreationDate isGreaterThan:createdDate]) { 519 | DLog(@"found repeat name, but we're older: %@ vs: %@\n", createdDate, previousCreationDate); 520 | [_duplicates addObject:provisionDict]; 521 | } else { 522 | DLog(@"found a newer profile: %@ replace the old one: %@\n", createdDate, previousCreationDate); 523 | [_duplicates addObject:otherDict]; 524 | [profileArray removeObject:otherDict]; 525 | [profileArray addObject:provisionDict]; 526 | } 527 | 528 | } else { 529 | 530 | //we dont have this name on record and it should be a valid profile! 531 | 532 | [profileArray addObject:provisionDict]; 533 | [profileNames addObject:name]; 534 | } 535 | } 536 | } 537 | } 538 | invalidProfiles = _invalids; 539 | expiredProfiles = _expired; 540 | duplicateProfiles = _duplicates; 541 | return profileArray; 542 | } 543 | 544 | + (NSString *)pathFromUUID:(NSString *)uuid { 545 | NSString *thePath = [[[self provisioningProfilesPath] stringByAppendingPathComponent:uuid] stringByAppendingPathExtension:@"mobileprovision"]; 546 | if (![MAN fileExistsAtPath:thePath]) { 547 | thePath = [[[self provisioningProfilesPath] stringByAppendingPathComponent:uuid] stringByAppendingPathExtension:@"provisionprofile"]; 548 | if (![MAN fileExistsAtPath:thePath]) { 549 | NSLog(@"provisioning profile not found: %@", thePath); 550 | return nil; 551 | } 552 | } 553 | return thePath; 554 | } 555 | 556 | //sort of obsolete, used to take the valid profiles and copy them out for the original POC 557 | 558 | - (NSArray *)validProfilePaths { 559 | NSMutableArray *pathArray = [NSMutableArray new]; 560 | NSArray *profiles = [self validProfiles]; 561 | for (NSDictionary *profile in profiles) 562 | { 563 | NSString *filePath = [[self class] pathFromUUID:profile[@"UUID"]]; 564 | if (filePath != nil) { 565 | [pathArray addObject:filePath]; 566 | } 567 | } 568 | return pathArray; 569 | } 570 | 571 | 572 | //take that profile and chop off the top and bottom "junk" data to get at the portion that we need. 573 | 574 | + (NSMutableDictionary *)provisioningDictionaryFromFilePath:(NSString *)profilePath { 575 | NSString *fileContents = [NSString stringWithContentsOfFile:profilePath encoding:NSASCIIStringEncoding error:nil]; 576 | NSUInteger fileLength = [fileContents length]; 577 | if (fileLength == 0) 578 | fileContents = [NSString stringWithContentsOfFile:profilePath]; //if ascii doesnt work, have to use the deprecated (thankfully not obsolete!) method 579 | 580 | fileLength = [fileContents length]; 581 | if (fileLength == 0) 582 | return nil; 583 | 584 | //find NSRange location of "]; 590 | 591 | //adjust the location of endingRange to include into our newly trimmed string. 592 | NSUInteger endingLocation = endingRange.location + endingRange.length; 593 | 594 | //offset the ending location to trim out the "garbage" before 598 | 599 | NSRange plistRange = NSMakeRange(startingLocation, endingLocationAdjusted); 600 | 601 | //actually create our string! 602 | NSString *plistString = [fileContents substringWithRange:plistRange]; 603 | 604 | //yay categories!! convert the dictionary raw string into an actual NSDictionary 605 | NSMutableDictionary *dict = [plistString dictionaryFromString]; 606 | 607 | 608 | NSString *appID = [dict[@"Entitlements"] objectForKey:@"application-identifier"]; 609 | 610 | if (appID == nil) { 611 | appID = [dict[@"Entitlements"] objectForKey:@"com.apple.application-identifier"]; 612 | } 613 | [dict setObject:appID forKey:@"applicationIdentifier"]; 614 | 615 | //since we will always need this data, best to grab it here and make it part of the dictionary for easy re-use / validity check. 616 | 617 | NSString *ourID = [self validIDFromCerts:dict[@"DeveloperCertificates"]]; 618 | 619 | if (ourID != nil) { 620 | [dict setValue:ourID forKey:@"CODE_SIGN_IDENTITY"]; 621 | //in THEORY should set the profile target to Debug or Release depending on if it finds "Developer:" string. 622 | if ([ourID rangeOfString:@"Developer:"].location != NSNotFound) { 623 | [dict setValue:@"Debug" forKey:@"Target"]; 624 | } else { 625 | [dict setValue:@"Release" forKey:@"Target"]; 626 | } 627 | } 628 | 629 | //grab all the valid certs, for later logging / debugging for why a profile might be invalid 630 | 631 | NSArray *validCertIds = [self certIDsFromCerts:dict[@"DeveloperCertificates"]]; 632 | [dict setValue:validCertIds forKey:@"CodeSignArray"]; 633 | 634 | // shouldnt need this frivolous data any longer, we know which ID (if any) we have and have all the valid ones too 635 | 636 | [dict removeObjectForKey:@"DeveloperCertificates"]; 637 | 638 | //write to file for debug / posterity 639 | // [dict writeToFile:[[[self pwd] stringByAppendingPathComponent:dict[@"Name"]] stringByAppendingPathExtension:@"plist"] atomically:TRUE]; 640 | return dict; 641 | } 642 | 643 | @end 644 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner/ProvisioningProfileCleaner-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import "Defines.h" 10 | #endif 11 | -------------------------------------------------------------------------------- /ProvisioningProfileCleaner/ProvisioningProfileCleaner.1: -------------------------------------------------------------------------------- 1 | .\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples. 2 | .\"See Also: 3 | .\"man mdoc.samples for a complete listing of options 4 | .\"man mdoc for the short list of editing options 5 | .\"/usr/share/misc/mdoc.template 6 | .Dd 8/7/14 \" DATE 7 | .Dt ProvisioningProfileCleaner 1 \" Program name and manual section number 8 | .Os Darwin 9 | .Sh NAME \" Section Header - required - don't modify 10 | .Nm ProvisioningProfileCleaner, 11 | .\" The following lines are read in generating the apropos(man -k) database. Use only key 12 | .\" words here as the database is built based on the words here and in the .ND line. 13 | .Nm Other_name_for_same_program(), 14 | .Nm Yet another name for the same program. 15 | .\" Use .Nm macro to designate other names for the documented program. 16 | .Nd This line parsed for whatis database. 17 | .Sh SYNOPSIS \" Section Header - required - don't modify 18 | .Nm 19 | .Op Fl abcd \" [-abcd] 20 | .Op Fl a Ar path \" [-a path] 21 | .Op Ar file \" [file] 22 | .Op Ar \" [file ...] 23 | .Ar arg0 \" Underlined argument - use .Ar anywhere to underline 24 | arg2 ... \" Arguments 25 | .Sh DESCRIPTION \" Section Header - required - don't modify 26 | Use the .Nm macro to refer to your program throughout the man page like such: 27 | .Nm 28 | Underlining is accomplished with the .Ar macro like this: 29 | .Ar underlined text . 30 | .Pp \" Inserts a space 31 | A list of items with descriptions: 32 | .Bl -tag -width -indent \" Begins a tagged list 33 | .It item a \" Each item preceded by .It macro 34 | Description of item a 35 | .It item b 36 | Description of item b 37 | .El \" Ends the list 38 | .Pp 39 | A list of flags and their descriptions: 40 | .Bl -tag -width -indent \" Differs from above in tag removed 41 | .It Fl a \"-a flag as a list item 42 | Description of -a flag 43 | .It Fl b 44 | Description of -b flag 45 | .El \" Ends the list 46 | .Pp 47 | .\" .Sh ENVIRONMENT \" May not be needed 48 | .\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1 49 | .\" .It Ev ENV_VAR_1 50 | .\" Description of ENV_VAR_1 51 | .\" .It Ev ENV_VAR_2 52 | .\" Description of ENV_VAR_2 53 | .\" .El 54 | .Sh FILES \" File used or created by the topic of the man page 55 | .Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact 56 | .It Pa /usr/share/file_name 57 | FILE_1 description 58 | .It Pa /Users/joeuser/Library/really_long_file_name 59 | FILE_2 description 60 | .El \" Ends the list 61 | .\" .Sh DIAGNOSTICS \" May not be needed 62 | .\" .Bl -diag 63 | .\" .It Diagnostic Tag 64 | .\" Diagnostic informtion here. 65 | .\" .It Diagnostic Tag 66 | .\" Diagnostic informtion here. 67 | .\" .El 68 | .Sh SEE ALSO 69 | .\" List links in ascending order by section, alphabetically within a section. 70 | .\" Please do not reference files that do not exist without filing a bug report 71 | .Xr a 1 , 72 | .Xr b 1 , 73 | .Xr c 1 , 74 | .Xr a 2 , 75 | .Xr b 2 , 76 | .Xr a 3 , 77 | .Xr b 3 78 | .\" .Sh BUGS \" Document known, unremedied bugs 79 | .\" .Sh HISTORY \" Document history if command behaves in a unique manner -------------------------------------------------------------------------------- /ProvisioningProfileCleaner/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ProvisioningProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 8/7/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KBProfileHelper.h" 11 | 12 | int main(int argc, const char * argv[]) 13 | { 14 | 15 | @autoreleasepool { 16 | 17 | // insert code here... 18 | // NSLog(@"Hello, World!"); 19 | KBProfileHelper *helper = [[KBProfileHelper alloc] init]; 20 | return [helper processProfiles]; 21 | } 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ProvisioningProfileCleaner 2 | ========================== 3 | 4 | Command line tool / Xcode Plugin to clean up your Provisioning profile folder! 5 | 6 | Run it from anywhere (even just run / build it in Xcode) and it will take your profiles in ~/Library/MobileDevice/Provisioning Profiles and organize them 7 | into Duplicate Profiles / Expired Profiles / Invalid Profiles folders as necessary. 8 | 9 | - Expired Profiles are passed their expiration date and are no longer valid. 10 | - Duplicate profiles are profiles where you have a newer version of them (generally with more UDID's) that should be ignored 11 | - Invalid Profiles are ones that don't have an active / valid keychain certificate to sign from. Profiles in this folder should NOT be expired, just invalid. 12 | 13 | This command line tool is structured to be non destructive, that is why files are moved into new folders so you can inspect them as necessary, inside each 14 | folder there is a log that will accompany it with text similar to the following: 15 | 16 | The following provisioning profiles have newer duplicates: 17 | --------------------------------------------------- 18 | 19 | Profile PROFILE_UDID.mobileprovision has duplicates! 20 | it expires on 2015-03-13 16:01:35 +0000 with team: Your Company, LLC (UNIQUE_CERT_ID) 21 | profile name: CatchAllProfile 22 | appID: UNIQUE_CERT_ID.* appIDName: Xcode: iOS Wildcard App ID 23 | 24 | 25 | The following provisioning profiles have expired: 26 | --------------------------------------------------- 27 | 28 | Profile PROFILE_UDID.mobileprovision expired on 2013-10-18 06:24:40 +0000 29 | with team: Your Name (UNIQUE_CERT_ID) profile name: Your Profile Name 30 | appID: UNIQUE_CERT_ID.com.yourcompany.AppName appIDName: Your App ID Name 31 | 32 | Future Plans 33 | ------------ 34 | Make it into a companion Xcode plugin and get it added to Alcatraz 35 | -------------------------------------------------------------------------------- /XCProfileCleaner/Defines.h: -------------------------------------------------------------------------------- 1 | // 2 | // Defines.h 3 | // ProvisioningProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 8/13/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | static NSString* const kPCScanOpenedProfiles = @"kPCScanOpenedProfiles"; 10 | static NSString* const kPCScanOpenedProjects = @"kPCScanOpenedProjects"; 11 | static NSString* const kPCAlertOnProjectChanges = @"kPCAlertOnProjectChanges"; 12 | 13 | #define UD [NSUserDefaults standardUserDefaults] -------------------------------------------------------------------------------- /XCProfileCleaner/JRSwizzle.h: -------------------------------------------------------------------------------- 1 | // JRSwizzle.h semver:1.0 2 | // Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/MIT 4 | // https://github.com/rentzsch/jrswizzle 5 | 6 | #import 7 | 8 | @interface NSObject (JRSwizzle) 9 | 10 | + (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_; 11 | + (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /XCProfileCleaner/JRSwizzle.m: -------------------------------------------------------------------------------- 1 | // JRSwizzle.m semver:1.0 2 | // Copyright (c) 2007-2011 Jonathan 'Wolf' Rentzsch: http://rentzsch.com 3 | // Some rights reserved: http://opensource.org/licenses/MIT 4 | // https://github.com/rentzsch/jrswizzle 5 | 6 | #import "JRSwizzle.h" 7 | 8 | #if TARGET_OS_IPHONE 9 | #import 10 | #import 11 | #else 12 | #import 13 | #endif 14 | 15 | #define SetNSErrorFor(FUNC, ERROR_VAR, FORMAT,...) \ 16 | if (ERROR_VAR) { \ 17 | NSString *errStr = [NSString stringWithFormat:@"%s: " FORMAT,FUNC,##__VA_ARGS__]; \ 18 | *ERROR_VAR = [NSError errorWithDomain:@"NSCocoaErrorDomain" \ 19 | code:-1 \ 20 | userInfo:[NSDictionary dictionaryWithObject:errStr forKey:NSLocalizedDescriptionKey]]; \ 21 | } 22 | #define SetNSError(ERROR_VAR, FORMAT,...) SetNSErrorFor(__func__, ERROR_VAR, FORMAT, ##__VA_ARGS__) 23 | 24 | #if OBJC_API_VERSION >= 2 25 | #define GetClass(obj) object_getClass(obj) 26 | #else 27 | #define GetClass(obj) (obj ? obj->isa : Nil) 28 | #endif 29 | 30 | @implementation NSObject (JRSwizzle) 31 | 32 | + (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ { 33 | #if OBJC_API_VERSION >= 2 34 | Method origMethod = class_getInstanceMethod(self, origSel_); 35 | if (!origMethod) { 36 | #if TARGET_OS_IPHONE 37 | SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]); 38 | #else 39 | SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); 40 | #endif 41 | return NO; 42 | } 43 | 44 | Method altMethod = class_getInstanceMethod(self, altSel_); 45 | if (!altMethod) { 46 | #if TARGET_OS_IPHONE 47 | SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]); 48 | #else 49 | SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); 50 | #endif 51 | return NO; 52 | } 53 | 54 | class_addMethod(self, 55 | origSel_, 56 | class_getMethodImplementation(self, origSel_), 57 | method_getTypeEncoding(origMethod)); 58 | class_addMethod(self, 59 | altSel_, 60 | class_getMethodImplementation(self, altSel_), 61 | method_getTypeEncoding(altMethod)); 62 | 63 | method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_)); 64 | return YES; 65 | #else 66 | // Scan for non-inherited methods. 67 | Method directOriginalMethod = NULL, directAlternateMethod = NULL; 68 | 69 | void *iterator = NULL; 70 | struct objc_method_list *mlist = class_nextMethodList(self, &iterator); 71 | while (mlist) { 72 | int method_index = 0; 73 | for (; method_index < mlist->method_count; method_index++) { 74 | if (mlist->method_list[method_index].method_name == origSel_) { 75 | assert(!directOriginalMethod); 76 | directOriginalMethod = &mlist->method_list[method_index]; 77 | } 78 | if (mlist->method_list[method_index].method_name == altSel_) { 79 | assert(!directAlternateMethod); 80 | directAlternateMethod = &mlist->method_list[method_index]; 81 | } 82 | } 83 | mlist = class_nextMethodList(self, &iterator); 84 | } 85 | 86 | // If either method is inherited, copy it up to the target class to make it non-inherited. 87 | if (!directOriginalMethod || !directAlternateMethod) { 88 | Method inheritedOriginalMethod = NULL, inheritedAlternateMethod = NULL; 89 | if (!directOriginalMethod) { 90 | inheritedOriginalMethod = class_getInstanceMethod(self, origSel_); 91 | if (!inheritedOriginalMethod) { 92 | SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self className]); 93 | return NO; 94 | } 95 | } 96 | if (!directAlternateMethod) { 97 | inheritedAlternateMethod = class_getInstanceMethod(self, altSel_); 98 | if (!inheritedAlternateMethod) { 99 | SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self className]); 100 | return NO; 101 | } 102 | } 103 | 104 | int hoisted_method_count = !directOriginalMethod && !directAlternateMethod ? 2 : 1; 105 | struct objc_method_list *hoisted_method_list = malloc(sizeof(struct objc_method_list) + (sizeof(struct objc_method)*(hoisted_method_count-1))); 106 | hoisted_method_list->obsolete = NULL; // soothe valgrind - apparently ObjC runtime accesses this value and it shows as uninitialized in valgrind 107 | hoisted_method_list->method_count = hoisted_method_count; 108 | Method hoisted_method = hoisted_method_list->method_list; 109 | 110 | if (!directOriginalMethod) { 111 | bcopy(inheritedOriginalMethod, hoisted_method, sizeof(struct objc_method)); 112 | directOriginalMethod = hoisted_method++; 113 | } 114 | if (!directAlternateMethod) { 115 | bcopy(inheritedAlternateMethod, hoisted_method, sizeof(struct objc_method)); 116 | directAlternateMethod = hoisted_method; 117 | } 118 | class_addMethods(self, hoisted_method_list); 119 | } 120 | 121 | // Swizzle. 122 | IMP temp = directOriginalMethod->method_imp; 123 | directOriginalMethod->method_imp = directAlternateMethod->method_imp; 124 | directAlternateMethod->method_imp = temp; 125 | 126 | return YES; 127 | #endif 128 | } 129 | 130 | + (BOOL)jr_swizzleClassMethod:(SEL)origSel_ withClassMethod:(SEL)altSel_ error:(NSError**)error_ { 131 | return [GetClass((id)self) jr_swizzleMethod:origSel_ withMethod:altSel_ error:error_]; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /XCProfileCleaner/NSData+Split.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Split.h 3 | // 4 | // Created by ████ 5 | // 6 | 7 | #import 8 | 9 | 10 | /// An NSData category that allows splitting the data into separate components. 11 | 12 | @interface NSData (Split) 13 | 14 | /** Splits the source data into any array of components separated by the specified byte. 15 | 16 | Taken from http://www.geektheory.ca/blog/splitting-nsdata-object-data-specific-byte/ 17 | 18 | @param sep Byte to separate by. 19 | @return NSArray of components 20 | */ 21 | -(NSArray *)componentsSeparatedByByte:(Byte)sep; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /XCProfileCleaner/NSData+Split.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+Split.m 3 | // 4 | // Created by ████ 5 | // 6 | 7 | #import "NSData+Split.h" 8 | 9 | @implementation NSData (Split) 10 | 11 | -(NSArray *)componentsSeparatedByByte:(Byte)sep; 12 | { 13 | unsigned long len, index, last_sep_index; 14 | NSData *line; 15 | NSMutableArray *lines = nil; 16 | 17 | len = [self length]; 18 | Byte *cData = malloc(len); 19 | if (cData == NULL) 20 | { 21 | return lines; 22 | } 23 | 24 | [self getBytes:cData length:len]; 25 | 26 | index = last_sep_index = 0; 27 | 28 | lines = [[NSMutableArray alloc] init]; 29 | 30 | do 31 | { 32 | if (sep == cData[index]) 33 | { 34 | NSRange startEndRange = NSMakeRange(last_sep_index, index - last_sep_index); 35 | line = [self subdataWithRange:startEndRange]; 36 | 37 | [lines addObject:line]; 38 | 39 | last_sep_index = index + 1; 40 | 41 | continue; 42 | } 43 | } while (index++ < len); 44 | 45 | free(cData); 46 | cData = NULL; 47 | 48 | return lines; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCPCModel.h: -------------------------------------------------------------------------------- 1 | // // XCPRModel.h // XToDo / XCPullRequest // // Created by Travis on 13-11-28. // updated and modified by Kevin Bradley // Copyright (c) 2013年 Plumn LLC. All rights reserved. // #import #import #import "XCPCProjectSetting.h" @interface XCPropertyInfoContext : NSObject - (id)expandedValueForString:(id)arg1 withConditionSet:(id)arg2; - (id)expandedValueForString:(id)arg1; - (id)expandedValueForPropertyNamed:(id)arg1; - (id)expandedValueForPropertyNamed:(id)arg1 withConditionSet:(id)arg2; - (void)setValue:(id)arg1 forPropertyName:(id)arg2; - (void)setValue:(id)arg1 forPropertyName:(id)arg2 conditionSet:(id)arg3; - (void)setValue:(id)arg1 forPropertyName:(id)arg2 conditionSet:(id)arg3 atDefinitionLevel:(int)arg4; @end @interface DVTMacroDefinitionConditionSet : NSObject + (id)conditionSetFromStringRepresentation:(id)arg1 getBaseMacroName:(id *)arg2 error:(id *)arg3; @end @interface PBXObject : NSObject - (id)name; - (id)targetWithGlobalID:(id)arg1; - (id)targetNamed:(id)arg1; - (id)targets; - (id)targetTemplates; @end @interface PBXContainer : PBXObject @end @interface PBXProject : PBXContainer - (id)path; + (id)projectWithFile:(id)arg1; + (id)openProjects; - (id)cachedPropertyInfoContextForConfigurationNamed:(id)arg1; @end @interface PBXContainerItem : PBXObject @end @interface PBXProjectItem : PBXContainerItem @end @interface PBXTarget : PBXProjectItem @end @interface PBXNativeTarget : PBXTarget - (id)infoPlistFilePath; - (id)infoPlistFilePathForConfigurationNamed:(id)arg1; - (id)productSettingForKey:(id)arg1; - (id)productSettings; - (id)infoPlistSettings; @end @interface DVTChoice : NSObject - (id)initWithTitle:(id)arg1 toolTip:(id)arg2 image:(id)arg3 representedObject:(id)arg4; @end @interface DVTTextDocumentLocation : NSObject @property (readonly) NSRange characterRange; @property (readonly) NSRange lineRange; @end @interface DVTTextPreferences : NSObject + (id)preferences; @property BOOL trimWhitespaceOnlyLines; @property BOOL trimTrailingWhitespace; @property BOOL useSyntaxAwareIndenting; @end @interface DVTSourceTextStorage : NSTextStorage - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)string withUndoManager:(id)undoManager; - (NSRange)lineRangeForCharacterRange:(NSRange)range; - (NSRange)characterRangeForLineRange:(NSRange)range; - (void)indentCharacterRange:(NSRange)range undoManager:(id)undoManager; @end @interface DVTFileDataType : NSObject @property (readonly) NSString *identifier; @end @interface DVTFilePath : NSObject @property (readonly) NSURL *fileURL; @property (readonly) DVTFileDataType *fileDataTypePresumed; @end @interface IDEContainerItem : NSObject @property (readonly) DVTFilePath *resolvedFilePath; @end @interface IDEGroup : IDEContainerItem @end @interface IDEFileReference : IDEContainerItem @end @interface IDENavigableItem : NSObject @property (readonly) IDENavigableItem *parentItem; @property (readonly) id representedObject; @end @interface IDEFileNavigableItem : IDENavigableItem @property (readonly) DVTFileDataType *documentType; @property (readonly) NSURL *fileURL; @end @interface IDEStructureNavigator : NSObject @property (retain) NSArray *selectedObjects; @end @interface IDENavigableItemCoordinator : NSObject - (id)structureNavigableItemForDocumentURL:(id)arg1 inWorkspace:(id)arg2 error:(id *)arg3; @end @interface IDENavigatorArea : NSObject @property NSArrayController *extensionsController; - (id)currentNavigator; @end @interface IDEWorkspaceTabController : NSObject @property (readonly) IDENavigatorArea *navigatorArea; @property(readonly) IDEWorkspaceTabController *structureEditWorkspaceTabController; @end @interface IDEDocumentController : NSDocumentController +(IDEDocumentController*)sharedDocumentController; + (id)editorDocumentForNavigableItem:(id)arg1; + (id)retainedEditorDocumentForNavigableItem:(id)arg1 error:(id *)arg2; + (void)releaseEditorDocument:(id)arg1; @end @interface IDESourceCodeDocument : NSDocument - (DVTSourceTextStorage *)textStorage; - (NSUndoManager *)undoManager; @end @interface IDESourceCodeComparisonEditor : NSObject @property (readonly) NSTextView *keyTextView; @property (retain) NSDocument *primaryDocument; @end @interface IDESourceCodeEditor : NSObject @property (retain) NSTextView *textView; - (IDESourceCodeDocument *)sourceCodeDocument; @end @interface IDEEditorContext : NSObject - (id)editor; // returns the current editor. If the editor is the code editor, the class is `IDESourceCodeEditor` @end @interface IDEEditorArea : NSObject - (IDEEditorContext *)lastActiveEditorContext; @end @interface IDEConsoleArea : NSObject - (IDEEditorContext *)lastActiveEditorContext; @end @interface IDEWorkspaceWindowController : NSObject @property (readonly) IDEWorkspaceTabController *activeWorkspaceTabController; - (IDEEditorArea *)editorArea; @end @interface DVTMapTable : NSObject - (id)dictionaryRepresentation; @end @interface IDESourceControlProject : NSObject @property(retain) NSMutableDictionary *requiredConfigurationsDictionary; // @synthesize requiredConfigurationsDictionary=_requiredConfigurationsDictionary; @property(retain) NSMutableDictionary *repositoryRootForConfigurationDictionary; // @synthesize repositoryRootForConfigurationDictionary=_repositoryRootForConfigurationDictionary; @property(retain) NSMutableDictionary *relativeInstallPathForConfigurationDictionary; // @synthesize relativeInstallPathForConfigurationDictionary=_relativeInstallPathForConfigurationDictionary; @property(retain) NSMutableDictionary *originForConfigurationDictionary; // @synthesize originForConfigurationDictionary=_originForConfigurationDictionary; //@property(retain) DVTCustomDataSpecifier *customDataSpecifier; // @synthesize customDataSpecifier=_customDataSpecifier; @property(getter=isFavorite) BOOL favorite; // @synthesize favorite=_favorite; @property(retain) NSMutableArray *configurationsInCurrentWorkspace; // @synthesize configurationsInCurrentWorkspace=_configurationsInCurrentWorkspace; @property unsigned long long type; // @synthesize type=_type; //@property(retain) IDESourceControlWorkingCopyConfiguration *projectWCC; // @synthesize projectWCC=_projectWCC; @property(copy) NSString *relativePathToProject; // @synthesize relativePathToProject=_relativePathToProject; @property(copy) NSURL *projectURL; // @synthesize projectURL=_projectURL; @property(readonly) NSString *uniqueIdentifier; // @synthesize uniqueIdentifier=_uniqueIdentifier; @property(copy) NSString *name; // @synthesize name=_name; - (id)dictionaryRepresentation; @end @interface IDESourceControlWorkspaceMonitor : NSObject @property(retain) IDESourceControlProject *sourceControlProject; // @synthesize sourceControlProject=_sourceControlProject; @property(readonly) DVTMapTable *workspaceRootForWorkingTreeMapTable; @end @interface IDEWorkspace : NSWorkspace @property (readonly) DVTFilePath *representingFilePath; @property(readonly) NSString *representingTitle; - (id)displayName; @property(readonly) NSString *name; @property(retain) IDESourceControlWorkspaceMonitor *sourceControlWorkspaceMonitor; // @synthesize @end @interface IDEWorkspaceDocument : NSDocument @property (readonly) IDEWorkspace *workspace; @end @interface XCPCModel : NSObject @property (nonatomic, assign) id delegate; + (NSString *)currentProjectName; + (NSString *)applicationSupportFolder; + (IDEWorkspaceDocument *)currentWorkspaceDocument; + (IDEWorkspaceTabController*)tabController; + (IDESourceCodeEditor*)currentEditor; + (IDESourceControlWorkspaceMonitor *)sourceControlMonitor; + (NSString *)currentProjectFile; + (NSString *)currentRootPath; + (void) cleanAllTempFiles; + (NSString *) addPathSlash:(NSString *)path; + (NSString *) rootPathMacro; + (NSArray *) explandRootPathMacros:(NSArray *)paths projectPath:(NSString *)projectPath; + (NSString *) explandRootPathMacro:(NSString *)path projectPath:(NSString *)projectPath; + (NSString *) settingFilePathByProjectName:(NSString *)projectName; + (XCPCProjectSetting *) projectSettingByProjectName:(NSString *)projectName; + (void) saveProjectSetting:(XCPCProjectSetting *)projectSetting ByProjectName:(NSString *)projectName; @end extern NSString* const kNotifyProjectSettingChanged; -------------------------------------------------------------------------------- /XCProfileCleaner/XCPCModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCPCModel.m 3 | // XToDo 4 | // 5 | // Created by Travis on 13-11-28. 6 | // Copyright (c) 2013年 Plumn LLC. All rights reserved. 7 | // 8 | 9 | #import "XCPCModel.h" 10 | #import 11 | 12 | 13 | 14 | #import "NSData+Split.h" 15 | 16 | static NSBundle *pluginBundle; 17 | 18 | 19 | @implementation XCPCModel 20 | 21 | + (NSString *)applicationSupportFolder 22 | { 23 | NSBundle *ourBundle = [NSBundle bundleForClass:objc_getClass("XCPullRequest")]; 24 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); 25 | NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil; 26 | basePath = [basePath stringByAppendingPathComponent:[[ourBundle infoDictionary] objectForKey:(NSString *)kCFBundleNameKey]]; 27 | if (![[NSFileManager defaultManager] fileExistsAtPath:basePath]) 28 | [[NSFileManager defaultManager] createDirectoryAtPath:basePath withIntermediateDirectories:YES attributes:nil error:nil]; 29 | 30 | return basePath; 31 | } 32 | 33 | + (IDEWorkspaceTabController*)tabController{ 34 | NSWindowController *currentWindowController = [[NSApp keyWindow] windowController]; 35 | if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { 36 | IDEWorkspaceWindowController *workspaceController = (IDEWorkspaceWindowController *)currentWindowController; 37 | 38 | return workspaceController.activeWorkspaceTabController; 39 | } 40 | return nil; 41 | } 42 | 43 | + (id)currentEditor { 44 | NSWindowController *currentWindowController = [[NSApp mainWindow] windowController]; 45 | if ([currentWindowController isKindOfClass:NSClassFromString(@"IDEWorkspaceWindowController")]) { 46 | IDEWorkspaceWindowController *workspaceController = (IDEWorkspaceWindowController *)currentWindowController; 47 | IDEEditorArea *editorArea = [workspaceController editorArea]; 48 | IDEEditorContext *editorContext = [editorArea lastActiveEditorContext]; 49 | return [editorContext editor]; 50 | } 51 | return nil; 52 | } 53 | + (IDEWorkspaceDocument *)currentWorkspaceDocument { 54 | NSWindowController *currentWindowController = [[NSApp mainWindow] windowController]; 55 | id document = [currentWindowController document]; 56 | if (currentWindowController && [document isKindOfClass:NSClassFromString(@"IDEWorkspaceDocument")]) { 57 | return (IDEWorkspaceDocument *)document; 58 | } 59 | return nil; 60 | } 61 | 62 | + (IDESourceCodeDocument *)currentSourceCodeDocument { 63 | 64 | IDESourceCodeEditor *editor=[self currentEditor]; 65 | 66 | if ([editor isKindOfClass:NSClassFromString(@"IDESourceCodeEditor")]) { 67 | return editor.sourceCodeDocument; 68 | } 69 | 70 | if ([editor isKindOfClass:NSClassFromString(@"IDESourceCodeComparisonEditor")]) { 71 | if ([[(IDESourceCodeComparisonEditor*)editor primaryDocument] isKindOfClass:NSClassFromString(@"IDESourceCodeDocument")]) { 72 | return (id)[(IDESourceCodeComparisonEditor *)editor primaryDocument]; 73 | } 74 | } 75 | 76 | return nil; 77 | } 78 | 79 | + (IDESourceControlWorkspaceMonitor *)sourceControlMonitor 80 | { 81 | return [XCPCModel currentWorkspaceDocument].workspace.sourceControlWorkspaceMonitor; 82 | 83 | } 84 | 85 | + (NSString *)currentProjectName 86 | { 87 | NSString *filePath = [XCPCModel currentWorkspaceDocument].workspace.name; 88 | //NSString *projectDir= [filePath stringByDeletingLastPathComponent]; 89 | return filePath; 90 | } 91 | 92 | //TESTME: some tests! 93 | /* 94 | + (NSString*)scannedStrings { 95 | NSArray* prefsStrings = [[NSUserDefaults standardUserDefaults] objectForKey:kXToDoTagsKey]; 96 | NSMutableArray* escapedStrings = [NSMutableArray arrayWithCapacity:[prefsStrings count]]; 97 | 98 | for (NSString* origStr in prefsStrings) { 99 | NSMutableString* str = [NSMutableString string]; 100 | 101 | for (NSUInteger i=0; i<[origStr length]; i++) { 102 | unichar c = [origStr characterAtIndex:i]; 103 | 104 | if (!isalpha(c) && ! isnumber(c)) { 105 | [str appendFormat:@"\\%C", c]; 106 | } else { 107 | [str appendFormat:@"%C", c]; 108 | } 109 | } 110 | 111 | [str appendFormat:@"\\:"]; 112 | 113 | [escapedStrings addObject:str]; 114 | } 115 | 116 | return [escapedStrings componentsJoinedByString:@"|"]; 117 | } 118 | */ 119 | 120 | typedef void(^OnFindedItem)(NSString *fullPath, BOOL isDirectory, BOOL *skipThis, BOOL *stopAll); 121 | + (void) scanFolder:(NSString*)folder findedItemBlock:(OnFindedItem)findedItemBlock 122 | { 123 | BOOL stopAll = NO; 124 | 125 | NSFileManager* localFileManager = [[NSFileManager alloc] init]; 126 | NSDirectoryEnumerationOptions option = NSDirectoryEnumerationSkipsHiddenFiles | NSDirectoryEnumerationSkipsPackageDescendants; 127 | NSDirectoryEnumerator* directoryEnumerator = [localFileManager enumeratorAtURL:[NSURL fileURLWithPath:folder] 128 | includingPropertiesForKeys:nil 129 | options:option 130 | errorHandler:nil]; 131 | for (NSURL* theURL in directoryEnumerator) 132 | { 133 | if (stopAll) 134 | { 135 | break; 136 | } 137 | 138 | NSString *fileName = nil; 139 | [theURL getResourceValue:&fileName forKey:NSURLNameKey error:NULL]; 140 | 141 | NSNumber *isDirectory = nil; 142 | [theURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:NULL]; 143 | 144 | BOOL skinThis = NO; 145 | 146 | BOOL directory = [isDirectory boolValue]; 147 | 148 | findedItemBlock([theURL path], directory, &skinThis, &stopAll); 149 | 150 | if (skinThis) 151 | { 152 | [directoryEnumerator skipDescendents]; 153 | } 154 | } 155 | } 156 | 157 | 158 | + (NSArray *)removeSubDirs:(NSArray*)dirs 159 | { 160 | // TODO: 161 | return dirs; 162 | } 163 | 164 | + (NSSet *)lowercaseFileTypes:(NSSet *)fileTypes 165 | { 166 | NSMutableSet *set = [NSMutableSet setWithCapacity:[fileTypes count]]; 167 | for (NSString * fileType in fileTypes) 168 | { 169 | [set addObject:[fileType lowercaseString]]; 170 | } 171 | return set; 172 | } 173 | 174 | + (NSArray*)findFileNameWithProjectPath:(NSString *)projectPath 175 | includeDirs:(NSArray *)includeDirs 176 | excludeDirs:(NSArray *)excludeDirs 177 | fileTypes:(NSSet *)fileTypes 178 | { 179 | includeDirs = [XCPCModel explandRootPathMacros:includeDirs projectPath:projectPath]; 180 | includeDirs = [XCPCModel removeSubDirs:includeDirs]; 181 | excludeDirs = [XCPCModel explandRootPathMacros:excludeDirs projectPath:projectPath]; 182 | excludeDirs = [XCPCModel removeSubDirs:excludeDirs]; 183 | fileTypes = [XCPCModel lowercaseFileTypes:fileTypes]; 184 | NSMutableArray *allFilePaths = [NSMutableArray arrayWithCapacity:1000]; 185 | for (NSString *includeDir in includeDirs) 186 | { 187 | [XCPCModel scanFolder:includeDir findedItemBlock:^(NSString *fullPath, BOOL isDirectory, BOOL *skipThis, BOOL *stopAll) { 188 | if (isDirectory) 189 | { 190 | for (NSString *excludeDir in excludeDirs) 191 | { 192 | if ([fullPath hasPrefix:excludeDir]) 193 | { 194 | *skipThis = YES; 195 | return; 196 | } 197 | } 198 | } 199 | else 200 | { 201 | if ([fileTypes containsObject:[[fullPath pathExtension] lowercaseString]]) 202 | { 203 | [allFilePaths addObject:fullPath]; 204 | } 205 | } 206 | 207 | }]; 208 | } 209 | return allFilePaths; 210 | } 211 | 212 | 213 | 214 | 215 | + (NSString *) _settingDirectory 216 | { 217 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 218 | // TODO [path count] == 0 219 | NSString *settingDirectory = [(NSString *)[paths objectAtIndex:0] stringByAppendingPathComponent:@"XCPullRequest"]; 220 | if ([[NSFileManager defaultManager] fileExistsAtPath:settingDirectory] == NO) 221 | { 222 | [[NSFileManager defaultManager] createDirectoryAtPath:settingDirectory 223 | withIntermediateDirectories:YES 224 | attributes:nil 225 | error:NULL]; 226 | } 227 | return settingDirectory; 228 | } 229 | 230 | + (NSString *) _tempFileDirectory 231 | { 232 | NSString *tempFileDirectory = [[XCPCModel _settingDirectory] stringByAppendingPathComponent:@"Temp"]; 233 | if ([[NSFileManager defaultManager] fileExistsAtPath:tempFileDirectory] == NO) 234 | { 235 | [[NSFileManager defaultManager] createDirectoryAtPath:tempFileDirectory 236 | withIntermediateDirectories:YES 237 | attributes:nil 238 | error:NULL]; 239 | } 240 | return tempFileDirectory; 241 | } 242 | 243 | + (void) cleanAllTempFiles 244 | { 245 | [XCPCModel scanFolder:[XCPCModel _tempFileDirectory] findedItemBlock:^(NSString *fullPath, BOOL isDirectory, BOOL *skipThis, BOOL *stopAll) { 246 | [[NSFileManager defaultManager] removeItemAtPath:fullPath error:nil]; 247 | }]; 248 | } 249 | 250 | + (NSString *)currentProjectFile 251 | { 252 | NSString *filePath = [[XCPCModel currentWorkspaceDocument].workspace.representingFilePath.fileURL path]; 253 | //NSString *projectDir= [filePath stringByDeletingLastPathComponent]; 254 | return filePath; 255 | } 256 | 257 | + (NSString *)currentRootPath 258 | { 259 | NSString *filePath = [[XCPCModel currentWorkspaceDocument].workspace.representingFilePath.fileURL path]; 260 | return [filePath stringByDeletingLastPathComponent]; 261 | } 262 | 263 | + (NSString *) rootPathMacro 264 | { 265 | return [XCPCModel addPathSlash:@"$(SRCROOT)"]; 266 | } 267 | 268 | + (NSArray *) explandRootPathMacros:(NSArray *)paths projectPath:(NSString *)projectPath 269 | { 270 | if (projectPath == nil) 271 | { 272 | return paths; 273 | } 274 | 275 | NSMutableArray *explandPaths = [NSMutableArray arrayWithCapacity:[paths count]]; 276 | for (NSString *path in paths) { 277 | [explandPaths addObject:[XCPCModel explandRootPathMacro:path projectPath:projectPath]]; 278 | } 279 | return explandPaths; 280 | } 281 | 282 | + (NSString *) addPathSlash:(NSString *)path 283 | { 284 | if ([path length] > 0) 285 | { 286 | if ([path characterAtIndex:([path length] - 1)] != '/') 287 | { 288 | path = [NSString stringWithFormat:@"%@/", path]; 289 | } 290 | } 291 | return path; 292 | } 293 | 294 | + (NSString *) explandRootPathMacro:(NSString *)path projectPath:(NSString *)projectPath 295 | { 296 | projectPath = [XCPCModel addPathSlash:projectPath]; 297 | path = [path stringByReplacingOccurrencesOfString:[XCPCModel rootPathMacro] withString:projectPath]; 298 | 299 | return [XCPCModel addPathSlash:path]; 300 | } 301 | 302 | + (NSString *) settingFilePathByProjectName:(NSString *)projectName 303 | { 304 | NSString *settingDirectory = [XCPCModel _settingDirectory]; 305 | NSString *fileName = [projectName length] ? projectName : @"Test.xcodeproj"; 306 | return [settingDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist",fileName]]; 307 | } 308 | 309 | 310 | 311 | + (XCPCProjectSetting *) projectSettingByProjectName:(NSString *)projectName 312 | { 313 | static NSMutableDictionary *projectName2ProjectSetting = nil; 314 | if (projectName2ProjectSetting == nil) 315 | { 316 | projectName2ProjectSetting = [[NSMutableDictionary alloc] init]; 317 | } 318 | 319 | if (projectName != nil) 320 | { 321 | id object = [projectName2ProjectSetting objectForKey:projectName]; 322 | if ([object isKindOfClass:[XCPCProjectSetting class]]) 323 | { 324 | return object; 325 | } 326 | } 327 | 328 | NSString *fullPath = [XCPCModel settingFilePathByProjectName:projectName]; 329 | XCPCProjectSetting *projectSetting = nil; 330 | @try { 331 | projectSetting = [NSKeyedUnarchiver unarchiveObjectWithFile:fullPath]; 332 | } 333 | @catch (NSException *exception) { 334 | } 335 | if ([projectSetting isKindOfClass:[projectSetting class]] == NO){ 336 | projectSetting = nil; 337 | } 338 | 339 | if (projectSetting == nil) { 340 | projectSetting = [XCPCProjectSetting defaultProjectSetting]; 341 | } 342 | if ((projectSetting != nil) && (projectName != nil)) 343 | { 344 | [projectName2ProjectSetting setObject:projectSetting forKey:projectName]; 345 | } 346 | return projectSetting; 347 | } 348 | 349 | + (void) saveProjectSetting:(XCPCProjectSetting *)projectSetting ByProjectName:(NSString *)projectName 350 | { 351 | if (projectSetting == nil) 352 | { 353 | return; 354 | } 355 | @try { 356 | NSString *filePath = [XCPCModel settingFilePathByProjectName:projectName]; 357 | [NSKeyedArchiver archiveRootObject:projectSetting 358 | toFile:filePath]; 359 | filePath = nil; 360 | } 361 | @catch (NSException *exception) { 362 | NSLog(@"saveProjectSetting:exception:%@", exception); 363 | } 364 | NSLog(@"haha"); 365 | } 366 | 367 | @end 368 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCPCProjectSetting.h: -------------------------------------------------------------------------------- 1 | // 2 | // GMProjectSetting 3 | // XToDo 4 | // 5 | // Created by shuice on 2014-03-08. 6 | // Copyright (c) 2014. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XCPCProjectSetting : NSObject 12 | @property NSArray *includeDirs; 13 | @property NSArray *excludeDirs; 14 | + (XCPCProjectSetting *) defaultProjectSetting; 15 | - (NSString *) firstIncludeDir; 16 | @end -------------------------------------------------------------------------------- /XCProfileCleaner/XCPCProjectSetting.m: -------------------------------------------------------------------------------- 1 | // 2 | // GMProjectSetting 3 | // XToDo 4 | // 5 | // Created by shuice on 2014-03-08. 6 | // Copyright (c) 2014. All rights reserved. 7 | // 8 | 9 | #import "XCPCProjectSetting.h" 10 | #import "XCPCModel.h" 11 | 12 | @implementation XCPCProjectSetting 13 | 14 | - (void)encodeWithCoder:(NSCoder *)aCoder 15 | { 16 | [aCoder encodeObject:self.includeDirs ? self.includeDirs : @[] forKey:@"includeDirs"]; 17 | [aCoder encodeObject:self.excludeDirs ? self.excludeDirs : @[] forKey:@"excludeDirs"]; 18 | } 19 | 20 | - (id)initWithCoder:(NSCoder *)aDecoder 21 | { 22 | self = [super init]; 23 | if (self) 24 | { 25 | self.includeDirs = [aDecoder decodeObjectForKey:@"includeDirs"]; 26 | self.excludeDirs = [aDecoder decodeObjectForKey:@"excludeDirs"]; 27 | } 28 | return self; 29 | } 30 | 31 | + (XCPCProjectSetting *) defaultProjectSetting 32 | { 33 | XCPCProjectSetting *projectSetting = [[XCPCProjectSetting alloc] init]; 34 | projectSetting.includeDirs = @[[XCPCModel rootPathMacro]]; 35 | projectSetting.excludeDirs = @[[XCPCModel addPathSlash:[[XCPCModel rootPathMacro] stringByAppendingPathComponent:@"Pods"]]]; 36 | return projectSetting; 37 | } 38 | 39 | - (NSString *)firstIncludeDir 40 | { 41 | NSString *firstDir = [self.includeDirs count] ? [self.includeDirs objectAtIndex:0] : @""; 42 | if ([firstDir length] == 0) 43 | { 44 | firstDir = [XCPCModel rootPathMacro]; 45 | } 46 | return firstDir; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCPCWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // XTWindowController.h 3 | // XCProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 7/14/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | 13 | @interface XCPCWindowController : NSWindowController 14 | { 15 | IBOutlet NSButton *scanProfilesCheckbox; 16 | IBOutlet NSButton *scanProjectsCheckbox; 17 | IBOutlet NSButton *alertMeCheckbox; 18 | } 19 | 20 | @property (nonatomic, assign) id delegate; 21 | @end 22 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCPCWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // XTWindowController.m 3 | // XCProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 7/14/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | #import "XCPCWindowController.h" 10 | #import "XCPCModel.h" 11 | @interface XCPCWindowController () 12 | 13 | @end 14 | 15 | @implementation XCPCWindowController 16 | 17 | 18 | - (void)windowWillClose:(NSNotification *)notification 19 | { 20 | //save prefs 21 | } 22 | 23 | - (void)awakeFromNib 24 | { 25 | scanProfilesCheckbox.toolTip = @"When enabled XCProfileCleaner will scan any new provisioning profiles that are opened and see if any of the currently opened projects need to be updated \ 26 | or whether or not they are invalid and need to be updated"; 27 | scanProjectsCheckbox.toolTip = @"When enabled XCProfileCleaner will scan any Xcode projects that open to see if they are currently up to date with a valid cert and provisioning profile \ 28 | if not it will search for a valid profile and update the project automatically"; 29 | alertMeCheckbox.toolTip = @"Alert me when either of the two options above make project changes"; 30 | 31 | } 32 | 33 | 34 | - (id)initWithWindow:(NSWindow *)window 35 | { 36 | self = [super initWithWindow:window]; 37 | if (self) { 38 | // Initialization code here. 39 | } 40 | return self; 41 | } 42 | 43 | - (void)windowDidLoad 44 | { 45 | [super windowDidLoad]; 46 | 47 | 48 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 49 | } 50 | 51 | - (void)windowDidBecomeKey:(NSNotification *)notification 52 | { 53 | //nada 54 | } 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCPCWindowController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 44 | 60 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCProfileCleaner-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.nito.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | DVTPlugInCompatibilityUUIDs 26 | 27 | C4A681B0-4A26-480E-93EC-1218098B9AA0 28 | 640F884E-CE55-4B40-87C0-8869546CAB7A 29 | 37B30044-3B14-46BA-ABAA-F01000C27B63 30 | A2E4D43F-41F4-4FB9-BB94-7177011C9AED 31 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 32 | 7265231C-39B4-402C-89E1-16167C4CC990 33 | F41BD31E-2683-44B8-AE7F-5F09E919790E 34 | E71C2CFE-BFD8-4044-8F06-00AE685A406C 35 | 0420B86A-AA43-4792-9ED0-6FE0F2B16A13 36 | ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C 37 | E0A62D1F-3C18-4D74-BFE5-A4167D643966 38 | 8A66E736-A720-4B3C-92F1-33D9962C69DF 39 | DA4FDFD8-C509-4D8B-8B55-84A7B66AE701 40 | 41 | NSPrincipalClass 42 | XCProfileCleaner 43 | XC4Compatible 44 | 45 | XC5Compatible 46 | 47 | XCGCReady 48 | 49 | XCPluginHasUI 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCProfileCleaner-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import "Defines.h" 10 | #endif 11 | -------------------------------------------------------------------------------- /XCProfileCleaner/XCProfileCleaner.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCProfileCleaner.h 3 | // XCProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 8/7/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface XCProfileCleaner : NSObject 12 | 13 | @property (nonatomic, strong) NSMutableArray *scannedProjects; 14 | 15 | @end -------------------------------------------------------------------------------- /XCProfileCleaner/XCProfileCleaner.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCProfileCleaner.m 3 | // XCProfileCleaner 4 | // 5 | // Created by Kevin Bradley on 8/7/14. 6 | // Copyright (c) 2014 nito. All rights reserved. 7 | // 8 | 9 | #import "XCProfileCleaner.h" 10 | #import "JRSwizzle.h" 11 | #import 12 | #import "XCPCModel.h" 13 | #import "KBProfileHelper.h" 14 | #import "XCPCWindowController.h" 15 | 16 | @interface NSString (additions) 17 | 18 | - (NSString *)standardBundleID; 19 | 20 | @end 21 | 22 | @implementation NSString (additions) 23 | 24 | - (NSString *)standardBundleID 25 | { 26 | NSRange stringRange = [self rangeOfString:@"."]; 27 | NSInteger offsetLocation = (stringRange.location+1); 28 | 29 | return [self substringFromIndex:offsetLocation]; 30 | } 31 | 32 | @end 33 | 34 | static XCProfileCleaner *sharedPlugin; 35 | 36 | @interface XCProfileCleaner() 37 | 38 | @property (nonatomic, strong) NSBundle *bundle; 39 | @property (nonatomic, strong) XCPCWindowController* windowController; 40 | @property (nonatomic, strong) NSMutableArray *alertDetails; 41 | @end 42 | 43 | @implementation XCProfileCleaner 44 | 45 | + (void)initialize 46 | { 47 | NSDictionary *appDefaults = @{kPCAlertOnProjectChanges: [NSNumber numberWithBool:TRUE], kPCScanOpenedProfiles: [NSNumber numberWithBool:TRUE], kPCScanOpenedProjects: [NSNumber numberWithBool:TRUE] }; 48 | [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults]; 49 | } 50 | 51 | + (void)pluginDidLoad:(NSBundle *)plugin 52 | { 53 | static dispatch_once_t onceToken; 54 | NSString *currentApplicationName = [[NSBundle mainBundle] infoDictionary][@"CFBundleName"]; 55 | if ([currentApplicationName isEqual:@"Xcode"]) { 56 | dispatch_once(&onceToken, ^{ 57 | sharedPlugin = [[self alloc] initWithBundle:plugin]; 58 | }); 59 | } 60 | } 61 | 62 | - (void)XCPDelayedSetup 63 | { 64 | NSMenuItem *menuItem = [[NSApp mainMenu] itemWithTitle:@"Product"]; 65 | if (menuItem) { 66 | [[menuItem submenu] addItem:[NSMenuItem separatorItem]]; 67 | 68 | NSMenuItem *ppMenuItem = [[NSMenuItem alloc] init]; 69 | [ppMenuItem setTitle:@"Provisioning Profiles"]; 70 | 71 | NSMenu *fullMenu = [[NSMenu alloc] initWithTitle:@""]; 72 | NSMenuItem *actionMenuItem = [[NSMenuItem alloc] initWithTitle:@"Clean provisioning profiles..." action:@selector(cleanProfiles) keyEquivalent:@"c"]; 73 | [actionMenuItem setKeyEquivalentModifierMask: NSCommandKeyMask | NSShiftKeyMask | NSAlternateKeyMask]; 74 | [actionMenuItem setTarget:self]; 75 | [fullMenu addItem:actionMenuItem]; 76 | NSMenuItem *prefItem = [[NSMenuItem alloc] initWithTitle:@"Show Preferences..." action:@selector(showPrefs:) keyEquivalent:@""]; 77 | [prefItem setTarget:self]; 78 | [fullMenu addItem:prefItem]; 79 | 80 | [ppMenuItem setSubmenu:fullMenu]; 81 | [[menuItem submenu] addItem:ppMenuItem]; 82 | 83 | } 84 | static dispatch_once_t onceToken2; 85 | dispatch_once(&onceToken2, ^{ 86 | 87 | //turn off the swizzling for now, everything inside there is experimental. 88 | [self doSwizzlingScience]; 89 | }); 90 | } 91 | 92 | - (id)initWithBundle:(NSBundle *)plugin 93 | { 94 | if (self = [super init]) { 95 | // reference to plugin's bundle, for resource acccess 96 | self.bundle = plugin; 97 | 98 | if (self.windowController == nil) { 99 | XCPCWindowController* wc = [[XCPCWindowController alloc] initWithWindowNibName:@"XCPCWindowController"]; 100 | self.windowController = wc; 101 | 102 | } 103 | self.alertDetails = [NSMutableArray new]; 104 | self.scannedProjects = [NSMutableArray new]; 105 | // Create menu items, initialize UI, etc. 106 | 107 | 108 | //experimental right now 109 | 110 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(workspaceScanned:) name:@"IDESourceControlDidScanWorkspaceNotification" object:nil]; 111 | 112 | 113 | [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(XCPDelayedSetup) userInfo:nil repeats:FALSE]; 114 | 115 | } 116 | return self; 117 | } 118 | 119 | - (void)showPrefs:(id)sender 120 | { 121 | [self.windowController.window makeKeyAndOrderFront:nil]; 122 | } 123 | 124 | - (void)workspaceScanned:(NSNotification *)n 125 | { 126 | if ([UD boolForKey:kPCScanOpenedProjects] == TRUE) 127 | { 128 | NSString *scannedWorkspace = [[[[[n object] workspace] representingFilePath] fileURL] path]; 129 | NSLog(@"#### scanned workspace: %@", scannedWorkspace); 130 | id project = [objc_getClass("PBXProject") projectWithFile:scannedWorkspace]; 131 | NSString *productName = [project name]; 132 | 133 | 134 | 135 | if ([[self scannedProjects] containsObject:productName]) 136 | { 137 | NSLog(@"#### already scanned %@", productName); 138 | return; 139 | } 140 | 141 | if (productName != nil) { 142 | [[self scannedProjects] addObject:productName]; 143 | } 144 | NSLog(@"#### evaluating project named: %@", productName); 145 | id mainTarget = [project targetNamed: productName]; 146 | 147 | BOOL iphoneRequired = [[mainTarget productSettingForKey:@"LSRequiresIPhoneOS"] boolValue]; 148 | 149 | if (iphoneRequired == TRUE) 150 | { 151 | [self validateProductTargetConfig:@"Debug" onTarget:mainTarget]; 152 | [self validateProductTargetConfig:@"Release" onTarget:mainTarget]; 153 | } 154 | 155 | [self showUpdateAlert]; 156 | } 157 | 158 | } 159 | 160 | - (void)validateProductTargetConfig:(NSString *)targetConfig onTarget:(id)mainTarget 161 | { 162 | NSArray *devCerts = [KBProfileHelper devCertsFull]; 163 | BOOL iphoneRequired = [[mainTarget productSettingForKey:@"LSRequiresIPhoneOS"] boolValue]; 164 | id targetContext = [mainTarget cachedPropertyInfoContextForConfigurationNamed:targetConfig]; 165 | NSString *productID = [mainTarget productSettingForKey:@"CFBundleIdentifier"]; 166 | 167 | NSString *provProfile = [targetContext expandedValueForPropertyNamed:@"PROVISIONING_PROFILE"]; 168 | NSString *codeSignID = [targetContext expandedValueForPropertyNamed:@"CODE_SIGN_IDENTITY"]; 169 | 170 | if (provProfile != nil || iphoneRequired == TRUE) // we dont care otherwise, no mac support yet 171 | { 172 | /* if codesignID length is 0 then it probably means they have "Automatic" set for the developer choice, we don't want to disrupt that and am 173 | not 100% sure how to even check that setting "properly". */ 174 | 175 | if ([devCerts containsObject:codeSignID] || codeSignID.length == 0) 176 | { 177 | NSLog(@"#### current project codesign value is valid or automatically selected: %@", codeSignID); 178 | 179 | } else { 180 | 181 | NSLog(@"#### %@ is not a valid code sign ID for project: %@ Certificate expiration is possible explanation", codeSignID, [mainTarget name]); 182 | 183 | } 184 | 185 | NSString *provProfilePath = [KBProfileHelper pathFromUUID:provProfile]; 186 | if (![[NSFileManager defaultManager] fileExistsAtPath:provProfilePath]) 187 | { 188 | NSLog(@"### provisioning profile %@ is missing!!!", provProfilePath); 189 | // return; 190 | } 191 | 192 | NSDictionary *currentProvProfile = [KBProfileHelper provisioningDictionaryFromFilePath:[KBProfileHelper pathFromUUID:provProfile]]; 193 | NSString *myCodesignID = currentProvProfile[@"CODE_SIGN_IDENTITY"]; 194 | if (myCodesignID == nil) 195 | { 196 | NSLog(@"### current provisioning profile: %@ is invalid", currentProvProfile); 197 | 198 | NSDictionary *validProfile = [KBProfileHelper validProfileForID:productID withTarget:targetConfig]; 199 | if (validProfile != nil) 200 | { 201 | NSLog(@"### found a valid profile, should change to it: %@", validProfile[@"Name"]); 202 | 203 | id conditionSet = [ objc_getClass("DVTMacroDefinitionConditionSet") conditionSetFromStringRepresentation:@"[sdk=iphoneos*]" getBaseMacroName:nil error:nil]; 204 | [targetContext setValue:validProfile[@"UUID"] forPropertyName:@"PROVISIONING_PROFILE"]; 205 | [targetContext setValue:validProfile[@"CODE_SIGN_IDENTITY"] forPropertyName:@"CODE_SIGN_IDENTITY"]; 206 | [targetContext setValue:validProfile[@"CODE_SIGN_IDENTITY"] forPropertyName:@"CODE_SIGN_IDENTITY" conditionSet:conditionSet]; 207 | 208 | NSDictionary *updatedProfile = @{@"projectName": [mainTarget name], @"profileName": validProfile[@"Name"], @"reason": @"Replacing invalid cert/profile", @"target": targetConfig}; 209 | 210 | [self.alertDetails addObject:updatedProfile]; 211 | 212 | 213 | } 214 | } 215 | 216 | 217 | 218 | } 219 | } 220 | /* 221 | 222 | TeamName, 223 | ExpirationDate, 224 | TimeToLive, 225 | AppIDName, 226 | CreationDate, 227 | DeveloperCertificates, 228 | ProvisionedDevices, 229 | Name, 230 | ApplicationIdentifierPrefix, 231 | Version, 232 | UUID, 233 | TeamIdentifier, 234 | Entitlements { 235 | application-identifier, 236 | get-task-allow, 237 | } 238 | 239 | */ 240 | 241 | /* 242 | 243 | all the magic happens here when it comes to the plugin processing profiles as they get opened by Xcode. 244 | 245 | */ 246 | 247 | //IDESourceControlDidScanWorkspaceNotification 248 | 249 | - (void)processProfile:(NSString *)theFile 250 | { 251 | NSLog(@"#### XCProfileCleaner Processing Profile: %@", theFile); 252 | 253 | Class pbxProjClass = objc_getClass("PBXProject"); 254 | Class macroClass = objc_getClass("DVTMacroDefinitionConditionSet"); 255 | NSArray *devCerts = [KBProfileHelper devCertsFull]; 256 | NSDictionary *openedProfile = [KBProfileHelper provisioningDictionaryFromFilePath:theFile]; 257 | NSString *openProfileName = openedProfile[@"Name"]; 258 | NSString *target = openedProfile[@"Target"]; 259 | NSString *incomingAppID = [[openedProfile[@"Entitlements"] objectForKey:@"application-identifier"] standardBundleID]; 260 | 261 | //modify debug/release target settings depending on whether the profile is a Distribution or Dev profile. 262 | 263 | if (target == nil) target = @"Debug"; 264 | 265 | NSArray *openedProjects = [pbxProjClass openProjects]; //eventually cycle through all open projects 266 | 267 | //remeber to make sure if it isnt currently codesigning, not to add it to projects that dont currently have it toggled on!! 268 | 269 | //the above note isn't important at the current moment since we are only supporting iOS profiles with the initial version. 270 | 271 | for (id project in openedProjects) 272 | { 273 | BOOL currentProfileValid = FALSE; 274 | BOOL incomingProfileValid = FALSE; 275 | BOOL codesignIDChanged = TRUE; 276 | 277 | NSString *productName = [project name]; 278 | 279 | NSLog(@"#### evaluating project named: %@", productName); 280 | 281 | id conditionSet = [macroClass conditionSetFromStringRepresentation:@"[sdk=iphoneos*]" getBaseMacroName:nil error:nil]; 282 | id cmdTarget = [project targetNamed: productName]; 283 | NSString *productID = [cmdTarget productSettingForKey:@"CFBundleIdentifier"]; 284 | BOOL iphoneRequired = [[cmdTarget productSettingForKey:@"LSRequiresIPhoneOS"] boolValue]; 285 | id targetContext = [cmdTarget cachedPropertyInfoContextForConfigurationNamed:target]; 286 | NSString *provProfile = [targetContext expandedValueForPropertyNamed:@"PROVISIONING_PROFILE"]; 287 | NSString *codeSignID = [targetContext expandedValueForPropertyNamed:@"CODE_SIGN_IDENTITY"]; 288 | 289 | /* if codesignID length is 0 then it probably means they have "Automatic" set for the developer choice, we don't want to disrupt that and am 290 | not 100% sure how to even check that setting "properly". */ 291 | 292 | if ([devCerts containsObject:codeSignID] || codeSignID.length == 0) 293 | { 294 | NSLog(@"#### current project codesign value is valid or automatically selected: %@", codeSignID); 295 | currentProfileValid = TRUE; 296 | 297 | } else { 298 | 299 | NSLog(@"#### %@ is not a valid code sign ID. Certificate expiration is possible explanation", codeSignID); 300 | currentProfileValid = FALSE; 301 | } 302 | 303 | /* 304 | 305 | validate the productID with the one in the provisioning profile to make sure its even applicable to this current project 306 | 307 | do this by matching the incoming ID (stripped to a standard product ID) against our current projects CFBundleIdentifier 308 | 309 | */ 310 | if (([incomingAppID isEqualToString:productID] || [incomingAppID isEqualToString:@"*"]) && iphoneRequired) 311 | { 312 | NSLog(@"#### the incoming profile is for iOS and ID is a wildcard OR ID matches our current application: %@", incomingAppID); 313 | 314 | 315 | //get the details of our current provisioning profile that this project is using to determine if its valid and to get the actual profile name. 316 | 317 | NSDictionary *currentProvProfile = [KBProfileHelper provisioningDictionaryFromFilePath:[KBProfileHelper pathFromUUID:provProfile]]; 318 | 319 | NSString *openID = openedProfile[@"CODE_SIGN_IDENTITY"]; //we append the value when we create the provisioning dictionary IF we find a matching valid certificate in keychain. 320 | 321 | if (openID != nil) //we have a valid profile because this key exists in the dictionary! 322 | { 323 | if ([openID isEqualToString:codeSignID]) 324 | { 325 | NSLog(@"#### ids already match!"); 326 | codesignIDChanged = FALSE; 327 | } 328 | 329 | NSLog(@"### We have a valid codesign ID for this new cert!"); 330 | incomingProfileValid = TRUE; 331 | 332 | } else { 333 | 334 | NSLog(@"### incoming profile is invalid you don't have any of the following certs: %@", openedProfile[@"CodeSignArray"]); 335 | incomingProfileValid = FALSE; 336 | return; 337 | 338 | } 339 | 340 | /* 341 | 342 | provisioning profiles are organized in the ~/Library/MobileDevice/Provisioning Profiles folder the are organized by their UUID, Xcode 343 | stores this UUID in target/projects property PROVISIONING_PROFILE key, if we want to update it thats how we do it. with the UUID. 344 | 345 | 346 | */ 347 | 348 | NSString *incomingProfile = openedProfile[@"UUID"]; 349 | 350 | NSString *profileName = currentProvProfile[@"Name"]; 351 | 352 | //compare our current provisioing profile in this project with the name of the incoming one, if they match its a newer version and we make sure Xcode updates/propagates this change. 353 | 354 | //FIXME: should probably do a sanity compare to make sure this profile is a) newer b) has more UDID's 355 | 356 | if ([openProfileName isEqualToString:profileName]) 357 | { 358 | NSLog(@"#### Project: %@ already uses the profile: %@! pointing towards the new one.", productName, profileName); 359 | 360 | if (incomingProfileValid == TRUE) 361 | { 362 | NSLog(@"#### updating project to new profile!"); 363 | [targetContext setValue:incomingProfile forPropertyName:@"PROVISIONING_PROFILE"]; 364 | 365 | if (codesignIDChanged == TRUE) 366 | { 367 | [targetContext setValue:openID forPropertyName:@"CODE_SIGN_IDENTITY"]; 368 | [targetContext setValue:openID forPropertyName:@"CODE_SIGN_IDENTITY" conditionSet:conditionSet]; 369 | } 370 | 371 | 372 | NSDictionary *updatedProfile = @{@"projectName": productName, @"profileName": openProfileName, @"reason": @"Newer profile", @"target": target}; 373 | 374 | [self.alertDetails addObject:updatedProfile]; 375 | 376 | return; 377 | 378 | } 379 | 380 | } 381 | 382 | /* 383 | if we have gotten this far its because the projects current and incoming profile dont match, but still want to check to see if the current one is valid 384 | if it is not then we are being "smart" and updating this project to use a valid profile. 385 | 386 | the reasoning here is we have passed all the rigorous validation checks (is it a wildcard/matching app ID, does it have a valid cert, and (for now) is it 387 | iOS based. 388 | 389 | 390 | */ 391 | 392 | if (currentProfileValid == FALSE) 393 | { 394 | NSLog(@"the current profile selected for this project is currently invalid: %@ Expected ID: %@", provProfile, codeSignID); 395 | 396 | [targetContext setValue:incomingProfile forPropertyName:@"PROVISIONING_PROFILE"]; 397 | 398 | if (codesignIDChanged == TRUE) 399 | { 400 | [targetContext setValue:openID forPropertyName:@"CODE_SIGN_IDENTITY"]; 401 | [targetContext setValue:openID forPropertyName:@"CODE_SIGN_IDENTITY" conditionSet:conditionSet]; 402 | } 403 | 404 | NSDictionary *updatedProfile = @{@"projectName": productName, @"profileName": openProfileName, @"reason": @"Expired/Invalid profile replaced", @"target": target}; 405 | 406 | [self.alertDetails addObject:updatedProfile]; 407 | 408 | } 409 | 410 | 411 | } else { 412 | 413 | NSLog(@"#### %@ is not iOS based, XCProfileCleaner is currently iOS only",productName ); 414 | 415 | } 416 | } 417 | 418 | [self showUpdateAlert]; 419 | 420 | 421 | 422 | } 423 | 424 | - (NSString *)alertDetailString 425 | { 426 | NSMutableString *newString = [NSMutableString new]; 427 | for (NSDictionary *detail in self.alertDetails) 428 | { 429 | NSString *detailString = [NSString stringWithFormat:@"Project '%@' target '%@' was updated to profile: '%@' for reason: %@\n\n", detail[@"projectName"], detail[@"target"], detail[@"profileName"], detail[@"reason"]]; 430 | [newString appendString:detailString]; 431 | } 432 | 433 | [newString appendString:@"\nThese settings can be changed in Product->Provisioning Profiles->Show Preferences"]; 434 | 435 | return newString; 436 | } 437 | 438 | - (void)showUpdateAlert 439 | { 440 | if ([UD boolForKey:kPCAlertOnProjectChanges] == TRUE) 441 | { 442 | 443 | if (self.alertDetails.count > 0) 444 | { 445 | NSAlert *theAlert = [NSAlert alertWithMessageText:@"Projects Updated" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@", [self alertDetailString]]; 446 | [theAlert setShowsSuppressionButton:TRUE]; 447 | [theAlert runModal]; 448 | NSInteger showButton = [[theAlert suppressionButton] state]; 449 | if (showButton == 1) 450 | { 451 | [UD setBool:false forKey:kPCAlertOnProjectChanges]; 452 | } 453 | } 454 | } 455 | 456 | [self.alertDetails removeAllObjects]; 457 | } 458 | 459 | /* 460 | 461 | Extra note: 462 | 463 | Limiting to iOS only right now removes the extra sanity check of whether or not the project even uses code signing to begin with. and since code signing is required 464 | to officially run anything on an iOS device, we don't need to check to see if they use code signing as is, they don't have a choice! 465 | 466 | so to add Mac support (it /should/ be rather trivial) 467 | 468 | 1. add "provisionprofile" to extensions checked in the openFiles: replacement 469 | 2. add an extra check to make sure you aren't adding codesign to a project that doesn't currently have it on / want to have it on 470 | 3. pretty sure the check for whether your a Debug/Release profile gets trickier to 471 | 472 | on that note, adding mac support isnt a high priority. never really agreed with the whole concept of adding a walled garden where one wasn't necessary. 473 | 474 | 475 | */ 476 | 477 | - (BOOL)newOurApplication:(NSApplication *)sender openFiles:(NSArray *)filenames 478 | { 479 | BOOL orig = [self newOurApplication:sender openFiles:filenames]; // call original method; 480 | for (NSString *theFile in filenames) 481 | { 482 | if ([[[theFile pathExtension] lowercaseString] isEqualToString:@"mobileprovision"]) 483 | { 484 | if([UD boolForKey:kPCScanOpenedProfiles] == TRUE) 485 | { 486 | [sharedPlugin processProfile:theFile]; 487 | } 488 | } 489 | } 490 | return orig; 491 | } 492 | 493 | 494 | 495 | - (void)_setIssues:(id)arg1 forFilePath:(id)arg2 {} 496 | 497 | - (void)_ourSetIssues:(id)arg1 forFilePath:(id)arg2 { 498 | 499 | NSLog(@"issuesClass: %@", NSStringFromClass([arg1 class])); 500 | NSLog(@"setIssues: %@ forFilePath: %@", arg1, arg2); 501 | [self _ourSetIssues:arg1 forFilePath:arg2]; 502 | 503 | } 504 | 505 | - (void)_addIssues:(id)arg1 { } 506 | 507 | - (void)_ourAddIssues:(id)arg1 { 508 | 509 | NSLog(@"issuesClass: %@", NSStringFromClass([arg1 class])); 510 | NSLog(@"_ourAddIssues: %@", arg1); 511 | [self _ourAddIssues:arg1]; 512 | 513 | } 514 | 515 | 516 | /* 517 | 518 | ws = window.document.workspace 519 | ip = ws.issueManager 520 | issueProviders = [ip valueForKey:"issueProviders"] 521 | dp = [issueProviders objectAtIndex:5] 522 | ig = ip.issueGroups 523 | */ 524 | 525 | 526 | 527 | - (void)doSwizzlingScience 528 | { 529 | 530 | Class xcAppClass = objc_getClass("IDEApplicationController"); 531 | 532 | 533 | NSError *theError = nil; 534 | 535 | BOOL swizzleScience = FALSE; 536 | 537 | Method ourFilesOpenReplacement = class_getInstanceMethod([self class], @selector(newOurApplication:openFiles:)); 538 | class_addMethod(xcAppClass, @selector(newOurApplication:openFiles:), method_getImplementation(ourFilesOpenReplacement), method_getTypeEncoding(ourFilesOpenReplacement)); 539 | 540 | swizzleScience = [xcAppClass jr_swizzleMethod:@selector(application:openFiles:) withMethod:@selector(newOurApplication:openFiles:) error:&theError]; 541 | 542 | if (swizzleScience == TRUE) 543 | { 544 | NSLog(@"IDEApplicationController application:openFiles: replaced!"); 545 | } else { 546 | 547 | NSLog(@"IDEApplicationController application:openFiles: failed to replace with error(: %@", theError); 548 | } 549 | 550 | //just messin around with adding issues. 551 | 552 | /* 553 | 554 | Class diagnosticIssueProvider = objc_getClass("IDEDiagnosticIssueProvider"); 555 | Class ifg = objc_getClass("IDEIssueFileGroup"); 556 | 557 | Method ourSetIssuesReplacement = class_getInstanceMethod([self class], @selector(_ourSetIssues:forFilePath:)); 558 | class_addMethod(diagnosticIssueProvider, @selector(_ourSetIssues:forFilePath:), method_getImplementation(ourSetIssuesReplacement), method_getTypeEncoding(ourSetIssuesReplacement)); 559 | 560 | swizzleScience = [diagnosticIssueProvider jr_swizzleMethod:@selector(_setIssues:forFilePath:) withMethod:@selector(_ourSetIssues:forFilePath:) error:&theError]; 561 | 562 | if (swizzleScience == TRUE) 563 | { 564 | NSLog(@"IDEDiagnosticIssueProvider _setIssues:forFilePath: replaced!"); 565 | } else { 566 | 567 | NSLog(@"IDEDiagnosticIssueProvider _setIssues:forFilePath: failed to replace with error(: %@", theError); 568 | } 569 | 570 | Method addIssuesReplacement = class_getInstanceMethod([self class], @selector(_ourAddIssues:)); 571 | class_addMethod(ifg, @selector(_ourAddIssues:), method_getImplementation(addIssuesReplacement), method_getTypeEncoding(addIssuesReplacement)); 572 | 573 | swizzleScience = [ifg jr_swizzleMethod:@selector(_addIssues:) withMethod:@selector(_ourAddIssues:) error:&theError]; 574 | 575 | if (swizzleScience == TRUE) 576 | { 577 | NSLog(@"IDEIssueFileGroup _addIssues: replaced!"); 578 | } else { 579 | 580 | NSLog(@"IDEIssueFileGroup _addIssues: failed to replace with error(: %@", theError); 581 | } 582 | 583 | */ 584 | } 585 | - (void)showProfileSuccessAlert 586 | { 587 | NSAlert *alert = [NSAlert alertWithMessageText:@"Profile clean completed!" defaultButton:@"Yes" alternateButton:@"No" otherButton:nil informativeTextWithFormat:@"Your provisioning profile folder has been cleaned, it is recommended to quit Xcode before you continue.\n Would you like to see the detail log?"]; 588 | NSModalResponse modalReturn = [alert runModal]; 589 | switch (modalReturn) { 590 | 591 | case NSAlertDefaultReturn: 592 | 593 | [[NSWorkspace sharedWorkspace] openFile:[KBProfileHelper mobileDeviceLog]]; 594 | break; 595 | 596 | case NSAlertAlternateReturn: 597 | 598 | break; 599 | } 600 | } 601 | 602 | // Sample Action, for menu item: 603 | - (void)cleanProfiles 604 | { 605 | NSAlert *alert = [NSAlert alertWithMessageText:@"Provisioning profile cleanup" defaultButton:@"Clean" alternateButton:@"Cancel" otherButton:@"More Info" informativeTextWithFormat:@"Cleaning your profile folder will remove any invalid or old duplicate profiles, would you like to continue?"]; 606 | 607 | KBProfileHelper *profileHelper = [[KBProfileHelper alloc] init]; 608 | 609 | NSModalResponse modalReturn = [alert runModal]; 610 | 611 | switch (modalReturn) { 612 | 613 | case NSAlertDefaultReturn: 614 | 615 | [profileHelper processProfilesWithOpen:FALSE]; 616 | [self showProfileSuccessAlert]; 617 | break; 618 | 619 | case NSAlertAlternateReturn: 620 | 621 | profileHelper = nil; 622 | break; 623 | 624 | case NSAlertOtherReturn: 625 | 626 | profileHelper = nil; 627 | [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://raw.githubusercontent.com/lechium/ProvisioningProfileCleaner/master/README.md"]]; 628 | break; 629 | 630 | } 631 | } 632 | 633 | - (void)dealloc 634 | { 635 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 636 | } 637 | 638 | @end 639 | -------------------------------------------------------------------------------- /XCProfileCleaner/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------