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)
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