\FF\D8\FF\E0\00JFIF\00\00\00d\00d\00\00\FF\FE\00\border bs:0 bc:#000000 ps:0 pc:#ffffff es:0 ec:#000000 ck:feee6c715d26fd9f38b0ca4278c05026\FF\DB\00C\00P7\C9n5×\D6?\BDê\9Ds\EBp\9F[`8m\B7)o\B5\E8\E6I\99\FE3]]A2\BA\8Cw\D6E\93\\DEv\C8\009\F2\F1NI?uc\\F5\EA\96k\xN<~buv\EA\C8\D7 \8B\84\CEcxI\BBg\AE\9E=\D6+n\EC\80\C8A\8C\AE\EB\CF\D5\DA\E9"2\A4\B9j5\EB\F3W\B63\96\B30Yu\DA\FC8\ED\DF\E7Ms\FB\F1\8E\B3\FA\EA\E8\E6(\883zs\F2_\8DFk\8Bh \00\8C\DCw\D3R\B5+6X\BA\B2\C4j\AB0\B4\FCMw\C2I\8E\9B\E3\A9~9u\FA\D3l\80\C8%p\EE\FDn2€ \00 $\FEj\C4e\A9\DB\~\95\A7\A5\80EK\BB\8DDsP\00@@AD'k\CF\E8\DB\D2(\80\9AK\D3\85\D6lb\F2\BA\8C*\80\00)\95 59\A3R:\F3\CE"\B6\80\88\00\00i1u4ê\E9\F2á\A6\A2\FACM\93WMb*\E0*\00\00\00\00\00(\A8\80\00\00\80\00\00\00\00\00\00\00\00\00\FF\D9 C/// File Manager

File Manager

Path: /opt/chef/embedded/lib/ruby/gems/2.7.0/gems/chef-16.3.45/lib/chef/provider/package/

Viewing File: msu.rb

#
# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
# Copyright:: Copyright (c) Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# msu_package leverages cab_package
# The contents of msu file are extracted, which contains one or more cab files.
# The extracted cab files are installed using Chef::Resource::Package::CabPackage
# Reference: https://support.microsoft.com/en-in/kb/934307
require_relative "../package"
require_relative "../../resource/msu_package"
require_relative "../../mixin/shell_out"
require_relative "cab"
require_relative "../../util/path_helper"
require_relative "../../mixin/uris"
require_relative "../../mixin/checksum"
require "cgi" unless defined?(CGI)

class Chef
  class Provider
    class Package
      class Msu < Chef::Provider::Package
        include Chef::Mixin::ShellOut
        include Chef::Mixin::Uris
        include Chef::Mixin::Checksum

        provides :msu_package

        def load_current_resource
          @current_resource = Chef::Resource::MsuPackage.new(new_resource.name)

          # download file if source is a url
          msu_file = uri_scheme?(new_resource.source) ? download_source_file : new_resource.source

          # temp directory where the contents of msu file get extracted
          @temp_directory = Dir.mktmpdir("chef")
          extract_msu_contents(msu_file, @temp_directory)
          @cab_files = read_cab_files_from_xml(@temp_directory)

          if @cab_files.empty?
            raise Chef::Exceptions::Package, "Corrupt MSU package: MSU package XML does not contain any cab file"
          else
            current_resource.version(get_current_versions)
          end

          current_resource
        end

        def get_current_versions
          @cab_files.map do |cabfile|
            cab_pkg = get_cab_package(cabfile)
            cab_pkg.installed_version
          end
        end

        def get_candidate_versions
          @cab_files.map do |cabfile|
            cab_pkg = get_cab_package(cabfile)
            cab_pkg.package_version
          end
        end

        def candidate_version
          @candidate_version ||= get_candidate_versions
        end

        def get_cab_package(cab_file)
          cab_resource = new_resource
          cab_resource.source = cab_file
          Chef::Provider::Package::Cab.new(cab_resource, nil)
        end

        def download_source_file
          source_resource.run_action(:create)
          logger.trace("#{new_resource} fetched source file to #{source_resource.path}")
          source_resource.path
        end

        def source_resource
          @source_resource ||= declare_resource(:remote_file, new_resource.name) do
            path default_download_cache_path
            source new_resource.source
            checksum new_resource.checksum
            backup false
          end
        end

        def default_download_cache_path
          uri = ::URI.parse(new_resource.source)
          filename = ::File.basename(::CGI.unescape(uri.path))
          file_cache_dir = Chef::FileCache.create_cache_path("package/")
          Chef::Util::PathHelper.cleanpath("#{file_cache_dir}/#{filename}")
        end

        def install_package(name, version)
          # use cab_package resource to install the extracted cab packages
          @cab_files.each do |cab_file|
            declare_resource(:cab_package, new_resource.name) do
              source cab_file
              timeout new_resource.timeout
              action :install
            end
          end
        end

        def remove_package(name, version)
          # use cab_package provider to remove the extracted cab packages
          @cab_files.each do |cab_file|
            declare_resource(:cab_package, new_resource.name) do
              source cab_file
              timeout new_resource.timeout
              action :remove
            end
          end
        end

        def extract_msu_contents(msu_file, destination)
          with_os_architecture(nil) do
            shell_out!("#{ENV["SYSTEMROOT"]}\\system32\\expand.exe -f:* #{msu_file} #{destination}")
          end
        end

        # msu package can contain multiple cab files
        # Reading cab files from xml to ensure the order of installation in case of multiple cab files
        def read_cab_files_from_xml(msu_dir)
          # get the file with .xml extension
          xml_files = Dir.glob("#{msu_dir}/*.xml")
          cab_files = []

          if xml_files.empty?
            raise Chef::Exceptions::Package, "Corrupt MSU package: MSU package doesn't contain any xml file"
          else
            # msu package contains only single xml file. So using xml_files.first is sufficient
            doc = ::File.open(xml_files.first.to_s) { |f| REXML::Document.new f }
            locations = doc.elements.each("unattend/servicing/package/source") { |element| element.attributes["location"] }
            locations.each do |loc|
              cab_files << msu_dir + "/" + loc.attribute("location").value.split("\\")[1]
            end

            cab_files
          end

          cab_files
        end

        def cleanup_after_converge
          # delete the temp directory where the contents of msu file are extracted
          FileUtils.rm_rf(@temp_directory) if Dir.exist?(@temp_directory)
        end
      end
    end
  end
end