class Chef::Provider::Package::Msu

def candidate_version

def candidate_version
  @candidate_version ||= get_candidate_versions
end

def cleanup_after_converge

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

def default_download_cache_path

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 download_source_file

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 extract_msu_contents(msu_file, destination)

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

def get_cab_package(cab_file)

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 get_candidate_versions

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

def get_current_versions

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

def install_package(name, version)

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 load_current_resource

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 read_cab_files_from_xml(msu_dir)

Reading cab files from xml to ensure the order of installation in case of multiple cab files
msu package can contain 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 remove_package(name, version)

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 source_resource

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