From 0fcc76bad58df4dc03a0a82be73ca5b1046e5b3b Mon Sep 17 00:00:00 2001 From: Mitch Francese <2227948+tux234@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:53:34 -0500 Subject: [PATCH] Add FMA: Google Drive (#35915) **Related issue:** Resolves # --------- Co-authored-by: Allen Houchins <32207388+allenhouchins@users.noreply.github.com> --- .../inputs/homebrew/google-drive.json | 8 ++ .../inputs/winget/google-drive.json | 12 +++ .../winget/scripts/google_drive_install.ps1 | 23 +++++ .../winget/scripts/google_drive_uninstall.ps1 | 79 ++++++++++++++++++ ee/maintained-apps/outputs/apps.json | 14 ++++ .../outputs/google-drive/darwin.json | 21 +++++ .../outputs/google-drive/windows.json | 21 +++++ .../components/icons/GoogleDrive.tsx | 14 ++++ .../images/app-icon-google-drive-60x60@2x.png | Bin 0 -> 8613 bytes 9 files changed, 192 insertions(+) create mode 100644 ee/maintained-apps/inputs/homebrew/google-drive.json create mode 100644 ee/maintained-apps/inputs/winget/google-drive.json create mode 100644 ee/maintained-apps/inputs/winget/scripts/google_drive_install.ps1 create mode 100644 ee/maintained-apps/inputs/winget/scripts/google_drive_uninstall.ps1 create mode 100644 ee/maintained-apps/outputs/google-drive/darwin.json create mode 100644 ee/maintained-apps/outputs/google-drive/windows.json create mode 100644 frontend/pages/SoftwarePage/components/icons/GoogleDrive.tsx create mode 100644 website/assets/images/app-icon-google-drive-60x60@2x.png diff --git a/ee/maintained-apps/inputs/homebrew/google-drive.json b/ee/maintained-apps/inputs/homebrew/google-drive.json new file mode 100644 index 0000000000..eac6182765 --- /dev/null +++ b/ee/maintained-apps/inputs/homebrew/google-drive.json @@ -0,0 +1,8 @@ +{ + "name": "Google Drive", + "slug": "google-drive/darwin", + "unique_identifier": "com.google.drivefs", + "token": "google-drive", + "installer_format": "dmg", + "default_categories": ["Productivity"] +} diff --git a/ee/maintained-apps/inputs/winget/google-drive.json b/ee/maintained-apps/inputs/winget/google-drive.json new file mode 100644 index 0000000000..7f68f0b4e3 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/google-drive.json @@ -0,0 +1,12 @@ +{ + "name": "Google Drive", + "slug": "google-drive/windows", + "package_identifier": "Google.GoogleDrive", + "unique_identifier": "Google Drive", + "install_script_path": "ee/maintained-apps/inputs/winget/scripts/google_drive_install.ps1", + "uninstall_script_path": "ee/maintained-apps/inputs/winget/scripts/google_drive_uninstall.ps1", + "installer_arch": "x64", + "installer_type": "exe", + "installer_scope": "machine", + "default_categories": ["Productivity"] +} diff --git a/ee/maintained-apps/inputs/winget/scripts/google_drive_install.ps1 b/ee/maintained-apps/inputs/winget/scripts/google_drive_install.ps1 new file mode 100644 index 0000000000..c7b959a1a3 --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/google_drive_install.ps1 @@ -0,0 +1,23 @@ +$exeFilePath = "${env:INSTALLER_PATH}" + +try { + +$processOptions = @{ + FilePath = "$exeFilePath" + ArgumentList = "--silent --skip_launch_new --gsuite_shortcuts=false" + PassThru = $true + Wait = $true +} + +# Start process and track exit code +$process = Start-Process @processOptions +$exitCode = $process.ExitCode + +# Prints the exit code +Write-Host "Install exit code: $exitCode" +Exit $exitCode + +} catch { + Write-Host "Error: $_" + Exit 1 +} diff --git a/ee/maintained-apps/inputs/winget/scripts/google_drive_uninstall.ps1 b/ee/maintained-apps/inputs/winget/scripts/google_drive_uninstall.ps1 new file mode 100644 index 0000000000..19107e150f --- /dev/null +++ b/ee/maintained-apps/inputs/winget/scripts/google_drive_uninstall.ps1 @@ -0,0 +1,79 @@ +$softwareName = "Google Drive" + +$uninstallArgs = "--silent --force_stop" + +$expectedExitCodes = @() + +$machineKey = ` + 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' +$machineKey32on64 = ` + 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' + +$exitCode = 0 + +try { + + [array]$uninstallKeys = Get-ChildItem ` + -Path @($machineKey, $machineKey32on64) ` + -ErrorAction SilentlyContinue | + ForEach-Object { Get-ItemProperty $_.PSPath } + + $foundUninstaller = $false + foreach ($key in $uninstallKeys) { + if ($key.DisplayName -eq $softwareName) { + $foundUninstaller = $true + # Get the uninstall command. + $uninstallCommand = if ($key.QuietUninstallString) { + $key.QuietUninstallString + } else { + $key.UninstallString + } + + # Split the command and args + $splitArgs = $uninstallCommand.Split('"') + if ($splitArgs.Length -gt 1) { + if ($splitArgs.Length -eq 3) { + $uninstallArgs = "$( $splitArgs[2] ) $uninstallArgs".Trim() + } elseif ($splitArgs.Length -gt 3) { + Throw ` + "Uninstall command contains multiple quoted strings. " + + "Please update the uninstall script.`n" + + "Uninstall command: $uninstallCommand" + } + $uninstallCommand = $splitArgs[1] + } + Write-Host "Uninstall command: $uninstallCommand" + Write-Host "Uninstall args: $uninstallArgs" + + $processOptions = @{ + FilePath = $uninstallCommand + PassThru = $true + Wait = $true + ArgumentList = "$uninstallArgs".Split(' ') + NoNewWindow = $true + } + + # Start process and track exit code + $process = Start-Process @processOptions + $exitCode = $process.ExitCode + + Write-Host "Uninstall exit code: $exitCode" + break + } + } + + if (-not $foundUninstaller) { + Write-Host "Uninstaller for '$softwareName' not found." + Exit 1 + } + +} catch { + Write-Host "Error: $_" + Exit 1 +} + +if ($expectedExitCodes -contains $exitCode) { + $exitCode = 0 +} + +Exit $exitCode diff --git a/ee/maintained-apps/outputs/apps.json b/ee/maintained-apps/outputs/apps.json index 6cf2028366..3db64904bb 100644 --- a/ee/maintained-apps/outputs/apps.json +++ b/ee/maintained-apps/outputs/apps.json @@ -232,6 +232,20 @@ "unique_identifier": "Google Chrome", "description": "Google Chrome Enterprise is a fast, reliable web browser built for performance and compatibility across platforms." }, + { + "name": "Google Drive", + "slug": "google-drive/darwin", + "platform": "darwin", + "unique_identifier": "com.google.drivefs", + "description": "Google Drive is a cloud storage service that allows you to store, sync, and share files across devices with seamless access to your Google Workspace content." + }, + { + "name": "Google Drive", + "slug": "google-drive/windows", + "platform": "windows", + "unique_identifier": "Google Drive", + "description": "Google Drive is a cloud storage service that allows you to store, sync, and share files across devices with seamless access to your Google Workspace content." + }, { "name": "iMazing Profile Editor", "slug": "imazing-profile-editor/darwin", diff --git a/ee/maintained-apps/outputs/google-drive/darwin.json b/ee/maintained-apps/outputs/google-drive/darwin.json new file mode 100644 index 0000000000..7998df5373 --- /dev/null +++ b/ee/maintained-apps/outputs/google-drive/darwin.json @@ -0,0 +1,21 @@ +{ + "versions": [ + { + "version": "117.0.0", + "queries": { + "exists": "SELECT 1 FROM apps WHERE bundle_identifier = 'com.google.drivefs';" + }, + "installer_url": "https://dl.google.com/drive-file-stream/5-percent/GoogleDrive.dmg", + "install_script_ref": "b1cef788", + "uninstall_script_ref": "9f918e97", + "sha256": "no_check", + "default_categories": [ + "Productivity" + ] + } + ], + "refs": { + "9f918e97": "#!/bin/sh\n\n# variables\nLOGGED_IN_USER=$(scutil <<< \"show State:/Users/ConsoleUser\" | awk '/Name :/ { print $3 }')\n# functions\n\nexpand_pkgid_and_map() {\n local PKGID=\"$1\"\n local FUNC=\"$2\"\n if [[ \"$PKGID\" == *\"*\" ]]; then\n local prefix=\"${PKGID%\\*}\"\n echo \"Expanding wildcard for PKGID: $PKGID\"\n for receipt in $(pkgutil --pkgs | grep \"^${prefix}\"); do\n echo \"Processing $receipt\"\n \"$FUNC\" \"$receipt\"\n done\n else\n \"$FUNC\" \"$PKGID\"\n fi\n}\n\nforget_pkg() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" forget_receipt\n}\n\nforget_receipt() {\n local PKGID=\"$1\"\n sudo pkgutil --forget \"$PKGID\"\n}\n\nquit_application() {\n local bundle_id=\"$1\"\n local timeout_duration=10\n\n # check if the application is running\n if ! osascript -e \"application id \\\"$bundle_id\\\" is running\" 2>/dev/null; then\n return\n fi\n\n local console_user\n console_user=$(stat -f \"%Su\" /dev/console)\n if [[ $EUID -eq 0 && \"$console_user\" == \"root\" ]]; then\n echo \"Not logged into a non-root GUI; skipping quitting application ID '$bundle_id'.\"\n return\n fi\n\n echo \"Quitting application '$bundle_id'...\"\n\n # try to quit the application within the timeout period\n local quit_success=false\n SECONDS=0\n while (( SECONDS < timeout_duration )); do\n if osascript -e \"tell application id \\\"$bundle_id\\\" to quit\" >/dev/null 2>&1; then\n if ! pgrep -f \"$bundle_id\" >/dev/null 2>&1; then\n echo \"Application '$bundle_id' quit successfully.\"\n quit_success=true\n break\n fi\n fi\n sleep 1\n done\n\n if [[ \"$quit_success\" = false ]]; then\n echo \"Application '$bundle_id' did not quit.\"\n fi\n}\n\n\nremove_launchctl_service() {\n local service=\"$1\"\n local booleans=(\"true\" \"false\")\n local plist_status\n local paths\n local should_sudo\n\n echo \"Removing launchctl service ${service}\"\n\n for should_sudo in \"${booleans[@]}\"; do\n plist_status=$(launchctl list \"${service}\" 2>/dev/null)\n\n if [[ $plist_status == \\{* ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo launchctl remove \"${service}\"\n else\n launchctl remove \"${service}\"\n fi\n sleep 1\n fi\n\n paths=(\n \"/Library/LaunchAgents/${service}.plist\"\n \"/Library/LaunchDaemons/${service}.plist\"\n )\n\n # if not using sudo, prepend the home directory to the paths\n if [[ $should_sudo == \"false\" ]]; then\n for i in \"${!paths[@]}\"; do\n paths[i]=\"${HOME}${paths[i]}\"\n done\n fi\n\n for path in \"${paths[@]}\"; do\n if [[ -e \"$path\" ]]; then\n if [[ $should_sudo == \"true\" ]]; then\n sudo rm -f -- \"$path\"\n else\n rm -f -- \"$path\"\n fi\n fi\n done\n done\n}\n\nremove_pkg_files() {\n local PKGID=\"$1\"\n expand_pkgid_and_map \"$PKGID\" remove_receipt_files\n}\n\nremove_receipt_files() {\n local PKGID=\"$1\"\n local PKGINFO VOLUME INSTALL_LOCATION FULL_INSTALL_LOCATION\n\n echo \"pkgutil --pkg-info-plist \\\"$PKGID\\\"\"\n PKGINFO=$(pkgutil --pkg-info-plist \"$PKGID\")\n VOLUME=$(echo \"$PKGINFO\" | awk '/volume<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n INSTALL_LOCATION=$(echo \"$PKGINFO\" | awk '/install-location<\\/key>/ {getline; gsub(/.*|<\\/string>.*/, \"\"); print}')\n\n if [ -z \"$INSTALL_LOCATION\" ] || [ \"$INSTALL_LOCATION\" = \"/\" ]; then\n FULL_INSTALL_LOCATION=\"$VOLUME\"\n else\n FULL_INSTALL_LOCATION=\"$VOLUME/$INSTALL_LOCATION\"\n FULL_INSTALL_LOCATION=$(echo \"$FULL_INSTALL_LOCATION\" | sed 's|//|/|g')\n fi\n\n echo \"sudo pkgutil --only-files --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-files --files \"$PKGID\" | sed \"s|^|/${INSTALL_LOCATION}/|\" | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n echo \"sudo pkgutil --only-dirs --files \\\"$PKGID\\\" | sed \\\"s|^|${FULL_INSTALL_LOCATION}/|\\\" | grep '\\\\.app$' | tr '\\\\\\\\n' '\\\\\\\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\"\n sudo pkgutil --only-dirs --files \"$PKGID\" | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" | grep '\\.app$' | tr '\\n' '\\0' | /usr/bin/sudo -u root -E -- /usr/bin/xargs -0 -- /bin/rm -rf\n\n root_app_dir=$(\n sudo pkgutil --only-dirs --files \"$PKGID\" \\\n | sed \"s|^|${FULL_INSTALL_LOCATION}/|\" \\\n | grep 'Applications' \\\n | awk '{ print length, $0 }' \\\n | sort -n \\\n | head -n1 \\\n | cut -d' ' -f2-\n )\n if [ -n \"$root_app_dir\" ]; then\n echo \"sudo rmdir -p \\\"$root_app_dir\\\" 2>/dev/null || :\"\n sudo rmdir -p \"$root_app_dir\" 2>/dev/null || :\n fi\n}\n\ntrash() {\n local logged_in_user=\"$1\"\n local target_file=\"$2\"\n local timestamp=\"$(date +%Y-%m-%d-%s)\"\n local rand=\"$(jot -r 1 0 99999)\"\n\n # replace ~ with /Users/$logged_in_user\n if [[ \"$target_file\" == ~* ]]; then\n target_file=\"/Users/$logged_in_user${target_file:1}\"\n fi\n\n local trash=\"/Users/$logged_in_user/.Trash\"\n local file_name=\"$(basename \"${target_file}\")\"\n\n if [[ -e \"$target_file\" ]]; then\n echo \"removing $target_file.\"\n mv -f \"$target_file\" \"$trash/${file_name}_${timestamp}_${rand}\"\n else\n echo \"$target_file doesn't exist.\"\n fi\n}\n\nquit_application 'com.google.drivefs'\nquit_application 'com.google.drivefs.finderhelper.findersync'\nremove_pkg_files 'com.google.drivefs.arm64'\nforget_pkg 'com.google.drivefs.arm64'\nremove_pkg_files 'com.google.drivefs.filesystems.dfsfuse.arm64'\nforget_pkg 'com.google.drivefs.filesystems.dfsfuse.arm64'\nremove_pkg_files 'com.google.drivefs.filesystems.dfsfuse.x86_64'\nforget_pkg 'com.google.drivefs.filesystems.dfsfuse.x86_64'\nremove_pkg_files 'com.google.drivefs.shortcuts'\nforget_pkg 'com.google.drivefs.shortcuts'\nremove_pkg_files 'com.google.drivefs.x86_64'\nforget_pkg 'com.google.drivefs.x86_64'\nremove_launchctl_service 'com.google.GoogleUpdater.wake.system'\nremove_launchctl_service 'com.google.keystone.agent'\nremove_launchctl_service 'com.google.keystone.daemon'\nremove_launchctl_service 'com.google.keystone.system.agent'\nremove_launchctl_service 'com.google.keystone.system.xpcservice'\nremove_launchctl_service 'com.google.keystone.xpcservice'\nremove_pkg_files 'com.google.pkg.Keystone'\nforget_pkg 'com.google.pkg.Keystone'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/com.google.drivefs*'\ntrash $LOGGED_IN_USER '~/Library/Application Scripts/EQHXZ8M8AV.group.com.google.drivefs'\ntrash $LOGGED_IN_USER '~/Library/Application Support/FileProvider/com.google.drivefs.fpext'\ntrash $LOGGED_IN_USER '~/Library/Application Support/Google/DriveFS'\ntrash $LOGGED_IN_USER '~/Library/Caches/com.google.drivefs'\ntrash $LOGGED_IN_USER '~/Library/Containers/com.google.drivefs*'\ntrash $LOGGED_IN_USER '~/Library/Group Containers/*group.com.google.drivefs'\ntrash $LOGGED_IN_USER '~/Library/Preferences/com.google.drivefs*.plist'\ntrash $LOGGED_IN_USER '~/Library/Preferences/Google Drive File Stream Helper.plist'\n", + "b1cef788": "#!/bin/sh\n\n# variables\nAPPDIR=\"/Applications/\"\nTMPDIR=$(dirname \"$(realpath $INSTALLER_PATH)\")\n\n# extract contents\nMOUNT_POINT=$(mktemp -d /tmp/dmg_mount_XXXXXX)\nhdiutil attach -plist -nobrowse -readonly -mountpoint \"$MOUNT_POINT\" \"$INSTALLER_PATH\"\nsudo cp -R \"$MOUNT_POINT\"/* \"$TMPDIR\"\nhdiutil detach \"$MOUNT_POINT\"\n# install pkg files\nsudo installer -pkg \"$TMPDIR/GoogleDrive.pkg\" -target /\n" + } +} diff --git a/ee/maintained-apps/outputs/google-drive/windows.json b/ee/maintained-apps/outputs/google-drive/windows.json new file mode 100644 index 0000000000..5349df1c80 --- /dev/null +++ b/ee/maintained-apps/outputs/google-drive/windows.json @@ -0,0 +1,21 @@ +{ + "versions": [ + { + "version": "116.0.6.0", + "queries": { + "exists": "SELECT 1 FROM programs WHERE name = 'Google Drive' AND publisher = 'Google LLC';" + }, + "installer_url": "https://dl.google.com/release2/drive-file-stream/dsiupwjcww5gzroykb7fpxic4q_116.0.6.0/setup.exe", + "install_script_ref": "fa36b892", + "uninstall_script_ref": "785a96b9", + "sha256": "5741e2d046554906b923257b8922d7109e5ac879c9fb6c376c988a5126ae90ae", + "default_categories": [ + "Productivity" + ] + } + ], + "refs": { + "785a96b9": "$softwareName = \"Google Drive\"\n\n$uninstallArgs = \"--silent --force_stop\"\n\n$expectedExitCodes = @()\n\n$machineKey = `\n 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n$machineKey32on64 = `\n 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'\n\n$exitCode = 0\n\ntry {\n\n [array]$uninstallKeys = Get-ChildItem `\n -Path @($machineKey, $machineKey32on64) `\n -ErrorAction SilentlyContinue |\n ForEach-Object { Get-ItemProperty $_.PSPath }\n\n $foundUninstaller = $false\n foreach ($key in $uninstallKeys) {\n if ($key.DisplayName -eq $softwareName) {\n $foundUninstaller = $true\n # Get the uninstall command.\n $uninstallCommand = if ($key.QuietUninstallString) {\n $key.QuietUninstallString\n } else {\n $key.UninstallString\n }\n\n # Split the command and args\n $splitArgs = $uninstallCommand.Split('\"')\n if ($splitArgs.Length -gt 1) {\n if ($splitArgs.Length -eq 3) {\n $uninstallArgs = \"$( $splitArgs[2] ) $uninstallArgs\".Trim()\n } elseif ($splitArgs.Length -gt 3) {\n Throw `\n \"Uninstall command contains multiple quoted strings. \" +\n \"Please update the uninstall script.`n\" +\n \"Uninstall command: $uninstallCommand\"\n }\n $uninstallCommand = $splitArgs[1]\n }\n Write-Host \"Uninstall command: $uninstallCommand\"\n Write-Host \"Uninstall args: $uninstallArgs\"\n\n $processOptions = @{\n FilePath = $uninstallCommand\n PassThru = $true\n Wait = $true\n ArgumentList = \"$uninstallArgs\".Split(' ')\n NoNewWindow = $true\n }\n\n # Start process and track exit code\n $process = Start-Process @processOptions\n $exitCode = $process.ExitCode\n\n Write-Host \"Uninstall exit code: $exitCode\"\n break\n }\n }\n\n if (-not $foundUninstaller) {\n Write-Host \"Uninstaller for '$softwareName' not found.\"\n Exit 1\n }\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n\nif ($expectedExitCodes -contains $exitCode) {\n $exitCode = 0\n}\n\nExit $exitCode\n", + "fa36b892": "$exeFilePath = \"${env:INSTALLER_PATH}\"\n\ntry {\n\n$processOptions = @{\n FilePath = \"$exeFilePath\"\n ArgumentList = \"--silent --skip_launch_new --gsuite_shortcuts=false\"\n PassThru = $true\n Wait = $true\n}\n\n# Start process and track exit code\n$process = Start-Process @processOptions\n$exitCode = $process.ExitCode\n\n# Prints the exit code\nWrite-Host \"Install exit code: $exitCode\"\nExit $exitCode\n\n} catch {\n Write-Host \"Error: $_\"\n Exit 1\n}\n" + } +} diff --git a/frontend/pages/SoftwarePage/components/icons/GoogleDrive.tsx b/frontend/pages/SoftwarePage/components/icons/GoogleDrive.tsx new file mode 100644 index 0000000000..b4abd431fb --- /dev/null +++ b/frontend/pages/SoftwarePage/components/icons/GoogleDrive.tsx @@ -0,0 +1,14 @@ +import * as React from "react"; + +import type { SVGProps } from "react"; + +const GoogleDrive = (props: SVGProps) => ( + + + +); +export default GoogleDrive; diff --git a/website/assets/images/app-icon-google-drive-60x60@2x.png b/website/assets/images/app-icon-google-drive-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fde88db09bf53e34556c6b8113a30925e00be8ef GIT binary patch literal 8613 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 literal 0 HcmV?d00001