Class: Homebrew::Bundle::Winget Private

Inherits:
Extension show all
Defined in:
bundle/extensions/winget.rb

Overview

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

Support dumping and installing Windows packages through WinGet from WSL.

Defined Under Namespace

Classes: App, self

Constant Summary collapse

DEFAULT_SOURCE =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

"winget"
SOURCES =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let([DEFAULT_SOURCE, "msstore"].freeze, T::Array[String])
ELEVATED_INSTALL_FAILURE_PATTERNS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let([
  /Installer failed with exit code:\s*1603/i,
  /\b(?:admin|administrator|elevat|UAC)\b/i,
].freeze, T::Array[Regexp])
INSTALLER_UI_FAILURE_PATTERNS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let([
  /\b(?:interactive|user input|user cancelled)\b/i,
].freeze, T::Array[Regexp])
INTERNAL_PACKAGE_PATTERNS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

T.let([
  /\AApp Installer\z/i,
  /\A9NBLGGH4NNS1\z/i,
  /\AMicrosoft Store\z/i,
  /\AStore Experience Host\z/i,
  /\AWindows (?:Feature|Web) Experience Pack\z/i,
  /\AMicrosoft Edge WebView2 Runtime\z/i,
  /\AMicrosoft Visual C\+\+/i,
  /\AWindows App Runtime/i,
  /\AMicrosoft\.(?:AppInstaller|DesktopAppInstaller|DirectX|DotNet|Edge|EdgeWebView2Runtime|GameInput)\b/i,
  /\AMicrosoft\.(?:HEVCVideoExtension|NET\.Native|RawImageExtension)\b/i,
  /\AMicrosoft\.(?:OneDrive|WSL)\z/i,
  /\AMicrosoft\.(?:Services\.Store\.Engagement|StorePurchaseApp|UI\.Xaml|VCLibs|WindowsAppRuntime)\b/i,
  /\AMicrosoft\.(?:WindowsStore|WebMediaExtensions|WebpImageExtension|VP9VideoExtensions)\b/i,
  /\AMicrosoft\.VCRedist\./i,
  /\ANvidia\.PhysX\z/i,
].freeze, T::Array[Regexp])

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Extension

check, cleanup!, cleanup_disable_description, cleanup_disable_env, cleanup_supported?, disable_predicate_method, dump, dump_disable_description, dump_disable_env, dump_disable_predicate_method, dump_disable_supported?, dump_output, dump_supported?, dump_with, ensure_package_manager_installed!, #failure_reason, flag, inherited, install_package!, install_supported?, install_verb, legacy_check_step, package_description, package_installed?, package_manager_env, package_manager_executable!, package_manager_installed?, package_manager_name, package_record, predicate_method, quote, remove_supported?, with_package_manager_env

Methods included from Utils::Output::Mixin

#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_once, #opoo_outside_github_actions, #opoo_without_github_actions_annotation, #pretty_cannot_install, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_uninstalled, #pretty_unmarked, #pretty_upgradable, #pretty_warning

Methods inherited from PackageType

batch_installable?, check, #checkable_entries, dump, dump_output, dump_supported?, #exit_early_check, #failure_reason, #find_actionable, #full_check, inherited, install_batch!, install_supported?, install_verb

Class Method Details

.absolute_windows_path?(path) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


248
249
250
251
252
253
254
255
256
# File 'bundle/extensions/winget.rb', line 248

def absolute_windows_path?(path)
  path_with_forward_slashes = path.tr("\\", "/")
  return true if path_with_forward_slashes.match?(%r{\A[A-Za-z]:/})

  # Check the original prefix so Linux paths beginning with // still require conversion.
  return false unless path.start_with?("\\\\")

  path_with_forward_slashes.match?(%r{\A//[^/]+/[^/]+})
end

.add_supported? ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


66
67
68
# File 'bundle/extensions/winget.rb', line 66

def add_supported?
  false
end

.app_installed?(id, source:) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


411
412
413
# File 'bundle/extensions/winget.rb', line 411

def app_installed?(id, source:)
  installed_app_records.any? { |app_id, app_source| app_id.casecmp?(id) && app_source == source }
end

.apps ⇒ Array<App>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'bundle/extensions/winget.rb', line 141

def apps
  apps = @apps
  return apps if apps

  @apps = if (winget = package_manager_executable)
    SOURCES.flat_map do |source|
      export_apps(winget, source:)
    end
  end
  return [] if @apps.nil?

  @apps
end

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



58
# File 'bundle/extensions/winget.rb', line 58

def banner_name = "WinGet packages"

.check_label ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



55
# File 'bundle/extensions/winget.rb', line 55

def check_label = "WinGet Package"

.cleanup_heading ⇒ String?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



71
72
73
# File 'bundle/extensions/winget.rb', line 71

def cleanup_heading
  banner_name
end

.cleanup_item(app) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



365
366
367
# File 'bundle/extensions/winget.rb', line 365

def cleanup_item(app)
  JSON.generate("id" => app.id, "name" => app.name, "source" => app.source)
end

.cleanup_item_name(item) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



370
371
372
373
374
375
376
377
378
# File 'bundle/extensions/winget.rb', line 370

def cleanup_item_name(item)
  app = parse_cleanup_item(item)
  return app.id if app.name == app.id && app.source == DEFAULT_SOURCE
  return "#{app.id} (#{app.source})" if app.name == app.id

  return "#{app.name} (#{app.id})" if app.source == DEFAULT_SOURCE

  "#{app.name} (#{app.id}, #{app.source})"
end

.cleanup_items(entries) ⇒ Array<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'bundle/extensions/winget.rb', line 381

def cleanup_items(entries)
  kept_apps = entries.filter_map do |entry|
    next if entry.type != type

    [entry.options.fetch(:id, entry.name).to_s, entry.options.fetch(:source, DEFAULT_SOURCE).to_s]
  end
  return [].freeze if kept_apps.empty?

  winget = package_manager_executable
  return [].freeze if winget.nil?

  cleanup_packages = SOURCES.flat_map { |source| exported_apps(winget, source:) }
                            .reject { |app| internal_package?(app) }
                            .sort_by do |app|
                              [SOURCES.index(app.source) || SOURCES.length, app.name.downcase]
                            end
  packages_to_cleanup = cleanup_packages.reject do |app|
    kept_apps.any? { |id, source| app.id.casecmp?(id) && app.source == source }
  end
  packages_to_cleanup.map { |app| cleanup_item(app) }
end

.dump_entry(package) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



355
356
357
358
359
360
361
362
# File 'bundle/extensions/winget.rb', line 355

def dump_entry(package)
  app = T.cast(package, App)
  line = "winget #{quote(app.name)}"
  line += ", id: #{quote(app.id)}" if app.id != app.name
  return line if app.source == DEFAULT_SOURCE

  "#{line}, source: #{quote(app.source)}"
end

.dump_name(package) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



350
351
352
# File 'bundle/extensions/winget.rb', line 350

def dump_name(package)
  T.cast(package, App).name
end

.elevation_failure?(output) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


550
551
552
# File 'bundle/extensions/winget.rb', line 550

def elevation_failure?(output)
  ELEVATED_INSTALL_FAILURE_PATTERNS.any? { |pattern| output.match?(pattern) }
end

.entry(name, options = {}) ⇒ Dsl::Entry

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'bundle/extensions/winget.rb', line 76

def entry(name, options = {})
  unknown_options = options.keys - [:id, :source]
  raise "unknown options(#{unknown_options.inspect}) for winget" if unknown_options.present?

  id = options.fetch(:id, name)
  raise "options[:id](#{id.inspect}) should be a String object" unless id.is_a?(String)

  source = options.fetch(:source, DEFAULT_SOURCE)
  raise "options[:source](#{source.inspect}) should be a String object" unless source.is_a?(String)
  unless SOURCES.include?(source)
    raise "options[:source](#{source.inspect}) should be one of #{SOURCES.inspect}"
  end

  Dsl::Entry.new(type, name, id:, source:)
end

.export_apps(winget, source:) ⇒ Array<App>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



156
157
158
159
160
161
162
163
164
# File 'bundle/extensions/winget.rb', line 156

def export_apps(winget, source:)
  exported = exported_apps(winget, source:)
  return [] if exported.empty?

  names = listed_app_names(winget, source:)
  exported.map do |app|
    App.new(id: app.id, name: names.fetch(app.id.downcase, app.name), source: app.source)
  end
end

.exported_apps(winget, source:) ⇒ Array<App>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'bundle/extensions/winget.rb', line 167

def exported_apps(winget, source:)
  Tempfile.create(["brew-bundle-winget", ".json"]) do |file|
    output_path = inventory_windows_path(file.path)
    powershell, script = prepare_inventory_command(
      winget, args: ["export", "--source", source, "--output", output_path,
                     "--accept-source-agreements", "--disable-interactivity"]
    )
    script += <<~POWERSHELL
      if ($nativeCode -eq #{NO_APPLICATIONS_FOUND}) {
        [System.IO.File]::WriteAllText(#{powershell_quote(output_path)}, '{"Sources":[]}',
          (New-Object System.Text.UTF8Encoding($false)))
        exit 0
      }
    POWERSHELL
    run_inventory_script(powershell, script)

    parse_export(File.read(file.path), source:)
  end
end

.install!(name, id: nil, with: nil, preinstall: true, no_upgrade: false, verbose: false, force: false, source: DEFAULT_SOURCE, **options) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • name (String)
  • id (String, nil) (defaults to: nil)
  • with (Array<String>, nil) (defaults to: nil)
  • preinstall (Boolean) (defaults to: true)
  • no_upgrade (Boolean) (defaults to: false)
  • verbose (Boolean) (defaults to: false)
  • force (Boolean) (defaults to: false)
  • source (String) (defaults to: DEFAULT_SOURCE)
  • options (Homebrew::Bundle::EntryOption)

Returns:

  • (Boolean)


459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'bundle/extensions/winget.rb', line 459

def install!(name, id: nil, with: nil, preinstall: true, no_upgrade: false, verbose: false, force: false,
             source: DEFAULT_SOURCE, **options)
  _ = with
  _ = no_upgrade
  _ = force
  _ = options

  return true unless preinstall

  id ||= name
  winget = package_manager_executable!
  args = ["install", "--id", id, "--exact", "--source", source,
          "--accept-source-agreements", "--accept-package-agreements",
          "--disable-interactivity"]
  success, output = run_install_command(winget, args, verbose:, elevated: false)
  if !success && elevation_failure?(output)
    puts "WinGet install for #{name} may require Windows UAC/elevation; retrying elevated."
    success, elevated_output = run_install_command(winget, args, verbose:, elevated: true)
    output = elevated_output.presence || output
  end
  unless success
    report_install_failure(name, id:, source:, output:)
    return false
  end

  unless apps.any? { |app| app.id.casecmp?(id) && app.source == source }
    apps << App.new(id:, name:, source:)
    @packages = nil
  end
  installed_app_records << [id, source] unless installed_app_records.any? do |app_id, app_source|
    app_id.casecmp?(id) && app_source == source
  end
  true
end

.installed_app_records ⇒ Array<Array<(String, String)>>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



342
343
344
345
346
347
# File 'bundle/extensions/winget.rb', line 342

def installed_app_records
  installed_app_records = @installed_app_records
  return installed_app_records if installed_app_records

  @installed_app_records = apps.map { |app| [app.id, app.source] }
end

.installed_packages ⇒ Array<App>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



330
331
332
# File 'bundle/extensions/winget.rb', line 330

def installed_packages
  apps
end

.installer_ui_failure?(output) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


555
556
557
# File 'bundle/extensions/winget.rb', line 555

def installer_ui_failure?(output)
  INSTALLER_UI_FAILURE_PATTERNS.any? { |pattern| output.match?(pattern) }
end

.internal_package?(app) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


335
336
337
338
339
# File 'bundle/extensions/winget.rb', line 335

def internal_package?(app)
  INTERNAL_PACKAGE_PATTERNS.any? do |pattern|
    pattern.match?(app.id) || pattern.match?(app.name)
  end
end

.inventory_windows_path(path) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



233
234
235
236
237
238
239
240
241
242
243
244
# File 'bundle/extensions/winget.rb', line 233

def inventory_windows_path(path)
  return path if absolute_windows_path?(path)
  raise "Invalid WinGet inventory path: #{path}" unless path.start_with?("/")

  wslpath = which("wslpath", ORIGINAL_PATHS)
  raise "WinGet inventory requires wslpath for Windows path conversion" if wslpath.nil?

  converted = command_output(wslpath, args: ["-w", path]).chomp
  raise "Invalid Windows path from wslpath" unless absolute_windows_path?(converted)

  converted
end

.listed_app_names(winget, source:) ⇒ Hash{String => String}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



188
189
190
191
192
193
194
195
# File 'bundle/extensions/winget.rb', line 188

def listed_app_names(winget, source:)
  powershell, script = prepare_inventory_command(
    winget, args: ["list", "--source", source, "--accept-source-agreements",
                   "--disable-interactivity", "--nowarn"]
  )

  parse_list_names(run_inventory_script(powershell, script))
end

.package_manager_executable ⇒ Pathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



93
94
95
96
97
# File 'bundle/extensions/winget.rb', line 93

def package_manager_executable
  return unless OS.wsl?

  which("winget.exe", ORIGINAL_PATHS) || windows_apps_executables.find(&:executable?)
end

.packages ⇒ Array<App>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



321
322
323
324
325
326
327
# File 'bundle/extensions/winget.rb', line 321

def packages
  packages = @packages
  return packages if packages

  @packages = apps.reject { |app| internal_package?(app) }
                  .sort_by { |app| [SOURCES.index(app.source) || SOURCES.length, app.name.downcase] }
end

.parse_cleanup_item(item) ⇒ App

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

Raises:

  • (TypeError)


577
578
579
580
581
582
583
584
585
586
587
588
589
# File 'bundle/extensions/winget.rb', line 577

def parse_cleanup_item(item)
  parsed = JSON.parse(item)
  raise TypeError, "Invalid WinGet cleanup item: #{item}" unless parsed.is_a?(Hash)

  id = parsed["id"]
  name = parsed["name"]
  source = parsed["source"]
  if !id.is_a?(String) || !name.is_a?(String) || !source.is_a?(String)
    raise TypeError, "Invalid WinGet cleanup item: #{item}"
  end

  App.new(id:, name:, source:)
end

.parse_export(output, source:) ⇒ Array<App>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'bundle/extensions/winget.rb', line 298

def parse_export(output, source:)
  export = JSON.parse(output)
  if !export.is_a?(Hash) || !export["Sources"].is_a?(Array)
    raise "Invalid WinGet export: expected Sources array"
  end

  export.fetch("Sources").flat_map do |source_export|
    if !source_export.is_a?(Hash) || !source_export["Packages"].is_a?(Array)
      raise "Invalid WinGet export: expected Packages array"
    end

    source_export.fetch("Packages").map do |package|
      id = package.is_a?(Hash) ? package["PackageIdentifier"] : nil
      raise "Invalid WinGet export: expected package identifier" if !id.is_a?(String) || id.blank?

      App.new(id:, name: id, source:)
    end
  end
rescue JSON::ParserError
  raise "Invalid WinGet export: expected JSON"
end

.parse_list_names(output) ⇒ Hash{String => String}

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'bundle/extensions/winget.rb', line 198

def parse_list_names(output)
  lines = output.encode("UTF-8", invalid: :replace, undef: :replace)
                .delete("\r")
                .lines(chomp: true)
  header_index = lines.index { |line| line.match?(/\bName\s+Id\s+Version\b/) }
  return {} if header_index.nil?

  header = lines[header_index]
  return {} if header.nil?

  header_start = header.index("Name")
  id_column = header.index("Id", header_start || 0)
  version_column = header.index("Version", header_start || 0)
  return {} if header_start.nil? || id_column.nil? || version_column.nil?

  lines.drop(header_index + 1).each_with_object({}) do |line, names|
    next if line.blank? || line[header_start..].to_s.match?(/\A-+\z/)

    name = line[header_start...id_column].to_s.strip
    id = line[id_column...version_column].to_s.strip
    names[id.downcase] = name if name.present? && id.present?
  end
end

.powershell_quote(value) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



544
545
546
547
# File 'bundle/extensions/winget.rb', line 544

def powershell_quote(value)
  # Escape smart quotes because PowerShell treats them as delimiters.
  "'#{value.gsub(/['\u2018-\u201B]/) { |quote| quote * 2 }}'"
end

.preinstall!(name, id: nil, with: nil, no_upgrade: false, verbose: false, source: DEFAULT_SOURCE, **options) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'bundle/extensions/winget.rb', line 426

def preinstall!(name, id: nil, with: nil, no_upgrade: false, verbose: false, source: DEFAULT_SOURCE,
                **options)
  _ = with
  _ = no_upgrade
  _ = options

  id ||= name

  unless package_manager_installed?
    raise "Unable to install #{name} WinGet package. winget.exe is not installed."
  end

  if app_installed?(id, source:)
    puts "Skipping install of #{name} WinGet package. It is already installed." if verbose
    return false
  end

  true
end

.prepare_inventory_command(winget, args:) ⇒ Array<(Pathname, String)>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'bundle/extensions/winget.rb', line 260

def prepare_inventory_command(winget, args:)
  powershell = which("powershell.exe", ORIGINAL_PATHS) ||
               Pathname.new("/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe")
  raise "WinGet inventory requires Windows PowerShell 5.1 (powershell.exe)" unless powershell.executable?

  script = <<~POWERSHELL
    trap { [Console]::Error.WriteLine($_); exit 1 }
    [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)

    $LASTEXITCODE = $null
    & #{powershell_quote(inventory_windows_path(winget.to_s))} #{args.map { |arg| powershell_quote(arg) }.join(" ")}
    $nativeStatus = $LASTEXITCODE

    if ($nativeStatus -isnot [int]) {
      [Console]::Error.WriteLine('WinGet did not return a valid native exit status')
      exit 1
    }

    $nativeCode = [long]$nativeStatus -band [uint32]::MaxValue
    if ($nativeCode -eq 0) { exit 0 }
  POWERSHELL

  [powershell, script]
end

.report_install_failure(name, id:, source:, output:) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# File 'bundle/extensions/winget.rb', line 560

def report_install_failure(name, id:, source:, output:)
  puts "WinGet failed to install #{name} (#{id}) from #{source}."
  if elevation_failure?(output)
    puts "The installer may require Windows UAC/elevation."
    puts "Try installing it from an elevated Windows Terminal:"
    puts "  winget install --id #{id} --exact --source #{source} --disable-interactivity"
  elsif installer_ui_failure?(output)
    puts "The installer appears to require installer UI or user input, which brew bundle does not automate."
    puts "Install it manually from Windows:"
    puts "  winget install --id #{id} --exact --source #{source}"
  else
    puts "Try installing it manually from Windows:"
    puts "  winget install --id #{id} --exact --source #{source}"
  end
end

.reset! ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



134
135
136
137
138
# File 'bundle/extensions/winget.rb', line 134

def reset!
  @apps = T.let(nil, T.nilable(T::Array[App]))
  @packages = T.let(nil, T.nilable(T::Array[App]))
  @installed_app_records = T.let(nil, T.nilable(T::Array[[String, String]]))
end

.run_elevated_install_command(winget, args, verbose:) ⇒ Array<(Boolean, String)>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
# File 'bundle/extensions/winget.rb', line 520

def run_elevated_install_command(winget, args, verbose:)
  powershell = which("powershell.exe", ORIGINAL_PATHS) ||
               Pathname.new("/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe")
  return [false, "powershell.exe is not available.\n"] unless powershell.executable?

  winget_path = winget.to_s.include?("/") ? windows_export_path(winget.to_s) : winget.to_s
  argument_list = args.map { |arg| powershell_quote(arg) }.join(", ")
  script = <<~POWERSHELL
    $startProcessArgs = @{
      FilePath = #{powershell_quote(winget_path)}
      ArgumentList = @(#{argument_list})
      Verb = 'RunAs'
      Wait = $true
      PassThru = $true
    }
    $process = Start-Process @startProcessArgs
    $process.WaitForExit()
    exit $process.ExitCode
  POWERSHELL

  [Bundle.system(powershell, "-NoProfile", "-Command", script, verbose:), ""]
end

.run_install_command(winget, args, verbose:, elevated:) ⇒ Array<(Boolean, String)>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'bundle/extensions/winget.rb', line 502

def run_install_command(winget, args, verbose:, elevated:)
  return run_elevated_install_command(winget, args, verbose:) if elevated

  logs = T.let([], T::Array[String])
  success = T.let(false, T::Boolean)
  IO.popen([winget.to_s, *args], err: [:child, :out]) do |pipe|
    while (line = pipe.gets)
      print line if verbose
      logs << line
    end
    Process.wait(pipe.pid)
    success = $CHILD_STATUS.success?
    pipe.close
  end
  [success, logs.join]
end

.run_inventory_script(powershell, script) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



287
288
289
290
291
292
293
294
# File 'bundle/extensions/winget.rb', line 287

def run_inventory_script(powershell, script)
  script += <<~POWERSHELL
    [Console]::Error.WriteLine(('WinGet failed with native exit code 0x{0:X8}' -f $nativeCode))
    exit 1
  POWERSHELL

  command_output(powershell, args: ["-NoProfile", "-NonInteractive", "-Command", script])
end

.switch_description(description) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



61
62
63
# File 'bundle/extensions/winget.rb', line 61

def switch_description(description)
  "#{super} Note: WSL only."
end

.type ⇒ Symbol

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



52
# File 'bundle/extensions/winget.rb', line 52

def type = :winget

.uninstall_package!(name, executable: Pathname.new("")) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


404
405
406
407
408
# File 'bundle/extensions/winget.rb', line 404

def uninstall_package!(name, executable: Pathname.new(""))
  app = parse_cleanup_item(name)
  Bundle.system(executable, "uninstall", "--id", app.id, "--exact", "--source", app.source,
                "--accept-source-agreements", "--disable-interactivity", verbose: false)
end

.windows_apps_executables ⇒ Array<Pathname>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



100
101
102
103
104
105
106
107
108
# File 'bundle/extensions/winget.rb', line 100

def windows_apps_executables
  [
    ENV.fetch("LOCALAPPDATA", nil)&.+("\\Microsoft\\WindowsApps\\winget.exe"),
    ENV.fetch("USERPROFILE", nil)&.+("\\AppData\\Local\\Microsoft\\WindowsApps\\winget.exe"),
    windows_local_appdata&.+("\\Microsoft\\WindowsApps\\winget.exe"),
  ].compact.uniq.filter_map do |path|
    windows_path_to_wsl_path(path) if path.exclude?("%")
  end
end

.windows_export_path(path) ⇒ String

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



223
224
225
226
227
228
229
230
# File 'bundle/extensions/winget.rb', line 223

def windows_export_path(path)
  wslpath = which("wslpath", ORIGINAL_PATHS)
  return path if wslpath.nil?

  Utils.safe_popen_read(wslpath, "-w", path, err: :close).chomp.presence || path
rescue ErrorDuringExecution
  path
end

.windows_local_appdata ⇒ String?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



111
112
113
114
115
116
# File 'bundle/extensions/winget.rb', line 111

def windows_local_appdata
  cmd = which("cmd.exe", ORIGINAL_PATHS) || Pathname.new("/mnt/c/Windows/System32/cmd.exe")
  return unless cmd.executable?

  Utils.popen_read_text(cmd, "/d", "/c", "echo", "%LOCALAPPDATA%", err: File::NULL).strip.presence
end

.windows_path_to_wsl_path(path) ⇒ Pathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'bundle/extensions/winget.rb', line 119

def windows_path_to_wsl_path(path)
  path = path.tr("\\", "/")
  return Pathname.new(path) if path.start_with?("/")

  match = path.match(%r{\A(?<drive>[A-Za-z]):/(?<relative_path>.+)\z})
  return if match.nil?

  drive = match[:drive]
  relative_path = match[:relative_path]
  return if drive.nil? || relative_path.nil?

  Pathname.new("/mnt/#{drive.downcase}/#{relative_path}")
end

Instance Method Details

#format_checkable(entries) ⇒ Array<Object>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:



593
594
595
596
597
598
# File 'bundle/extensions/winget.rb', line 593

def format_checkable(entries)
  checkable_entries(entries).map do |entry|
    App.new(id: T.cast(entry.options.fetch(:id), String), name: entry.name,
            source: T.cast(entry.options.fetch(:source), String))
  end
end

#installed_and_up_to_date?(package, no_upgrade: false) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

  • package (Object)
  • no_upgrade (Boolean) (defaults to: false)

Returns:

  • (Boolean)


601
602
603
604
605
606
# File 'bundle/extensions/winget.rb', line 601

def installed_and_up_to_date?(package, no_upgrade: false)
  _ = no_upgrade

  app = T.cast(package, App)
  self.class.app_installed?(app.id, source: app.source)
end