diff --git a/README.md b/README.md index bf744f8..300920c 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ - Search recipes content to find processors usage - Open autopkg cache folder - Create your own buttons + - Check and verify recipes files System requirements: 10.14 or later
diff --git a/RecipeBuilder.xcodeproj/project.pbxproj b/RecipeBuilder.xcodeproj/project.pbxproj index df2e25a..71fd2b6 100644 --- a/RecipeBuilder.xcodeproj/project.pbxproj +++ b/RecipeBuilder.xcodeproj/project.pbxproj @@ -12,8 +12,11 @@ 7300825F245308B600F4909F /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7300825D245308B600F4909F /* MainMenu.xib */; }; 73008268245309F200F4909F /* Buttons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73008267245309F200F4909F /* Buttons.swift */; }; 734EF16724B2582200C203C9 /* UserButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 734EF16624B2582200C203C9 /* UserButtons.swift */; }; + 7352335927CBF82C004FAA8C /* Buttons3rdParty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7352335827CBF82C004FAA8C /* Buttons3rdParty.swift */; }; + 7352335B27D2981C004FAA8C /* JamfUploaderHelpTexts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7352335A27D2981C004FAA8C /* JamfUploaderHelpTexts.swift */; }; 73631C1024587182008A6491 /* Highlightr in Frameworks */ = {isa = PBXBuildFile; productRef = 73631C0F24587182008A6491 /* Highlightr */; }; 73A9CE74247A5D2600627DA8 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73A9CE73247A5D2600627DA8 /* Functions.swift */; }; + 73B2E40D27D51F9800CFEA88 /* CheckAndVerify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73B2E40C27D51F9800CFEA88 /* CheckAndVerify.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -25,7 +28,10 @@ 73008261245308B600F4909F /* RecipeBuilder.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RecipeBuilder.entitlements; sourceTree = ""; }; 73008267245309F200F4909F /* Buttons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons.swift; sourceTree = ""; }; 734EF16624B2582200C203C9 /* UserButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserButtons.swift; sourceTree = ""; }; + 7352335827CBF82C004FAA8C /* Buttons3rdParty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Buttons3rdParty.swift; sourceTree = ""; }; + 7352335A27D2981C004FAA8C /* JamfUploaderHelpTexts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JamfUploaderHelpTexts.swift; sourceTree = ""; }; 73A9CE73247A5D2600627DA8 /* Functions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; + 73B2E40C27D51F9800CFEA88 /* CheckAndVerify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckAndVerify.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,9 +66,12 @@ isa = PBXGroup; children = ( 73008259245308B100F4909F /* AppDelegate.swift */, + 7352335A27D2981C004FAA8C /* JamfUploaderHelpTexts.swift */, 734EF16624B2582200C203C9 /* UserButtons.swift */, 73008267245309F200F4909F /* Buttons.swift */, + 7352335827CBF82C004FAA8C /* Buttons3rdParty.swift */, 73A9CE73247A5D2600627DA8 /* Functions.swift */, + 73B2E40C27D51F9800CFEA88 /* CheckAndVerify.swift */, 7300825B245308B600F4909F /* Assets.xcassets */, 7300825D245308B600F4909F /* MainMenu.xib */, 73008260245308B600F4909F /* Info.plist */, @@ -101,7 +110,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1130; - LastUpgradeCheck = 1130; + LastUpgradeCheck = 1320; ORGANIZATIONNAME = "Mikael Löfgren"; TargetAttributes = { 73008255245308B100F4909F = { @@ -149,6 +158,9 @@ files = ( 7300825A245308B100F4909F /* AppDelegate.swift in Sources */, 73008268245309F200F4909F /* Buttons.swift in Sources */, + 73B2E40D27D51F9800CFEA88 /* CheckAndVerify.swift in Sources */, + 7352335927CBF82C004FAA8C /* Buttons3rdParty.swift in Sources */, + 7352335B27D2981C004FAA8C /* JamfUploaderHelpTexts.swift in Sources */, 734EF16724B2582200C203C9 /* UserButtons.swift in Sources */, 73A9CE74247A5D2600627DA8 /* Functions.swift in Sources */, ); @@ -194,6 +206,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -255,6 +268,7 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; @@ -291,14 +305,15 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 543LXU387V; + DEVELOPMENT_TEAM = F489D96499; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/RecipeBuilder/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = se.dicom.RecipeBuilder; + MARKETING_VERSION = 1.04; + PRODUCT_BUNDLE_IDENTIFIER = se.mikaellofgren.RecipeBuilder; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -310,17 +325,18 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = RecipeBuilder/RecipeBuilder.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEVELOPMENT_TEAM = 543LXU387V; + DEVELOPMENT_TEAM = F489D96499; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = "$(SRCROOT)/RecipeBuilder/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = se.dicom.RecipeBuilder; + MARKETING_VERSION = 1.04; + PRODUCT_BUNDLE_IDENTIFIER = se.mikaellofgren.RecipeBuilder; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; diff --git a/RecipeBuilder.xcodeproj/project.xcworkspace/xcuserdata/mikael.xcuserdatad/UserInterfaceState.xcuserstate b/RecipeBuilder.xcodeproj/project.xcworkspace/xcuserdata/mikael.xcuserdatad/UserInterfaceState.xcuserstate index 5eebecd..7ef85e2 100644 Binary files a/RecipeBuilder.xcodeproj/project.xcworkspace/xcuserdata/mikael.xcuserdatad/UserInterfaceState.xcuserstate and b/RecipeBuilder.xcodeproj/project.xcworkspace/xcuserdata/mikael.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/RecipeBuilder.xcodeproj/xcshareddata/xcschemes/RecipeBuilder.xcscheme b/RecipeBuilder.xcodeproj/xcshareddata/xcschemes/RecipeBuilder.xcscheme index cb92c23..eb74559 100644 --- a/RecipeBuilder.xcodeproj/xcshareddata/xcschemes/RecipeBuilder.xcscheme +++ b/RecipeBuilder.xcodeproj/xcshareddata/xcschemes/RecipeBuilder.xcscheme @@ -1,6 +1,6 @@ - + - + @@ -15,63 +15,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - + @@ -753,23 +720,23 @@ - + - + - + - + - + @@ -781,7 +748,7 @@ - + @@ -797,9 +764,9 @@ - + - + @@ -813,7 +780,7 @@ - + @@ -822,9 +789,9 @@ - + - + @@ -833,9 +800,9 @@ - + - + @@ -847,10 +814,10 @@ - + - + @@ -923,11 +900,11 @@ - - + + - + @@ -935,7 +912,8 @@ - + + @@ -946,7 +924,10 @@ - + + + + @@ -954,535 +935,46 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1491,7 +983,7 @@ For xar it also optionally skips extracting the payload. - + @@ -1532,7 +1024,7 @@ For xar it also optionally skips extracting the payload. - + @@ -1561,7 +1053,7 @@ For xar it also optionally skips extracting the payload. - + @@ -1581,5 +1073,27 @@ For xar it also optionally skips extracting the payload. + + + + + + + + + + + + + + + + + + + + + + diff --git a/RecipeBuilder/Buttons.swift b/RecipeBuilder/Buttons.swift index 5047c41..e03ddd1 100644 --- a/RecipeBuilder/Buttons.swift +++ b/RecipeBuilder/Buttons.swift @@ -17,6 +17,62 @@ func appDelegate() -> AppDelegate { return NSApplication.shared.delegate as! AppDelegate } +// Function calling every button function to create defaults buttons +func createAllDefaultButtons () { + createButtonAppDMGVersioner () + createButtonAppPkgCreator () + createButtonCodeSignatureVerifierApp () + createButtonCodeSignatureVerifierPKG () + createButtonCopier () + createButtonDeprecationWarning () + createButtonDmgCreator () + createButtonEndOfCheckPhase () + createButtonFileCreator () + createButtonFileFinder () + createButtonFileMover () + createButtonFlatPkgPacker () + createButtonFlatPkgUnpacker () + createButtonGitHubReleasesInfoProvider () + createButtonInstaller () + createButtonInstallFromDMG () + createButtonMunkiCatalogBuilder () + createButtonMunkiImporter () + createButtonMunkiInfoCreator () + createButtonMunkiInstallsItemsCreator () + createButtonMunkiPkginfoMerger () + createButtonPackageRequired () + createButtonpathDeleter () + createButtonPkgCopier () + createButtonPkgCreator () + createButtonPkgExtractor () + createButtonPkgInfoCreator () + createButtonPkgPayloadUnpacker () + createButtonPkgRootCreator () + createButtonPlistEditor () + createButtonPlistReader () + createButtonSparkleUpdateInfoProvider () + createButtonStopProcessingIf () + createButtonSymlinker () + createButtonUnarchiver () + createButtonUrlDownloader () + createButtonUrlTextSearcher () + createButtonVersioner () +} + +// 21 pixels between every button +func createButtonAppDMGVersioner () { +let appDMGVersioner = NSButton(frame: NSRect(x: 17, y: 855, width: 191, height: 17)) + appDMGVersioner.title = "AppDmgVersioner" + appDMGVersioner.bezelStyle = NSButton.BezelStyle.inline + appDMGVersioner.setButtonType(NSButton.ButtonType.momentaryPushIn) + appDMGVersioner.isBordered = true + appDMGVersioner.font = .boldSystemFont(ofSize: 11) + appDMGVersioner.toolTip = "Extracts bundle ID and version of app inside a dmg" + appDMGVersioner.action = #selector(appDelegate().appDMGVersionerAction) +appDelegate().processorsView.addSubview(appDMGVersioner) +} + + func appDMGVersioner () { output = """ @@ -33,6 +89,19 @@ func appDMGVersioner () { } +func createButtonAppPkgCreator () { +let appPkgCreator = NSButton(frame: NSRect(x: 17, y: 834, width: 191, height: 17)) + appPkgCreator.title = "AppPkgCreator" + appPkgCreator.bezelStyle = NSButton.BezelStyle.inline + appPkgCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + appPkgCreator.isBordered = true + appPkgCreator.font = .boldSystemFont(ofSize: 11) + appPkgCreator.toolTip = "Calls autopkgserver to create a package from an application" + appPkgCreator.action = #selector(appDelegate().appPkgCreatorAction) +appDelegate().processorsView.addSubview(appPkgCreator) +} + + func appPkgCreator () { output = """ @@ -51,6 +120,17 @@ func appPkgCreator () { writeOutput () } +func createButtonCodeSignatureVerifierApp () { +let codeSignatureVerifierApp = NSButton(frame: NSRect(x: 17, y: 813, width: 191, height: 17)) + codeSignatureVerifierApp.title = "CodeSignatureVerifier App " + codeSignatureVerifierApp.bezelStyle = NSButton.BezelStyle.inline + codeSignatureVerifierApp.setButtonType(NSButton.ButtonType.momentaryPushIn) + codeSignatureVerifierApp.isBordered = true + codeSignatureVerifierApp.font = .boldSystemFont(ofSize: 11) + codeSignatureVerifierApp.toolTip = "Verifies application bundle signature" + codeSignatureVerifierApp.action = #selector(appDelegate().codeSignatureVerifierAppAction) +appDelegate().processorsView.addSubview(codeSignatureVerifierApp) +} func codeSignatureVerifierApp () { @@ -114,6 +194,17 @@ if (dialog.runModal() == NSApplication.ModalResponse.OK) { writeOutput () } +func createButtonCodeSignatureVerifierPKG () { +let codeSignatureVerifierPKG = NSButton(frame: NSRect(x: 17, y: 792, width: 191, height: 17)) + codeSignatureVerifierPKG.title = "CodeSignatureVerifier PKG" + codeSignatureVerifierPKG.bezelStyle = NSButton.BezelStyle.inline + codeSignatureVerifierPKG.setButtonType(NSButton.ButtonType.momentaryPushIn) + codeSignatureVerifierPKG.isBordered = true + codeSignatureVerifierPKG.font = .boldSystemFont(ofSize: 11) + codeSignatureVerifierPKG.toolTip = "Verifies installer package signature" + codeSignatureVerifierPKG.action = #selector(appDelegate().codeSignatureVerifierPkgAction) +appDelegate().processorsView.addSubview(codeSignatureVerifierPKG) +} func codeSignatureVerifierPKG () { @@ -148,11 +239,17 @@ func codeSignatureVerifierPKG () { } var codeSignPkg = shell("pkgutil --check-signature '\(path)'") - let codeSignPkgArrayTemp = codeSignPkg.components(separatedBy: CharacterSet.newlines) - if codeSignPkgArrayTemp.isEmpty { + var codeSignPkgArrayTemp = codeSignPkg.components(separatedBy: CharacterSet.newlines) + codeSignPkgArrayTemp = codeSignPkgArrayTemp.map { $0.trimmingCharacters(in: .whitespaces) } + // Get only matching line 1. + codeSignPkgArrayTemp = codeSignPkgArrayTemp.filter({ $0.hasPrefix("1. ") }) + + + if codeSignPkgArrayTemp.isEmpty { } else { - codeSignPkg = codeSignPkgArrayTemp[3].replacingOccurrences(of: "1. ", with: "", options: [.regularExpression, .caseInsensitive]) - codeSignPkg = codeSignPkg.trimmingCharacters(in: .whitespacesAndNewlines) + codeSignPkg = codeSignPkgArrayTemp.joined(separator: "") + codeSignPkg = codeSignPkg.replacingOccurrences(of: "1. ", with: "", options: [.regularExpression, .caseInsensitive]) + //codeSignPkg = codeSignPkg.trimmingCharacters(in: .whitespacesAndNewlines) } @@ -177,6 +274,18 @@ func codeSignatureVerifierPKG () { writeOutput () } +func createButtonCopier () { +let copier = NSButton(frame: NSRect(x: 17, y: 771, width: 191, height: 17)) + copier.title = "Copier" + copier.bezelStyle = NSButton.BezelStyle.inline + copier.setButtonType(NSButton.ButtonType.momentaryPushIn) + copier.isBordered = true + copier.font = .boldSystemFont(ofSize: 11) + copier.toolTip = "Copies source_path to destination_path" + copier.action = #selector(appDelegate().copierAction) +appDelegate().processorsView.addSubview(copier) +} + func copier () { output = """ @@ -195,7 +304,19 @@ func copier () { writeOutput () } - + +func createButtonDeprecationWarning () { +let deprecationWarning = NSButton(frame: NSRect(x: 17, y: 750, width: 191, height: 17)) + deprecationWarning.title = "DeprecationWarning" + deprecationWarning.bezelStyle = NSButton.BezelStyle.inline + deprecationWarning.setButtonType(NSButton.ButtonType.momentaryPushIn) + deprecationWarning.isBordered = true + deprecationWarning.font = .boldSystemFont(ofSize: 11) + deprecationWarning.toolTip = "This processor outputs a warning that the recipe has been deprecated" + deprecationWarning.action = #selector(appDelegate().deprecationWarningAction) +appDelegate().processorsView.addSubview(deprecationWarning) +} + func deprecationWarning () { output = """ @@ -211,7 +332,19 @@ func deprecationWarning () { writeOutput () } - + +func createButtonDmgCreator () { +let dmgCreator = NSButton(frame: NSRect(x: 17, y: 729, width: 191, height: 17)) + dmgCreator.title = "DmgCreator" + dmgCreator.bezelStyle = NSButton.BezelStyle.inline + dmgCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + dmgCreator.isBordered = true + dmgCreator.font = .boldSystemFont(ofSize: 11) + dmgCreator.toolTip = "Creates a disk image from a directory" + dmgCreator.action = #selector(appDelegate().dmgCreatorAction) +appDelegate().processorsView.addSubview(dmgCreator) +} + func dmgCreator () { output = """ @@ -229,10 +362,25 @@ func dmgCreator () { writeOutput () } - + + func dmgMounter () { } - + + +func createButtonEndOfCheckPhase () { +let endOfCheckPhase = NSButton(frame: NSRect(x: 17, y: 708, width: 191, height: 17)) + endOfCheckPhase.title = "EndOfCheckPhase" + endOfCheckPhase.bezelStyle = NSButton.BezelStyle.inline + endOfCheckPhase.setButtonType(NSButton.ButtonType.momentaryPushIn) + endOfCheckPhase.isBordered = true + endOfCheckPhase.font = .boldSystemFont(ofSize: 11) + endOfCheckPhase.toolTip = "This processor does nothing at all" + endOfCheckPhase.action = #selector(appDelegate().endOfCheckPhaseAction) +appDelegate().processorsView.addSubview(endOfCheckPhase) +} + + func endOfCheckPhase () { output = """ @@ -244,6 +392,20 @@ func endOfCheckPhase () { writeOutput () } + +func createButtonFileCreator () { +let fileCreator = NSButton(frame: NSRect(x: 17, y: 687, width: 191, height: 17)) + fileCreator.title = "FileCreator" + fileCreator.bezelStyle = NSButton.BezelStyle.inline + fileCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + fileCreator.isBordered = true + fileCreator.font = .boldSystemFont(ofSize: 11) + fileCreator.toolTip = "Create a file" + fileCreator.action = #selector(appDelegate().fileCreatorAction) +appDelegate().processorsView.addSubview(fileCreator) +} + + func fileCreator () { output = """ @@ -265,6 +427,19 @@ func fileCreator () { } +func createButtonFileFinder () { +let fileFinder = NSButton(frame: NSRect(x: 17, y: 666, width: 191, height: 17)) + fileFinder.title = "FileFinder" + fileFinder.bezelStyle = NSButton.BezelStyle.inline + fileFinder.setButtonType(NSButton.ButtonType.momentaryPushIn) + fileFinder.isBordered = true + fileFinder.font = .boldSystemFont(ofSize: 11) + fileFinder.toolTip = "Finds a filename for use in other Processors" + fileFinder.action = #selector(appDelegate().fileFinderAction) +appDelegate().processorsView.addSubview(fileFinder) +} + + func fileFinder () { output = """ @@ -282,6 +457,19 @@ func fileFinder () { } +func createButtonFileMover () { +let fileMover = NSButton(frame: NSRect(x: 17, y: 645, width: 191, height: 17)) + fileMover.title = "FileMover" + fileMover.bezelStyle = NSButton.BezelStyle.inline + fileMover.setButtonType(NSButton.ButtonType.momentaryPushIn) + fileMover.isBordered = true + fileMover.font = .boldSystemFont(ofSize: 11) + fileMover.toolTip = "Moves/renames a file" + fileMover.action = #selector(appDelegate().fileMoverAction) +appDelegate().processorsView.addSubview(fileMover) +} + + func fileMover () { output = """ @@ -302,6 +490,20 @@ func fileMover () { writeOutput () } + +func createButtonFlatPkgPacker () { +let flatPkgPacker = NSButton(frame: NSRect(x: 17, y: 624, width: 191, height: 17)) + flatPkgPacker.title = "FlatPkgPacker" + flatPkgPacker.bezelStyle = NSButton.BezelStyle.inline + flatPkgPacker.setButtonType(NSButton.ButtonType.momentaryPushIn) + flatPkgPacker.isBordered = true + flatPkgPacker.font = .boldSystemFont(ofSize: 11) + flatPkgPacker.toolTip = "Flatten an expanded package using pkgutil" + flatPkgPacker.action = #selector(appDelegate().flatPkgPackerAction) +appDelegate().processorsView.addSubview(flatPkgPacker) +} + + func flatPkgPacker () { output = """ @@ -321,6 +523,19 @@ func flatPkgPacker () { } +func createButtonFlatPkgUnpacker () { +let flatPkgUnpacker = NSButton(frame: NSRect(x: 17, y: 603, width: 191, height: 17)) + flatPkgUnpacker.title = "FlatPkgPacker" + flatPkgUnpacker.bezelStyle = NSButton.BezelStyle.inline + flatPkgUnpacker.setButtonType(NSButton.ButtonType.momentaryPushIn) + flatPkgUnpacker.isBordered = true + flatPkgUnpacker.font = .boldSystemFont(ofSize: 11) + flatPkgUnpacker.toolTip = "Expands a flat package using pkgutil or xar. For xar it also optionally skips extracting the payload" + flatPkgUnpacker.action = #selector(appDelegate().flatPkgUnpackerAction) +appDelegate().processorsView.addSubview(flatPkgUnpacker) +} + + func flatPkgUnpacker () { output = """ @@ -342,6 +557,19 @@ func flatPkgUnpacker () { } +func createButtonGitHubReleasesInfoProvider () { +let gitHubReleasesInfoProvider = NSButton(frame: NSRect(x: 17, y: 582, width: 191, height: 17)) + gitHubReleasesInfoProvider.title = "GitHubReleasesInfoProvider" + gitHubReleasesInfoProvider.bezelStyle = NSButton.BezelStyle.inline + gitHubReleasesInfoProvider.setButtonType(NSButton.ButtonType.momentaryPushIn) + gitHubReleasesInfoProvider.isBordered = true + gitHubReleasesInfoProvider.font = .boldSystemFont(ofSize: 11) + gitHubReleasesInfoProvider.toolTip = "Get metadata from the latest release from a GitHub project using the GitHub Releases API. Requires version 0.5.0" + gitHubReleasesInfoProvider.action = #selector(appDelegate().gitHubReleasesInfoProviderAction) +appDelegate().processorsView.addSubview(gitHubReleasesInfoProvider) +} + + func gitHubReleasesInfoProvider () { output = """ @@ -358,22 +586,18 @@ func gitHubReleasesInfoProvider () { writeOutput () } +func createButtonInstallFromDMG () { +let installFromDMG = NSButton(frame: NSRect(x: 17, y: 561, width: 191, height: 17)) + installFromDMG.title = "InstallFromDMG" + installFromDMG.bezelStyle = NSButton.BezelStyle.inline + installFromDMG.setButtonType(NSButton.ButtonType.momentaryPushIn) + installFromDMG.isBordered = true + installFromDMG.font = .boldSystemFont(ofSize: 11) + installFromDMG.toolTip = "Calls autopkginstalld to copy items from a disk image to the root filesystem" + installFromDMG.action = #selector(appDelegate().installFromDMGAction) +appDelegate().processorsView.addSubview(installFromDMG) +} -func installer () { - output = """ - - Arguments - - pkg_path - %RECIPE_CACHE_DIR%/%NAME%.pkg - - Processor - Installer - - """ - - writeOutput () - } func installFromDMG () { output = """ @@ -400,6 +624,80 @@ func installFromDMG () { writeOutput () } + +func createButtonInstaller () { +let installer = NSButton(frame: NSRect(x: 17, y: 540, width: 191, height: 17)) + installer.title = "Installer" + installer.bezelStyle = NSButton.BezelStyle.inline + installer.setButtonType(NSButton.ButtonType.momentaryPushIn) + installer.isBordered = true + installer.font = .boldSystemFont(ofSize: 11) + installer.toolTip = "Calls autopkginstalld to install a package" + installer.action = #selector(appDelegate().installerAction) +appDelegate().processorsView.addSubview(installer) +} + + +func installer () { + output = """ + + Arguments + + pkg_path + %RECIPE_CACHE_DIR%/%NAME%.pkg + + Processor + Installer + + """ + + writeOutput () + } + + +func createButtonMunkiCatalogBuilder () { +let munkiCatalogBuilder = NSButton(frame: NSRect(x: 17, y: 519, width: 191, height: 17)) + munkiCatalogBuilder.title = "MunkiCatalogBuilder" + munkiCatalogBuilder.bezelStyle = NSButton.BezelStyle.inline + munkiCatalogBuilder.setButtonType(NSButton.ButtonType.momentaryPushIn) + munkiCatalogBuilder.isBordered = true + munkiCatalogBuilder.font = .boldSystemFont(ofSize: 11) + munkiCatalogBuilder.toolTip = "Rebuilds Munki catalogs" + munkiCatalogBuilder.action = #selector(appDelegate().munkiCatalogBuilderAction) +appDelegate().processorsView.addSubview(munkiCatalogBuilder) +} + + +func munkiCatalogBuilder () { + output = """ + + Arguments + + MUNKI_REPO + /path/to/munki/repo + + Processor + MunkiCatalogBuilder + + """ + + writeOutput () +} + + +func createButtonMunkiImporter () { +let munkiImporter = NSButton(frame: NSRect(x: 17, y: 498, width: 191, height: 17)) + munkiImporter.title = "MunkiImporter" + munkiImporter.bezelStyle = NSButton.BezelStyle.inline + munkiImporter.setButtonType(NSButton.ButtonType.momentaryPushIn) + munkiImporter.isBordered = true + munkiImporter.font = .boldSystemFont(ofSize: 11) + munkiImporter.toolTip = "Imports a pkg or dmg to the Munki repo" + munkiImporter.action = #selector(appDelegate().munkiImporterAction) +appDelegate().processorsView.addSubview(munkiImporter) +} + + func munkiImporter () { output = """ @@ -418,6 +716,20 @@ func munkiImporter () { writeOutput () } + +func createButtonMunkiInfoCreator () { +let munkiInfoCreator = NSButton(frame: NSRect(x: 17, y: 477, width: 191, height: 17)) + munkiInfoCreator.title = "MunkiInfoCreator" + munkiInfoCreator.bezelStyle = NSButton.BezelStyle.inline + munkiInfoCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + munkiInfoCreator.isBordered = true + munkiInfoCreator.font = .boldSystemFont(ofSize: 11) + munkiInfoCreator.toolTip = "Creates a pkginfo file for a munki package" + munkiInfoCreator.action = #selector(appDelegate().munkiInfoCreatorAction) +appDelegate().processorsView.addSubview(munkiInfoCreator) +} + + func munkiInfoCreator () { output = """ @@ -435,6 +747,19 @@ func munkiInfoCreator () { } +func createButtonMunkiInstallsItemsCreator () { +let munkiInstallsItemsCreator = NSButton(frame: NSRect(x: 17, y: 456, width: 191, height: 17)) + munkiInstallsItemsCreator.title = "MunkiInstallsItemsCreator" + munkiInstallsItemsCreator.bezelStyle = NSButton.BezelStyle.inline + munkiInstallsItemsCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + munkiInstallsItemsCreator.isBordered = true + munkiInstallsItemsCreator.font = .boldSystemFont(ofSize: 11) + munkiInstallsItemsCreator.toolTip = "Generates an installs array for a pkginfo file" + munkiInstallsItemsCreator.action = #selector(appDelegate().MunkiInstallsItemsCreatorAction) +appDelegate().processorsView.addSubview(munkiInstallsItemsCreator) +} + + func munkiInstallsItemsCreator () { output = """ @@ -456,6 +781,19 @@ func munkiInstallsItemsCreator () { } +func createButtonMunkiPkginfoMerger () { +let munkiPkginfoMerger = NSButton(frame: NSRect(x: 17, y: 435, width: 191, height: 17)) + munkiPkginfoMerger.title = "MunkiPkginfoMerger" + munkiPkginfoMerger.bezelStyle = NSButton.BezelStyle.inline + munkiPkginfoMerger.setButtonType(NSButton.ButtonType.momentaryPushIn) + munkiPkginfoMerger.isBordered = true + munkiPkginfoMerger.font = .boldSystemFont(ofSize: 11) + munkiPkginfoMerger.toolTip = "Merges two pkginfo dictionaries" + munkiPkginfoMerger.action = #selector(appDelegate().munkiPkginfoMergerAction) +appDelegate().processorsView.addSubview(munkiPkginfoMerger) +} + + func munkiPkginfoMerger () { output = """ @@ -476,6 +814,19 @@ func munkiPkginfoMerger () { } +func createButtonPackageRequired () { +let packageRequired = NSButton(frame: NSRect(x: 17, y: 414, width: 191, height: 17)) + packageRequired.title = "PackageRequired" + packageRequired.bezelStyle = NSButton.BezelStyle.inline + packageRequired.setButtonType(NSButton.ButtonType.momentaryPushIn) + packageRequired.isBordered = true + packageRequired.font = .boldSystemFont(ofSize: 11) + packageRequired.toolTip = "Raises a ProcessorError if the PKG variable doesn't exist" + packageRequired.action = #selector(appDelegate().packageRequiredAction) +appDelegate().processorsView.addSubview(packageRequired) +} + + func packageRequired () { output = """ @@ -488,6 +839,19 @@ func packageRequired () { } +func createButtonpathDeleter () { +let pathDeleter = NSButton(frame: NSRect(x: 17, y: 393, width: 191, height: 17)) + pathDeleter.title = "PathDeleter" + pathDeleter.bezelStyle = NSButton.BezelStyle.inline + pathDeleter.setButtonType(NSButton.ButtonType.momentaryPushIn) + pathDeleter.isBordered = true + pathDeleter.font = .boldSystemFont(ofSize: 11) + pathDeleter.toolTip = "Deletes file paths" + pathDeleter.action = #selector(appDelegate().pathDeleterAction) +appDelegate().processorsView.addSubview(pathDeleter) +} + + func pathDeleter () { output = """ @@ -507,6 +871,19 @@ func pathDeleter () { } +func createButtonPkgCopier () { +let pkgCopier = NSButton(frame: NSRect(x: 17, y: 372, width: 191, height: 17)) + pkgCopier.title = "PkgCopier" + pkgCopier.bezelStyle = NSButton.BezelStyle.inline + pkgCopier.setButtonType(NSButton.ButtonType.momentaryPushIn) + pkgCopier.isBordered = true + pkgCopier.font = .boldSystemFont(ofSize: 11) + pkgCopier.toolTip = "Copies source_pkg to pkg_path" + pkgCopier.action = #selector(appDelegate().pkgCopierAction) +appDelegate().processorsView.addSubview(pkgCopier) +} + + func pkgCopier () { output = """ @@ -526,6 +903,69 @@ func pkgCopier () { } +func createButtonPkgCreator () { +let pkgCreator = NSButton(frame: NSRect(x: 17, y: 351, width: 191, height: 17)) + pkgCreator.title = "PkgCreator" + pkgCreator.bezelStyle = NSButton.BezelStyle.inline + pkgCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + pkgCreator.isBordered = true + pkgCreator.font = .boldSystemFont(ofSize: 11) + pkgCreator.toolTip = "Calls autopkgserver to create a package" + pkgCreator.action = #selector(appDelegate().pkgCreatorAction) +appDelegate().processorsView.addSubview(pkgCreator) +} + + +func pkgCreator () { + output = """ + + Arguments + + pkg_request + + pkgname + %NAME%-%version% + pkgdir + %RECIPE_CACHE_DIR%/%NAME% + id + com.yourdomain.%NAME% + options + purge_ds_store + chown + + + path + Applications + user + root + group + admin + + + + + Processor + PkgCreator + + """ + + writeOutput () + } + + +func createButtonPkgExtractor () { +let pkgExtractor = NSButton(frame: NSRect(x: 17, y: 330, width: 191, height: 17)) + pkgExtractor.title = "PkgExtractor" + pkgExtractor.bezelStyle = NSButton.BezelStyle.inline + pkgExtractor.setButtonType(NSButton.ButtonType.momentaryPushIn) + pkgExtractor.isBordered = true + pkgExtractor.font = .boldSystemFont(ofSize: 11) + pkgExtractor.toolTip = "Extracts the contents of a bundle-style pkg (possibly on a disk image) to pkgroot" + pkgExtractor.action = #selector(appDelegate().pkgExtractorAction) +appDelegate().processorsView.addSubview(pkgExtractor) +} + + func pkgExtractor () { output = """ @@ -545,6 +985,19 @@ func pkgExtractor () { } +func createButtonPkgInfoCreator () { +let pkgInfoCreator = NSButton(frame: NSRect(x: 17, y: 309, width: 191, height: 17)) + pkgInfoCreator.title = "PkgInfoCreator" + pkgInfoCreator.bezelStyle = NSButton.BezelStyle.inline + pkgInfoCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + pkgInfoCreator.isBordered = true + pkgInfoCreator.font = .boldSystemFont(ofSize: 11) + pkgInfoCreator.toolTip = "Creates an PackageInfo file for a package" + pkgInfoCreator.action = #selector(appDelegate().pkgInfoCreatorAction) +appDelegate().processorsView.addSubview(pkgInfoCreator) +} + + func pkgInfoCreator () { output = """ @@ -566,6 +1019,19 @@ func pkgInfoCreator () { } +func createButtonPkgPayloadUnpacker () { +let pkgPayloadUnpacker = NSButton(frame: NSRect(x: 17, y: 288, width: 191, height: 17)) + pkgPayloadUnpacker.title = "PkgPayloadUnpacker" + pkgPayloadUnpacker.bezelStyle = NSButton.BezelStyle.inline + pkgPayloadUnpacker.setButtonType(NSButton.ButtonType.momentaryPushIn) + pkgPayloadUnpacker.isBordered = true + pkgPayloadUnpacker.font = .boldSystemFont(ofSize: 11) + pkgPayloadUnpacker.toolTip = "Unpacks a package payload" + pkgPayloadUnpacker.action = #selector(appDelegate().pkgPayloadUnpackerAction) +appDelegate().processorsView.addSubview(pkgPayloadUnpacker) +} + + func pkgPayloadUnpacker () { output = """ @@ -586,6 +1052,20 @@ func pkgPayloadUnpacker () { writeOutput () } + +func createButtonPkgRootCreator () { +let pkgRootCreator = NSButton(frame: NSRect(x: 17, y: 267, width: 191, height: 17)) + pkgRootCreator.title = "PkgRootCreator" + pkgRootCreator.bezelStyle = NSButton.BezelStyle.inline + pkgRootCreator.setButtonType(NSButton.ButtonType.momentaryPushIn) + pkgRootCreator.isBordered = true + pkgRootCreator.font = .boldSystemFont(ofSize: 11) + pkgRootCreator.toolTip = "Creates a package root and a directory structure. (Can also be used to create directory structures for other purposes.)" + pkgRootCreator.action = #selector(appDelegate().pkgRootCreatorAction) +appDelegate().processorsView.addSubview(pkgRootCreator) +} + + func pkgRootCreator () { output = """ @@ -597,7 +1077,7 @@ func pkgRootCreator () { 0755 pkgroot - %RECIPE_CACHE_DIR%/ + %RECIPE_CACHE_DIR%/%NAME% Processor PkgRootCreator @@ -608,6 +1088,19 @@ func pkgRootCreator () { } +func createButtonPlistEditor () { +let plistEditor = NSButton(frame: NSRect(x: 17, y: 246, width: 191, height: 17)) + plistEditor.title = "PlistEditor" + plistEditor.bezelStyle = NSButton.BezelStyle.inline + plistEditor.setButtonType(NSButton.ButtonType.momentaryPushIn) + plistEditor.isBordered = true + plistEditor.font = .boldSystemFont(ofSize: 11) + plistEditor.toolTip = "Merges data with an input plist (which can be empty) and writes a new plist" + plistEditor.action = #selector(appDelegate().plistEditorAction) +appDelegate().processorsView.addSubview(plistEditor) +} + + func plistEditor () { output = """ @@ -632,6 +1125,19 @@ func plistEditor () { } +func createButtonPlistReader () { +let plistReader = NSButton(frame: NSRect(x: 17, y: 225, width: 191, height: 17)) + plistReader.title = "PlistReader" + plistReader.bezelStyle = NSButton.BezelStyle.inline + plistReader.setButtonType(NSButton.ButtonType.momentaryPushIn) + plistReader.isBordered = true + plistReader.font = .boldSystemFont(ofSize: 11) + plistReader.toolTip = "Extracts values from top-level keys in a plist file, and assigns to arbitrary output variables. This behavior is different from other processors that pre-define all their possible output variables. As it is often used for versioning, it defaults to extracting 'CFBundleShortVersionString' to 'version'. This can be used as a replacement for both the AppDmgVersioner and Versioner processors" + plistReader.action = #selector(appDelegate().plistReaderAction) +appDelegate().processorsView.addSubview(plistReader) +} + + func plistReader () { output = """ @@ -656,6 +1162,19 @@ func plistReader () { } +func createButtonSparkleUpdateInfoProvider () { +let sparkleUpdateInfoProvider = NSButton(frame: NSRect(x: 17, y: 204, width: 191, height: 17)) + sparkleUpdateInfoProvider.title = "SparkleUpdateInfoProvider" + sparkleUpdateInfoProvider.bezelStyle = NSButton.BezelStyle.inline + sparkleUpdateInfoProvider.setButtonType(NSButton.ButtonType.momentaryPushIn) + sparkleUpdateInfoProvider.isBordered = true + sparkleUpdateInfoProvider.font = .boldSystemFont(ofSize: 11) + sparkleUpdateInfoProvider.toolTip = "Provides URL to the highest version number or latest update" + sparkleUpdateInfoProvider.action = #selector(appDelegate().sparkleUpdateInfoProviderAction) +appDelegate().processorsView.addSubview(sparkleUpdateInfoProvider) +} + + func sparkleUpdateInfoProvider () { output = """ @@ -673,6 +1192,19 @@ func sparkleUpdateInfoProvider () { } +func createButtonStopProcessingIf () { +let stopProcessingIf = NSButton(frame: NSRect(x: 17, y: 183, width: 191, height: 17)) + stopProcessingIf.title = "StopProcessingIf" + stopProcessingIf.bezelStyle = NSButton.BezelStyle.inline + stopProcessingIf.setButtonType(NSButton.ButtonType.momentaryPushIn) + stopProcessingIf.isBordered = true + stopProcessingIf.font = .boldSystemFont(ofSize: 11) + stopProcessingIf.toolTip = "Sets a variable to tell AutoPackager to stop processing a recipe if a predicate comparison evaluates to true" + stopProcessingIf.action = #selector(appDelegate().stopProcessingIfAction) +appDelegate().processorsView.addSubview(stopProcessingIf) +} + + func stopProcessingIf () { output = """ @@ -690,6 +1222,19 @@ func stopProcessingIf () { } +func createButtonSymlinker () { +let symlinker = NSButton(frame: NSRect(x: 17, y: 162, width: 191, height: 17)) + symlinker.title = "Symlinker" + symlinker.bezelStyle = NSButton.BezelStyle.inline + symlinker.setButtonType(NSButton.ButtonType.momentaryPushIn) + symlinker.isBordered = true + symlinker.font = .boldSystemFont(ofSize: 11) + symlinker.toolTip = "Copies source_path to destination_path" + symlinker.action = #selector(appDelegate().symlinkerAction) +appDelegate().processorsView.addSubview(symlinker) +} + + func symlinker () { output = """ @@ -709,6 +1254,19 @@ func symlinker () { } +func createButtonUnarchiver () { +let unarchiver = NSButton(frame: NSRect(x: 17, y: 141, width: 191, height: 17)) + unarchiver.title = "Unarchiver" + unarchiver.bezelStyle = NSButton.BezelStyle.inline + unarchiver.setButtonType(NSButton.ButtonType.momentaryPushIn) + unarchiver.isBordered = true + unarchiver.font = .boldSystemFont(ofSize: 11) + unarchiver.toolTip = "Archive decompressor for zip and common tar-compressed formats" + unarchiver.action = #selector(appDelegate().unarchiverAction) +appDelegate().processorsView.addSubview(unarchiver) +} + + func unarchiver () { output = """ @@ -730,6 +1288,19 @@ func unarchiver () { } +func createButtonUrlDownloader () { +let urlDownloader = NSButton(frame: NSRect(x: 17, y: 120, width: 191, height: 17)) + urlDownloader.title = "URLDownloader" + urlDownloader.bezelStyle = NSButton.BezelStyle.inline + urlDownloader.setButtonType(NSButton.ButtonType.momentaryPushIn) + urlDownloader.isBordered = true + urlDownloader.font = .boldSystemFont(ofSize: 11) + urlDownloader.toolTip = "Downloads a URL to the specified download_dir using curl" + urlDownloader.action = #selector(appDelegate().urlDownloaderAction) +appDelegate().processorsView.addSubview(urlDownloader) +} + + func urlDownloader () { output = """ @@ -749,6 +1320,19 @@ func urlDownloader () { } +func createButtonUrlTextSearcher () { +let urlTextSearcher = NSButton(frame: NSRect(x: 17, y: 99, width: 191, height: 17)) + urlTextSearcher.title = "URLTextSearcher" + urlTextSearcher.bezelStyle = NSButton.BezelStyle.inline + urlTextSearcher.setButtonType(NSButton.ButtonType.momentaryPushIn) + urlTextSearcher.isBordered = true + urlTextSearcher.font = .boldSystemFont(ofSize: 11) + urlTextSearcher.toolTip = "Downloads a URL using curl and performs a regular expression match on the text" + urlTextSearcher.action = #selector(appDelegate().urlTextSearcherAction) +appDelegate().processorsView.addSubview(urlTextSearcher) +} + + func urlTextSearcher () { output = """ @@ -770,6 +1354,19 @@ func urlTextSearcher () { } +func createButtonVersioner () { +let versioner = NSButton(frame: NSRect(x: 17, y: 78, width: 191, height: 17)) + versioner.title = "Versioner" + versioner.bezelStyle = NSButton.BezelStyle.inline + versioner.setButtonType(NSButton.ButtonType.momentaryPushIn) + versioner.isBordered = true + versioner.font = .boldSystemFont(ofSize: 11) + versioner.toolTip = "Returns version information from a plist" + versioner.action = #selector(appDelegate().versionerAction) +appDelegate().processorsView.addSubview(versioner) +} + + func versioner () { output = """ diff --git a/RecipeBuilder/Buttons3rdParty.swift b/RecipeBuilder/Buttons3rdParty.swift new file mode 100644 index 0000000..498ce20 --- /dev/null +++ b/RecipeBuilder/Buttons3rdParty.swift @@ -0,0 +1,476 @@ +// +// Buttons3rdParty.swift +// RecipeBuilder +// +// Created by Mikael Löfgren on 2022-02-27. +// Copyright © 2022 Mikael Löfgren. All rights reserved. +// + +import Cocoa +import AppKit +import Foundation +import Highlightr + +func createAll3rdPartyButtons () { + createButtonJamfCategoryUploader () + createButtonJamfComputerGroupUploader () + createButtonJamfComputerProfileUploader () + createButtonJamfDockItemUploader () + createButtonJamfExtensionAttributeUploader () + createButtonJamfMacAppUploader () + createButtonJamfPackageUploader () + createButtonJamfPatchUploader () + createButtonJamfPolicyDeleter () + createButtonJamfPolicyLogFlusher () + createButtonJamfPolicyUploader () + createButtonJamfScriptUploader () + createButtonJamfSoftwareRestrictionUploader () + createButtonJamfUploaderSlacker () + createButtonJamfUploaderTeamsNotifier () +} + +// https://github.com/autopkg/grahampugh-recipes/blob/main/JamfUploaderProcessors/READMEs/JamfCategoryUploader.md + +func createButtonJamfCategoryUploader () { +let JamfCategoryUploader = NSButton(frame: NSRect(x: 17, y: 324, width: 191, height: 17)) + JamfCategoryUploader.title = "JamfCategoryUploader" + JamfCategoryUploader.bezelStyle = NSButton.BezelStyle.inline + JamfCategoryUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfCategoryUploader.isBordered = true + JamfCategoryUploader.font = .boldSystemFont(ofSize: 11) + JamfCategoryUploader.toolTip = "Upload a category to Jamf" + JamfCategoryUploader.action = #selector(appDelegate().JamfCategoryUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfCategoryUploader) +} + + +func JamfCategoryUploader () { + output = """ + + Arguments + + category_name + %CATEGORY% + + Processor + com.github.grahampugh.jamf-upload.processors/JamfCategoryUploader + +""" + writeOutput () + } + +func createButtonJamfComputerGroupUploader () { +let JamfComputerGroupUploader = NSButton(frame: NSRect(x: 17, y: 303, width: 191, height: 17)) + JamfComputerGroupUploader.title = "JamfComputerGroupUploader" + JamfComputerGroupUploader.bezelStyle = NSButton.BezelStyle.inline + JamfComputerGroupUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfComputerGroupUploader.isBordered = true + JamfComputerGroupUploader.font = .boldSystemFont(ofSize: 11) + JamfComputerGroupUploader.toolTip = "Upload a computer group to Jamf" + JamfComputerGroupUploader.action = #selector(appDelegate().JamfComputerGroupUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfComputerGroupUploader) +} + +func JamfComputerGroupUploader () { + output = """ + + Arguments + + computergroup_name + %GROUP_NAME% + computergroup_template + %GROUP_TEMPLATE% + + Processor + com.github.grahampugh.jamf-upload.processors/JamfComputerGroupUploader + +""" + writeOutput () +} + +func createButtonJamfComputerProfileUploader () { +let JamfComputerProfileUploader = NSButton(frame: NSRect(x: 17, y: 282, width: 191, height: 17)) + JamfComputerProfileUploader.title = "JamfComputerProfileUploader" + JamfComputerProfileUploader.bezelStyle = NSButton.BezelStyle.inline + JamfComputerProfileUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfComputerProfileUploader.isBordered = true + JamfComputerProfileUploader.font = .boldSystemFont(ofSize: 11) + JamfComputerProfileUploader.toolTip = "Upload computer configuration profiles to Jamf" + JamfComputerProfileUploader.action = #selector(appDelegate().JamfComputerProfileUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfComputerProfileUploader) +} + +func JamfComputerProfileUploader () { + output = """ + + Arguments + + profile_name + %PROFILE_NAME% + mobileconfig + /path/to/mobileconfig + + Processor + com.github.grahampugh.jamf-upload.processors/JamfComputerProfileUploader + +""" + writeOutput () +} + +func createButtonJamfDockItemUploader () { +let JamfDockItemUploader = NSButton(frame: NSRect(x: 17, y: 261, width: 191, height: 17)) + JamfDockItemUploader.title = "JamfDockItemUploader" + JamfDockItemUploader.bezelStyle = NSButton.BezelStyle.inline + JamfDockItemUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfDockItemUploader.isBordered = true + JamfDockItemUploader.font = .boldSystemFont(ofSize: 11) + JamfDockItemUploader.toolTip = "Upload a dock item to Jamf" + JamfDockItemUploader.action = #selector(appDelegate().JamfDockItemUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfDockItemUploader) +} + +func JamfDockItemUploader () { + output = """ + + Arguments + + dock_item_name + %NAME% + dock_item_type + App + dock_item_path + file:///Applications/AppName.app/ + replace_dock_item + False + + Processor + com.github.grahampugh.jamf-upload.processors/JamfDockItemUploader + +""" + writeOutput () +} + +func createButtonJamfExtensionAttributeUploader () { +let JamfExtensionAttributeUploader = NSButton(frame: NSRect(x: 17, y: 240, width: 191, height: 17)) + JamfExtensionAttributeUploader.title = "JamfExtensionAttributeUploader" + JamfExtensionAttributeUploader.bezelStyle = NSButton.BezelStyle.inline + JamfExtensionAttributeUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfExtensionAttributeUploader.isBordered = true + JamfExtensionAttributeUploader.font = .boldSystemFont(ofSize: 11) + JamfExtensionAttributeUploader.toolTip = "Upload a extension attribute to Jamf" + JamfExtensionAttributeUploader.action = #selector(appDelegate().JamfExtensionAttributeUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfExtensionAttributeUploader) +} + +func JamfExtensionAttributeUploader () { + output = """ + + Arguments + + ea_name + %EXTENSION_ATTRIBUTE_NAME% + ea_script_path + %EXTENSION_ATTRIBUTE_SCRIPT% + + Processor + com.github.grahampugh.jamf-upload.processors/JamfExtensionAttributeUploader + +""" + writeOutput () +} + +func createButtonJamfMacAppUploader () { + let JamfMacAppUploader = NSButton(frame: NSRect(x: 17, y: 219, width: 191, height: 17)) + JamfMacAppUploader.title = "JamfMacAppUploader" + JamfMacAppUploader.bezelStyle = NSButton.BezelStyle.inline + JamfMacAppUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfMacAppUploader.isBordered = true + JamfMacAppUploader.font = .boldSystemFont(ofSize: 11) + JamfMacAppUploader.toolTip = "Upload a Mac App Store app to a Jamf" + JamfMacAppUploader.action = #selector(appDelegate().JamfMacAppUploaderAction) + appDelegate().processorsView3rdParty.addSubview(JamfMacAppUploader) + } + +func JamfMacAppUploader () { + output = """ + + Arguments + + macapp_name + Mac App Store app name + macapp_template + /path/to/template.xml + + Processor + com.github.grahampugh.jamf-upload.processors/JamfMacAppUploader + +""" + writeOutput () +} + + + +func createButtonJamfPackageUploader () { +let JamfPackageUploader = NSButton(frame: NSRect(x: 17, y: 198, width: 191, height: 17)) + JamfPackageUploader.title = "JamfPackageUploader" + JamfPackageUploader.bezelStyle = NSButton.BezelStyle.inline + JamfPackageUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfPackageUploader.isBordered = true + JamfPackageUploader.font = .boldSystemFont(ofSize: 11) + JamfPackageUploader.toolTip = "Upload a package to Jamf" + JamfPackageUploader.action = #selector(appDelegate().JamfPackageUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfPackageUploader) +} + +func JamfPackageUploader () { + output = """ + + Arguments + + pkg_category + %CATEGORY% + + Processor + com.github.grahampugh.jamf-upload.processors/JamfPackageUploader + +""" + writeOutput () +} + +func createButtonJamfPatchUploader () { +let JamfPatchUploader = NSButton(frame: NSRect(x: 17, y: 177, width: 191, height: 17)) + JamfPatchUploader.title = "JamfPatchUploader" + JamfPatchUploader.bezelStyle = NSButton.BezelStyle.inline + JamfPatchUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfPatchUploader.isBordered = true + JamfPatchUploader.font = .boldSystemFont(ofSize: 11) + JamfPatchUploader.toolTip = "Upload a patch defination to Jamf" + JamfPatchUploader.action = #selector(appDelegate().JamfPatchUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfPatchUploader) +} + +func JamfPatchUploader () { + output = """ + + Arguments + + patch_softwaretitle + %NAME% + patch_name + %PATCH_NAME% + patch_template + PatchTemplate-selfservice.xml + patch_icon_policy_name + Install Latest %NAME% + + Processor + com.github.grahampugh.jamf-upload.processors/JamfPatchUploader + +""" + writeOutput () +} + +func createButtonJamfPolicyDeleter () { +let JamfPolicyDeleter = NSButton(frame: NSRect(x: 17, y: 156, width: 191, height: 17)) + JamfPolicyDeleter.title = "JamfPolicyDeleter" + JamfPolicyDeleter.bezelStyle = NSButton.BezelStyle.inline + JamfPolicyDeleter.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfPolicyDeleter.isBordered = true + JamfPolicyDeleter.font = .boldSystemFont(ofSize: 11) + JamfPolicyDeleter.toolTip = "Delete a policy from Jamf" + JamfPolicyDeleter.action = #selector(appDelegate().JamfPolicyDeleterAction) +appDelegate().processorsView3rdParty.addSubview(JamfPolicyDeleter) +} + +func JamfPolicyDeleter() { + output = """ + + Arguments + + policy_name + %POLICY_NAME% + + Processor + com.github.grahampugh.jamf-upload.processors/JamfPolicyDeleter + +""" + writeOutput () +} + +func createButtonJamfPolicyLogFlusher () { +let JamfPolicyLogFlusher = NSButton(frame: NSRect(x: 17, y: 135, width: 191, height: 17)) + JamfPolicyLogFlusher.title = "JamfPolicyLogFlusher" + JamfPolicyLogFlusher.bezelStyle = NSButton.BezelStyle.inline + JamfPolicyLogFlusher.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfPolicyLogFlusher.isBordered = true + JamfPolicyLogFlusher.font = .boldSystemFont(ofSize: 11) + JamfPolicyLogFlusher.toolTip = "Flush a policy log from Jamf" + JamfPolicyLogFlusher.action = #selector(appDelegate().JamfPolicyLogFlusherAction) +appDelegate().processorsView3rdParty.addSubview(JamfPolicyLogFlusher) +} + +func JamfPolicyLogFlusher() { + output = """ + + Arguments + + policy_name + %POLICY_NAME% + logflush_interval + Zero Days + + Processor + com.github.grahampugh.jamf-upload.processors/JamfPolicyLogFlusher + +""" + writeOutput () +} + +func createButtonJamfPolicyUploader () { +let JamfPolicyUploader = NSButton(frame: NSRect(x: 17, y: 114, width: 191, height: 17)) + JamfPolicyUploader.title = "JamfPolicyUploader" + JamfPolicyUploader.bezelStyle = NSButton.BezelStyle.inline + JamfPolicyUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfPolicyUploader.isBordered = true + JamfPolicyUploader.font = .boldSystemFont(ofSize: 11) + JamfPolicyUploader.toolTip = "Upload a policy to Jamf" + JamfPolicyUploader.action = #selector(appDelegate().JamfPolicyUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfPolicyUploader) +} + +func JamfPolicyUploader () { + output = """ + + Arguments + + icon + %SELF_SERVICE_ICON% + policy_name + %POLICY_NAME% + policy_template + %POLICY_TEMPLATE% + + Processor + com.github.grahampugh.jamf-upload.processors/JamfPolicyUploader + +""" + writeOutput () +} + +func createButtonJamfScriptUploader () { +let JamfScriptUploader = NSButton(frame: NSRect(x: 17, y: 93, width: 191, height: 17)) + JamfScriptUploader.title = "JamfScriptUploader" + JamfScriptUploader.bezelStyle = NSButton.BezelStyle.inline + JamfScriptUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfScriptUploader.isBordered = true + JamfScriptUploader.font = .boldSystemFont(ofSize: 11) + JamfScriptUploader.toolTip = "Upload a script to Jamf" + JamfScriptUploader.action = #selector(appDelegate().JamfScriptUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfScriptUploader) +} + +func JamfScriptUploader () { + output = """ + + Arguments + + script_path + /pat/to/script.sh + script_category + %SCRIPT_CATEGORY% + script_priority + After + + Processor + com.github.grahampugh.jamf-upload.processors/JamfScriptUploader + +""" + writeOutput () +} + +func createButtonJamfSoftwareRestrictionUploader () { +let JamfSoftwareRestrictionUploader = NSButton(frame: NSRect(x: 17, y: 72, width: 191, height: 17)) + JamfSoftwareRestrictionUploader.title = "JamfSoftwareRestrictionUploader" + JamfSoftwareRestrictionUploader.bezelStyle = NSButton.BezelStyle.inline + JamfSoftwareRestrictionUploader.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfSoftwareRestrictionUploader.isBordered = true + JamfSoftwareRestrictionUploader.font = .boldSystemFont(ofSize: 11) + JamfSoftwareRestrictionUploader.toolTip = "Upload a software restriction to Jamf" + JamfSoftwareRestrictionUploader.action = #selector(appDelegate().JamfSoftwareRestrictionUploaderAction) +appDelegate().processorsView3rdParty.addSubview(JamfSoftwareRestrictionUploader) +} + +func JamfSoftwareRestrictionUploader () { + output = """ + + Arguments + + restriction_name + Name of Restriction + restriction_template + /path/to/template.xml + + Processor + com.github.grahampugh.jamf-upload.processors/JamfSoftwareRestrictionUploader + +""" + writeOutput () +} + +func createButtonJamfUploaderSlacker () { +let JamfUploaderSlacker = NSButton(frame: NSRect(x: 17, y: 51, width: 191, height: 17)) + JamfUploaderSlacker.title = "JamfUploaderSlacker" + JamfUploaderSlacker.bezelStyle = NSButton.BezelStyle.inline + JamfUploaderSlacker.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfUploaderSlacker.isBordered = true + JamfUploaderSlacker.font = .boldSystemFont(ofSize: 11) + JamfUploaderSlacker.toolTip = "Postprocessor for AutoPkg that will send details about a recipe run to a Slack webhook" + JamfUploaderSlacker.action = #selector(appDelegate().JamfUploaderSlackerAction) +appDelegate().processorsView3rdParty.addSubview(JamfUploaderSlacker) +} + +func JamfUploaderSlacker () { + output = """ + + Arguments + + NAME + Name of the application + slack_webhook_url + https://slack.webhook.url + + Processor + com.github.grahampugh.jamf-upload.processors/JamfUploaderSlacker + +""" + writeOutput () +} + +func createButtonJamfUploaderTeamsNotifier () { +let JamfUploaderTeamsNotifier = NSButton(frame: NSRect(x: 17, y: 30, width: 191, height: 17)) + JamfUploaderTeamsNotifier.title = "JamfUploaderTeamsNotifier" + JamfUploaderTeamsNotifier.bezelStyle = NSButton.BezelStyle.inline + JamfUploaderTeamsNotifier.setButtonType(NSButton.ButtonType.momentaryPushIn) + JamfUploaderTeamsNotifier.isBordered = true + JamfUploaderTeamsNotifier.font = .boldSystemFont(ofSize: 11) + JamfUploaderTeamsNotifier.toolTip = "Postprocessor for AutoPkg that will send details about a recipe run to a Microsoft Teams webhook" + JamfUploaderTeamsNotifier.action = #selector(appDelegate().JamfUploaderTeamsNotifierAction) +appDelegate().processorsView3rdParty.addSubview(JamfUploaderTeamsNotifier) +} + +func JamfUploaderTeamsNotifier () { + output = """ + + Arguments + + NAME + Name of the application + teams_webhook_url + https://teams.webhook.url + + Processor + com.github.grahampugh.jamf-upload.processors/JamfUploaderTeamsNotifier + +""" + writeOutput () +} diff --git a/RecipeBuilder/CheckAndVerify.swift b/RecipeBuilder/CheckAndVerify.swift new file mode 100644 index 0000000..db017e6 --- /dev/null +++ b/RecipeBuilder/CheckAndVerify.swift @@ -0,0 +1,379 @@ +// +// CheckAndVerify.swift +// RecipeBuilder +// +// Created by Mikael Löfgren on 2022-03-06. +// Copyright © 2022 Mikael Löfgren. All rights reserved. +// + +import Foundation +import Cocoa + +// Inspired by: https://github.com/homebysix/pre-commit-macadmin/blob/main/pre_commit_hooks/check_autopkg_recipes.py + +func returnProcessVersion (processName: String) -> String { + var returnVersion = "" + + struct ProcessVersions { + var name: String + var version: String +} + +var processandversions: [ProcessVersions] +processandversions = [ +ProcessVersions(name:"AppDmgVersioner", version:"0"), // no version needed? +ProcessVersions(name:"AppPkgCreator", version:"1.0"), +ProcessVersions(name:"CodeSignatureVerifier", version:"0.3.1"), +ProcessVersions(name:"Copier", version:"0"), // no version needed? +ProcessVersions(name:"CURLTextSearcher", version:"0.5.1"), +ProcessVersions(name:"DeprecationWarning", version:"1.1"), +ProcessVersions(name:"DmgCreator", version:"0"), // no version needed? +ProcessVersions(name:"EndOfCheckPhase", version:"0.1.0"), +ProcessVersions(name:"FileCreator", version:"0"), // no version needed? +ProcessVersions(name:"FileFinder", version:"0.2.3"), +ProcessVersions(name:"FileMover", version:"0.2.9"), +ProcessVersions(name:"FlatPkgPacker", version:"0.2.4"), +ProcessVersions(name:"FlatPkgUnpacker", version:"0.1.0"), +ProcessVersions(name:"GitHubReleasesInfoProvider", version:"0.5.0"), +ProcessVersions(name:"Installer", version:"0.4.0"), +ProcessVersions(name:"InstallFromDMG", version:"0.4.0"), +ProcessVersions(name:"MunkiCatalogBuilder", version:"0.1.0"), +ProcessVersions(name:"MunkiImporter", version:"0.1.0"), +ProcessVersions(name:"MunkiInfoCreator", version:"0"), // no version needed? +ProcessVersions(name:"MunkiInstallsItemsCreator", version:"0.1.0"), +ProcessVersions(name:"MunkiPkginfoMerger", version:"0.1.0"), +ProcessVersions(name:"MunkiSetDefaultCatalog", version:"0.4.2"), +ProcessVersions(name:"PackageRequired", version:"0.5.1"), +ProcessVersions(name:"PathDeleter", version:"0.1.0"), +ProcessVersions(name:"PkgCopier", version:"0.1.0"), +ProcessVersions(name:"PkgCreator", version:"0"),// no version needed? +ProcessVersions(name:"PkgExtractor", version:"0.1.0"), +ProcessVersions(name:"PkgInfoCreator", version:"0"),// no version needed? +ProcessVersions(name:"PkgPayloadUnpacker", version:"0.1.0"), +ProcessVersions(name:"PkgRootCreator", version:"0"),// no version needed? +ProcessVersions(name:"PlistEditor", version:"0.1.0"), +ProcessVersions(name:"PlistReader", version:"0.2.5"), +ProcessVersions(name:"SparkleUpdateInfoProvider", version:"0.1.0"), +ProcessVersions(name:"StopProcessingIf", version:"0.1.0"), +ProcessVersions(name:"Symlinker", version:"0.1.0"), +ProcessVersions(name:"Unarchiver", version:"0.1.0"), +ProcessVersions(name:"URLDownloader", version:"0"), // no version needed? +ProcessVersions(name:"URLTextSearcher", version:"0.2.9"), +ProcessVersions(name:"Versioner", version:"0.1.0") +] + + let nameArray = processandversions.filter{$0.name.hasPrefix("\(processName)")}.map{$0.version} + + if !nameArray.isEmpty { + returnVersion = nameArray[0] + } + return returnVersion +} + +// Not in use +//struct InputValues:Codable { +//var Name:String +//private enum CodingKeys : String, CodingKey { +// case Name = "NAME" +//} +//} + +struct ProcessValues:Codable { + var Processor:String + private enum CodingKeys : String, CodingKey { + case Processor + } +} + +struct RecipePlistConfig:Codable { +var Description: String? +var Identifier: String +var MinimumVersion: String? +var ParentRecipe: String? +//var Input: InputValues? +var Process: [ProcessValues]? + +private enum CodingKeys : String, CodingKey { + case Description = "Description" + case Identifier = "Identifier" + case MinimumVersion = "MinimumVersion" + case ParentRecipe = "ParentRecipe" + //case Input = "Input" + case Process +} +} + + +struct Recipe { + let identifier: String + let path: String +} + +var recipesArraySet: [Recipe] = [] +var recipesArray: [String] = [] +var checkResults = "" + +func checkDuplicates(array: [String]) -> String { + var encountered = Set() + var duplicate = Set() + var result = "" + + for value in array { + if encountered.contains(value) { + // Add duplicate to the set + duplicate.insert(value) + } + else { + // Add value to the set + encountered.insert(value) + } + } + + for all in duplicate { + result += "\n⚠️ Duplicated identifier\nIdentifier: \(all)\n" + for recipePath in recipesArraySet { + if recipePath.identifier == all { + // Append the filepath to results + result += "File: \(recipePath.path)\n" + } + } + } + return result +} + + + +func startCheckAndVerify () { + + var defaultPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/AutoPkg/").path + + if !FileManager.default.fileExists(atPath: defaultPath) { defaultPath = FileManager.default.homeDirectoryForCurrentUser.path } + + let dialog = NSOpenPanel(); + dialog.message = "Choose a folder to verify .recipes files from" + dialog.directoryURL = NSURL.fileURL(withPath: defaultPath, isDirectory: true) + dialog.showsResizeIndicator = true; + dialog.showsHiddenFiles = true; + dialog.canCreateDirectories = false; + dialog.canChooseDirectories = true; + dialog.canChooseFiles = false; + dialog.allowsMultipleSelection = false; + + + + + if (dialog.runModal() == NSApplication.ModalResponse.OK) { + let result = dialog.url + + if (result != nil) { + defaultPath = result!.path + } + } else { + // User clicked on "Cancel" + appDelegate().fileOptions.selectItem(at: 0) + appDelegate().spinner.isHidden=true + return + } + + let recipeRepoDir = defaultPath + if !FileManager.default.fileExists(atPath: recipeRepoDir) { + print("Path doesnt exist at: \(recipeRepoDir)") + appDelegate().fileOptions.selectItem(at: 0) + appDelegate().spinner.isHidden=true + return } + + + let enumerator = FileManager.default.enumerator(atPath: recipeRepoDir) + let filePaths = enumerator?.allObjects as! [String] + var recipeFilePaths = filePaths.filter{$0.hasSuffix(".recipe")} + var recipeYamlFilePaths = filePaths.filter{$0.hasSuffix(".recipe.yaml")} + let totalRecipes = recipeFilePaths.count + recipeYamlFilePaths.count + + if recipeFilePaths.isEmpty { + appDelegate().fileOptions.selectItem(at: 0) + appDelegate().spinner.isHidden=true + return + } + + let startText = "Start checking \(totalRecipes) recipes files..." + appDelegate().spinner.isHidden=false + appDelegate().spinner.startAnimation(appDelegate) + appDelegate().logWindow.orderFront(Any?.self) + highlightr!.theme.codeFont = NSFont(name: "Menlo", size: 12) + let highlightedCode = highlightr!.highlight(startText, as: "bash")! + appDelegate().logTextView.string = "" + appDelegate().logTextView.textStorage?.insert(NSAttributedString(attributedString: highlightedCode), at: 0) + + DispatchQueue.global(qos: .userInteractive).async { + + + for recipes in recipeFilePaths { + let path = "\(recipeRepoDir)/\(recipes)" + + func parseRecipe() -> RecipePlistConfig { + let url = URL(fileURLWithPath: path) + guard let data = try? Data(contentsOf: url), + let preferences = try? PropertyListDecoder().decode(RecipePlistConfig.self, from: data) + else { + // If problem parsing file set Identifier to Empty + return RecipePlistConfig(Identifier: "Empty") + } + + return preferences + } + var minimumVersion = "MISSING" + if parseRecipe().MinimumVersion != nil { + // Value from plist + minimumVersion = parseRecipe().MinimumVersion! + } + + func checkProcessors () { + var processArray: [String] = [] + if parseRecipe().Process != nil { + for all in parseRecipe().Process! { + + // Value of process minimum (minimum version of autopkg required to run this recipe) + // and value from plist should be higher or equal + let processVersion = returnProcessVersion(processName: all.Processor) + + // if the vaules are Ascending 1..2..3 or same then ok + let versionCheck = processVersion.compare(minimumVersion, options: .numeric) + let ascending = versionCheck == ComparisonResult.orderedAscending + let orderedSame = versionCheck == ComparisonResult.orderedSame + //var descending = versionCheck == ComparisonResult.orderedDescending + + if ascending == false && orderedSame == false { + checkResults += "\n⚠️ Processor: \(all.Processor) needs MinimumVersion: \(processVersion), now version: \(minimumVersion) is set in:\nFile: \(path)\n" + +// print(all.Processor) +// print("Processor version:\(processVersion)") +// print("Plist version:\(minimumVersion)") + } + + // Check deprecated processors + if all.Processor == "CURLDownloader" { + checkResults += "\n⚠️ Processor: \(all.Processor) is deprecated\nFile: \(path)\n" + } + if all.Processor == "BrewCaskInfoProvider" { + checkResults += "\n⚠️ Processor: \(all.Processor) is deprecated\nFile: \(path)\n" + } + // Warn if any superclass processors + if all.Processor == "URLGetter" { + checkResults += "\n⚠️ Processor: \(all.Processor) is a superclass processor\nFile: \(path)\n" + } + processArray.append(all.Processor) + } + } + + + // Check EndofCheckPhase is after URLDownloader using Array with Index + let urlDownloader = processArray.firstIndex(where: {$0 == "URLDownloader"}) + let endOfCheckPhase = processArray.firstIndex(where: {$0 == "EndOfCheckPhase"}) + if urlDownloader != nil && endOfCheckPhase != nil { + // Convert to Int + let urlDownloaderInt = Int(urlDownloader!) + let endOfCheckPhaseInt = Int(endOfCheckPhase!) + + if urlDownloaderInt > endOfCheckPhaseInt { + checkResults += "\n⚠️ Processor: URLDownloader should be used before Processor: EndOfCheckPhase\nFile: \(path)\n" + } + + if urlDownloaderInt + 1 < endOfCheckPhaseInt { + checkResults += "\n⚠️ Processor: EndOfCheckPhase is recommended to be directly after Processor: URLDownloader\nFile: \(path)\n" + } + + } else { + if urlDownloader != nil && endOfCheckPhase == nil { + checkResults += "\n⚠️ Processor: URLDownloader exist but missing Processor: EndOfCheckPhase\nFile: \(path)\n" + } + + } + } + + checkProcessors () + + if parseRecipe().Identifier != "Empty" { + // Add Identifier to Array so we can check for duplicates + recipesArraySet.append(Recipe(identifier: parseRecipe().Identifier, path: path)) + + if parseRecipe().ParentRecipe != nil { + // If ParentRecipe is the same as Identifier then show warning + if parseRecipe().ParentRecipe == parseRecipe().Identifier { + checkResults += "\n🛑 ParentRecipe is the same as Identifier\nFile: \(path)\n" + } + } + } else { + // We get this error: Failed to set posix_spawn_file_actions for fd -1 at index 0 with errno 9 + // if we process to many files like ~3000 with a shell command + let plutil = shell("/usr/bin/plutil \"\(path)\"").replacingOccurrences(of: ":", with: "\nError:", options: [.regularExpression, .caseInsensitive]) + checkResults += "\n🛑 Error reading file\nFile: \(plutil)\n" + } + } + +if !recipeYamlFilePaths.isEmpty { + for yamlRecipes in recipeYamlFilePaths { + let path = "\(recipeRepoDir)/\(yamlRecipes)" + var yamlID = "" + if freopen(path, "r", stdin) == nil { + perror(path) + } + while let line = readLine() { + if line.contains("Identifier:") { + yamlID = line.replacingOccurrences(of: "Identifier: ", with: "", options: [.regularExpression, .caseInsensitive]) + recipesArraySet.append(Recipe(identifier: yamlID, path: path)) + } + + if line.contains("ParentRecipe:") { + let yamlParentRecipe = line.replacingOccurrences(of: "ParentRecipe: ", with: "", options: [.regularExpression, .caseInsensitive]) + + if yamlParentRecipe == yamlID { + checkResults += "\n🛑 ParentRecipe is the same as Identifier\nFile: \(path)\n" + } + } + + if line.contains("MinimumVersion:") { + var yamlMinimumVersion: String + yamlMinimumVersion = line.replacingOccurrences(of: "MinimumVersion: ", with: "", options: [.regularExpression, .caseInsensitive]) + yamlMinimumVersion = yamlMinimumVersion.replacingOccurrences(of: "\"", with: "", options: [.regularExpression, .caseInsensitive]) + yamlMinimumVersion = yamlMinimumVersion.replacingOccurrences(of: "'", with: "", options: [.regularExpression, .caseInsensitive]) + let yamlMinimumVersionDouble = Double(yamlMinimumVersion) ?? 0 + + if yamlMinimumVersionDouble < 2.3 { + checkResults += "\n⚠️ MinimumVersion is lower then 2.3\nFile: \(path)\n" + } + } + } + } + } + + DispatchQueue.main.async { + // Back on the main thread + recipesArray = recipesArraySet.map({ $0.identifier }) + + checkResults += checkDuplicates(array: recipesArray) + + if checkResults.isEmpty { + checkResults += ("✅ No problems found\n") + } + + checkResults += """ +-------------------------------------------- +Done checking \(totalRecipes) recipes files\n +""" + appDelegate().logWindow.orderFront(Any?.self) + highlightr!.theme.codeFont = NSFont(name: "Menlo", size: 12) + let highlightedCode = highlightr!.highlight(checkResults, as: "bash")! + appDelegate().logTextView.string = "" + appDelegate().logTextView.textStorage?.insert(NSAttributedString(attributedString: highlightedCode), at: 0) + appDelegate().fileOptions.selectItem(at: 0) + appDelegate().spinner.isHidden=true + // Reset all values + recipeFilePaths.removeAll() + recipeYamlFilePaths.removeAll() + recipesArray.removeAll() + recipesArraySet.removeAll() + checkResults = "" + +} + } +} diff --git a/RecipeBuilder/Functions.swift b/RecipeBuilder/Functions.swift index 6f12966..72f7b7b 100644 --- a/RecipeBuilder/Functions.swift +++ b/RecipeBuilder/Functions.swift @@ -20,6 +20,7 @@ var name = String () var identififerTextField = String () var format = "" var recipeFileName = "" +var tmpRecipeFile = "" let recipeBuilderFolder = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/AutoPkg/RecipeBuilder Output/").path var recipeBuilderOutputFolderCreate = "" var downloadPath = "" @@ -117,14 +118,37 @@ func writeOutput () { func writePopOvertext (processor: String, extraHelpText: String) { - var processorInfo = shell("/usr/local/bin/autopkg processor-info \(processor)") - processorInfo = processorInfo+"\nExtra Note:\n\(extraHelpText)" + var processorInfo = "" + if !processor.isEmpty { + processorInfo = shell("/usr/local/bin/autopkg processor-info \(processor)") + processorInfo = processorInfo+"\nExtra Note:\n\(extraHelpText)" + } else { + processorInfo = extraHelpText + } + + let attributedText = [ NSAttributedString.Key.font: NSFont(name: "Menlo", size: 10.0)! ] + let processorInfoAttributed = NSAttributedString(string: processorInfo, attributes: attributedText) + appDelegate().helpPopoverText.string = "" + appDelegate().helpPopoverText.textStorage?.insert(NSAttributedString(attributedString: processorInfoAttributed), at: 0) +} + +func writePopOvertextJamfUploader (processor: String, extraHelpText: String) { + var processorInfo = "" + if !processor.isEmpty { + processorInfo = shell("/usr/local/bin/autopkg processor-info -r com.github.grahampugh.jamf-upload.processors \(processor)") + processorInfo = processorInfo+"\nExtra Note:\n\(extraHelpText)" + } else { + processorInfo = extraHelpText + } + let attributedText = [ NSAttributedString.Key.font: NSFont(name: "Menlo", size: 10.0)! ] let processorInfoAttributed = NSAttributedString(string: processorInfo, attributes: attributedText) appDelegate().helpPopoverText.string = "" appDelegate().helpPopoverText.textStorage?.insert(NSAttributedString(attributedString: processorInfoAttributed), at: 0) } + + func writeOutputUserButtons () { insertionPointIndex = (appDelegate().outputTextField.selectedRanges.first?.rangeValue.location)! //let xmlFormatOutput = prettyFormat(xmlString: output) @@ -203,6 +227,9 @@ Add Identifier key and string to the document for example: case let identifierFormat where identifierFormat.contains(".install."): formatTitle = "install" format = ".install." + case let identifierFormat where identifierFormat.contains(".jamf."): + formatTitle = "jamf" + format = ".jamf." case let identifierFormat where identifierFormat.contains(".jss."): formatTitle = "jss" format = ".jss." @@ -430,6 +457,8 @@ func createNewDocument () { description = "Downloads the latest version of \(name) and creates a package" case "install": description = "Installs the latest version of \(name)" + case "jamf": + description = "Downloads the latest version of \(name) and then uploads to Jamf" case "jss": description = "Downloads the latest version of \(name) and then uploads to the JSS" case "lanrev": @@ -471,6 +500,53 @@ output = """ """ + +let munkiOutput = """ + + + + + Description + \(description) + Identifier + \(identifierManually) + Input + + NAME + \(name) + MUNKI_REPO_SUBDIR + apps/%NAME% + pkginfo + + catalogs + + testing + + description + INSERT_DESCRIPTION_HERE + category + INSERT_CATEGORY_HERE + developer + INSERT_DEVELOPER_HERE + display_name + INSERT_DISPLAY_NAME_HERE + name + %NAME% + unattended_install + + + + MinimumVersion + 0.5.0 + ParentRecipe + INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE + Process + + + + + +""" let downloadOutput = """ @@ -497,6 +573,65 @@ let downloadOutput = """ """ + let jamfOutput = """ + + + + + Description + \(description) + Identifier + \(identifierManually) + Input + + CATEGORY + Applications + GROUP_NAME + %NAME%-update-smart + GROUP_TEMPLATE + SmartGroupTemplate.xml + NAME + \(name) + PATCH_SOFTWARETITLE + Name of the patch softwaretitle (e.g. 'Mozilla Firefox') used in Jamf + PATCH_NAME + Name of the patch policy (e.g. 'Mozilla Firefox - 93.02.10') + PATCH_TEMPLATE + PatchTemplate-selfservice.xml + PATCH_ICON_POLICY_NAME + Name of an already existing (!) policy (not a patch policy) + POLICY_CATEGORY + Testing + POLICY_NAME + Install Latest %NAME% + POLICY_TEMPLATE + PolicyTemplate.xml + SELF_SERVICE_DESCRIPTION + Install Latest %NAME% + SELF_SERVICE_DISPLAY_NAME + Install Latest %NAME% + SELF_SERVICE_ICON + \(name).png + TESTING_GROUP_NAME + Testing + UPDATE_PREDICATE + pkg_uploaded == False + SLACK_WEBHOOK_URL + https://url.to.slack.com + TEAMS_WEBHOOK_URL + https://url.to.teams.com + + MinimumVersion + 2.3 + ParentRecipe + INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE_OR_REMOVE + Process + + + + + + """ let jssOutput = """ @@ -591,13 +726,53 @@ let jssOutput = """ range = (xmlFormatOutput as NSString).range(of: "") attributedReplaceText = NSAttributedString(string: "", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + } else if descriptionFormat == "jamf" { + output = jamfOutput + outputNewDocument () + var range = (xmlFormatOutput as NSString).range(of: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE_OR_REMOVE") + var attributedReplaceText = NSAttributedString(string: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE_OR_REMOVE", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + + + range = (xmlFormatOutput as NSString).range(of: "") + attributedReplaceText = NSAttributedString(string: "", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) } else if descriptionFormat == "jss" { - output = jssOutput + output = jssOutput + outputNewDocument () + let range = (xmlFormatOutput as NSString).range(of: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE") + let attributedReplaceText = NSAttributedString(string: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + + } else if descriptionFormat == "munki" { + output = munkiOutput outputNewDocument () - let range = (xmlFormatOutput as NSString).range(of: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE") - let attributedReplaceText = NSAttributedString(string: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE", attributes: yellowBackgroundAttributes) + var range = (xmlFormatOutput as NSString).range(of: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE") + var attributedReplaceText = NSAttributedString(string: "INSERT_YOUR_PARENT_RECIPE_IDENTIFIER_HERE", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + + range = (xmlFormatOutput as NSString).range(of: "INSERT_DESCRIPTION_HERE") + attributedReplaceText = NSAttributedString(string: "INSERT_DESCRIPTION_HERE", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + + range = (xmlFormatOutput as NSString).range(of: "INSERT_CATEGORY_HERE") + attributedReplaceText = NSAttributedString(string: "INSERT_CATEGORY_HERE", attributes: yellowBackgroundAttributes) appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + + range = (xmlFormatOutput as NSString).range(of: "INSERT_DEVELOPER_HERE") + attributedReplaceText = NSAttributedString(string: "INSERT_DEVELOPER_HERE", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + + range = (xmlFormatOutput as NSString).range(of: "INSERT_DISPLAY_NAME_HERE") + attributedReplaceText = NSAttributedString(string: "INSERT_DISPLAY_NAME_HERE", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + + range = (xmlFormatOutput as NSString).range(of: "") + attributedReplaceText = NSAttributedString(string: "", attributes: yellowBackgroundAttributes) + appDelegate().outputTextField.textStorage?.replaceCharacters(in: range, with: attributedReplaceText) + } else { outputNewDocument () let range = (xmlFormatOutput as NSString).range(of: "") @@ -618,7 +793,7 @@ func finaliseDocument () { if identifierMismatch == true {return} if recipeFileName == "" { return } createRecipeBuilderFolders(createFolder: "tmp") - let tmpRecipeFile = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/AutoPkg/RecipeBuilder Output/tmp/\(recipeFileName)").path + tmpRecipeFile = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/AutoPkg/RecipeBuilder Output/tmp/\(recipeFileName)").path let documentDirURL = URL(fileURLWithPath: tmpRecipeFile) // Save data to file let fileURL = documentDirURL @@ -686,8 +861,9 @@ func autoPkgRunner () { getIdentifier () if recipeFileName == "" { return } appDelegate().logWindow.orderFront(Any?.self) - createRecipeBuilderFolders(createFolder: "tmp") - let tmpRecipeFile = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/AutoPkg/RecipeBuilder Output/tmp/\(recipeFileName)").path + createRecipeBuilderFolders(createFolder: "") + let randomNumber = Int.random(in: 0 ..< 999) + tmpRecipeFile = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/AutoPkg/RecipeBuilder Output/\(recipeFileName).temp_\(randomNumber)").path let documentDirURL = URL(fileURLWithPath: tmpRecipeFile) // Save data to file let fileURL = documentDirURL @@ -753,6 +929,7 @@ func toggleExternalEditor () { appDelegate().bbedit.state = .off appDelegate().sublimeText.state = .off appDelegate().textMate.state = .off + appDelegate().visualStudioCode.state = .off } if appDelegate().selectedExternalEditor == "BBEdit" { @@ -760,6 +937,7 @@ func toggleExternalEditor () { appDelegate().bbedit.state = .on appDelegate().sublimeText.state = .off appDelegate().textMate.state = .off + appDelegate().visualStudioCode.state = .off } if appDelegate().selectedExternalEditor == "Sublime Text" { @@ -767,6 +945,7 @@ func toggleExternalEditor () { appDelegate().bbedit.state = .off appDelegate().sublimeText.state = .on appDelegate().textMate.state = .off + appDelegate().visualStudioCode.state = .off } if appDelegate().selectedExternalEditor == "TextMate" { @@ -774,6 +953,15 @@ func toggleExternalEditor () { appDelegate().bbedit.state = .off appDelegate().sublimeText.state = .off appDelegate().textMate.state = .on + appDelegate().visualStudioCode.state = .off + } + + if appDelegate().selectedExternalEditor == "Visual Studio Code" { + appDelegate().atom.state = .off + appDelegate().bbedit.state = .off + appDelegate().sublimeText.state = .off + appDelegate().textMate.state = .off + appDelegate().visualStudioCode.state = .on } } @@ -943,7 +1131,7 @@ func shell(_ command: String) -> String { let pipe = Pipe() task.standardOutput = pipe task.launch() - + let data = pipe.fileHandleForReading.readDataToEndOfFile() let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String @@ -1023,10 +1211,15 @@ try FileManager.default.createDirectory(atPath: recipeBuilderOutputFolderCreate, func deleteTmpFolder () { let tmpRecipeFolder = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library/AutoPkg/RecipeBuilder Output/tmp/").path - // If tempfolder exist - if FileManager.default.fileExists(atPath: tmpRecipeFolder) { - // Delete tempfolder - try? FileManager.default.removeItem(atPath: tmpRecipeFolder) - } + // If tempfolder exist + if FileManager.default.fileExists(atPath: tmpRecipeFolder) { + // Delete tempfolder + try? FileManager.default.removeItem(atPath: tmpRecipeFolder) + } + // If tempfile exist + if FileManager.default.fileExists(atPath: tmpRecipeFile) { + // Delete tempfile + try? FileManager.default.removeItem(atPath: tmpRecipeFile) + } } diff --git a/RecipeBuilder/Info.plist b/RecipeBuilder/Info.plist index acc8d94..6bae38d 100644 --- a/RecipeBuilder/Info.plist +++ b/RecipeBuilder/Info.plist @@ -34,7 +34,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.03 + $(MARKETING_VERSION) CFBundleVersion 1 LSApplicationCategoryType diff --git a/RecipeBuilder/JamfUploaderHelpTexts.swift b/RecipeBuilder/JamfUploaderHelpTexts.swift new file mode 100644 index 0000000..2e5e8fb --- /dev/null +++ b/RecipeBuilder/JamfUploaderHelpTexts.swift @@ -0,0 +1,26 @@ +// +// JamfUploaderHelpTexts.swift +// RecipeBuilder +// +// Created by Mikael Löfgren on 2022-03-04. +// Copyright © 2022 Mikael Löfgren. All rights reserved. +// + +import Foundation + +var JamfCategoryUploaderHelp="" +var JamfComputerGroupUploaderHelp="" +var JamfComputerProfileUploaderHelp="" +var JamfDockItemUploaderHelp="" +var JamfExtensionAttributeUploaderHelp="" +var JamfMacAppUploaderHelp="" +var JamfPackageUploaderHelp="" +var JamfPatchUploaderHelp="" +var JamfPolicyDeleterHelp="" +var JamfPolicyLogFlusherHelp="" +var JamfPolicyUploaderHelp="" +var JamfScriptUploaderHelp="" +var JamfSoftwareRestrictionUploaderHelp="" +var JamfUploaderSlackerHelp="" +var JamfUploaderTeamsNotifierHelp="" + diff --git a/RecipeBuilder/UserButtons.swift b/RecipeBuilder/UserButtons.swift index 67ef186..95553f2 100644 --- a/RecipeBuilder/UserButtons.swift +++ b/RecipeBuilder/UserButtons.swift @@ -61,7 +61,7 @@ var output10 = "" var help10 = "" func createButton1 (title: String) { -let button1 = NSButton(frame: NSRect(x: 17, y: 255, width: 195, height: 17)) +let button1 = NSButton(frame: NSRect(x: 17, y: 258, width: 191, height: 17)) button1.title = title button1.bezelStyle = NSButton.BezelStyle.inline button1.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -72,7 +72,7 @@ appDelegate().buttonView.addSubview(button1) } func createButton2 (title: String) { -let button2 = NSButton(frame: NSRect(x: 17, y: 234, width: 195, height: 17)) +let button2 = NSButton(frame: NSRect(x: 17, y: 237, width: 191, height: 17)) button2.title = title button2.bezelStyle = NSButton.BezelStyle.inline button2.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -83,7 +83,7 @@ appDelegate().buttonView.addSubview(button2) } func createButton3 (title: String) { -let button3 = NSButton(frame: NSRect(x: 17, y: 213, width: 195, height: 17)) +let button3 = NSButton(frame: NSRect(x: 17, y: 216, width: 191, height: 17)) button3.title = title button3.bezelStyle = NSButton.BezelStyle.inline button3.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -94,7 +94,7 @@ appDelegate().buttonView.addSubview(button3) } func createButton4 (title: String) { -let button4 = NSButton(frame: NSRect(x: 17, y: 192, width: 195, height: 17)) +let button4 = NSButton(frame: NSRect(x: 17, y: 195, width: 191, height: 17)) button4.title = title button4.bezelStyle = NSButton.BezelStyle.inline button4.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -105,7 +105,7 @@ appDelegate().buttonView.addSubview(button4) } func createButton5 (title: String) { -let button5 = NSButton(frame: NSRect(x: 17, y: 171, width: 195, height: 17)) +let button5 = NSButton(frame: NSRect(x: 17, y: 174, width: 191, height: 17)) button5.title = title button5.bezelStyle = NSButton.BezelStyle.inline button5.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -116,7 +116,7 @@ appDelegate().buttonView.addSubview(button5) } func createButton6 (title: String) { -let button6 = NSButton(frame: NSRect(x: 17, y: 150, width: 195, height: 17)) +let button6 = NSButton(frame: NSRect(x: 17, y: 153, width: 191, height: 17)) button6.title = title button6.bezelStyle = NSButton.BezelStyle.inline button6.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -127,7 +127,7 @@ appDelegate().buttonView.addSubview(button6) } func createButton7 (title: String) { -let button7 = NSButton(frame: NSRect(x: 17, y: 129, width: 195, height: 17)) +let button7 = NSButton(frame: NSRect(x: 17, y: 132, width: 191, height: 17)) button7.title = title button7.bezelStyle = NSButton.BezelStyle.inline button7.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -138,7 +138,7 @@ button7.action = #selector(appDelegate().buttonAction7) } func createButton8 (title: String) { -let button8 = NSButton(frame: NSRect(x: 17, y: 108, width: 195, height: 17)) +let button8 = NSButton(frame: NSRect(x: 17, y: 111, width: 191, height: 17)) button8.title = title button8.bezelStyle = NSButton.BezelStyle.inline button8.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -149,7 +149,7 @@ appDelegate().buttonView.addSubview(button8) } func createButton9 (title: String) { -let button9 = NSButton(frame: NSRect(x: 17, y: 87, width: 195, height: 17)) +let button9 = NSButton(frame: NSRect(x: 17, y: 90, width: 191, height: 17)) button9.title = title button9.bezelStyle = NSButton.BezelStyle.inline button9.setButtonType(NSButton.ButtonType.momentaryPushIn) @@ -160,7 +160,7 @@ appDelegate().buttonView.addSubview(button9) } func createButton10 (title: String) { -let button10 = NSButton(frame: NSRect(x: 17, y: 66, width: 195, height: 17)) +let button10 = NSButton(frame: NSRect(x: 17, y: 69, width: 191, height: 17)) button10.title = title button10.bezelStyle = NSButton.BezelStyle.inline button10.setButtonType(NSButton.ButtonType.momentaryPushIn)