diff --git a/Gemfile b/Gemfile
index 7276ba257..3ad9307e0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,6 +20,7 @@ gem 'nori'
gem 'programr', :git => "http://github.com/robertjwhitney/programr.git"
gem 'process_helper'
gem 'ovirt-engine-sdk'
+gem 'sqlite3'
#development only gems go here
group :test, :development do
diff --git a/lib/helpers/constants.rb b/lib/helpers/constants.rb
index 150aeb487..9f07e3543 100644
--- a/lib/helpers/constants.rb
+++ b/lib/helpers/constants.rb
@@ -15,6 +15,7 @@
VULNERABILITY_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/vulnerability_metadata_schema.xsd"
SERVICE_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/service_metadata_schema.xsd"
UTILITY_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/utility_metadata_schema.xsd"
+FORENSICS_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/forensic_metadata_schema.xsd"
GENERATOR_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/generator_metadata_schema.xsd"
ENCODER_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/encoder_metadata_schema.xsd"
NETWORK_SCHEMA_FILE = "#{ROOT_DIR}/lib/schemas/network_metadata_schema.xsd"
@@ -29,6 +30,7 @@
VULNERABILITIES_DIR = "#{MODULES_DIR}vulnerabilities/"
SERVICES_DIR = "#{MODULES_DIR}services/"
UTILITIES_DIR = "#{MODULES_DIR}utilities/"
+FORENSICS_DIR = "#{MODULES_DIR}forensics/"
GENERATORS_DIR = "#{MODULES_DIR}generators/"
ENCODERS_DIR = "#{MODULES_DIR}encoders/"
NETWORKS_DIR = "#{MODULES_DIR}networks/"
@@ -42,10 +44,16 @@
# Path to resources
WORDLISTS_DIR = "#{ROOT_DIR}/lib/resources/wordlists"
IMAGES_DIR = "#{ROOT_DIR}/lib/resources/images"
+URLLISTS_DIR = "#{ROOT_DIR}/lib/resources/urllists"
+INTERNET_BROWSER_FILES_DIR = "#{ROOT_DIR}/lib/resources/internet_browser_files"
+ILLEGAL_IMAGES_DIR = "#{ROOT_DIR}/lib/resources/illegal_images"
+FORENSIC_ARTEFACTS_DIR = "#{ROOT_DIR}/lib/resources/forensic_artefacts"
# Path to secgen_functions puppet module
SECGEN_FUNCTIONS_PUPPET_DIR = "#{MODULES_DIR}build/puppet/secgen_functions"
+FILE_TRANSFER_STORAGE_MODULE_DIR = "#{ROOT_DIR}/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module"
+
## PACKER CONSTANTS ##
# Path to Packerfile.erb file
diff --git a/lib/output/xml_scenario_generator.rb b/lib/output/xml_scenario_generator.rb
index 3dbe034f4..8b1942b6e 100644
--- a/lib/output/xml_scenario_generator.rb
+++ b/lib/output/xml_scenario_generator.rb
@@ -70,6 +70,10 @@ def module_element(selected_module, xml)
xml.utility(selected_module.attributes_for_scenario_output) {
insert_inputs_and_values(selected_module,xml)
}
+ when 'forensic'
+ xml.forensic(selected_module.attributes_for_scenario_output) {
+ insert_inputs_and_values(selected_module,xml)
+ }
when 'network'
xml.network(selected_module.attributes_for_scenario_output)
else
diff --git a/lib/readers/module_reader.rb b/lib/readers/module_reader.rb
index 1415d80f1..dea52c61c 100644
--- a/lib/readers/module_reader.rb
+++ b/lib/readers/module_reader.rb
@@ -31,6 +31,11 @@ def self.read_utilities
return read_modules('utility', UTILITIES_DIR, UTILITY_SCHEMA_FILE, true)
end
+ # reads in all forensics
+ def self.read_forensics
+ return read_modules('forensic', FORENSICS_DIR, FORENSICS_SCHEMA_FILE, true)
+ end
+
# reads in all utilities
def self.read_generators
return read_modules('generator', GENERATORS_DIR, GENERATOR_SCHEMA_FILE, true)
@@ -147,7 +152,7 @@ def self.read_modules(module_type, modules_dir, schema_file, require_puppet)
# for each default input
doc.xpath("/#{module_type}/default_input").each do |inputs_doc|
- inputs_doc.xpath('descendant::vulnerability | descendant::service | descendant::utility | descendant::network | descendant::base | descendant::encoder | descendant::generator').each do |module_node|
+ inputs_doc.xpath('descendant::vulnerability | descendant::service | descendant::utility | descendant::forensic | descendant::network | descendant::base | descendant::encoder | descendant::generator').each do |module_node|
# create a selector module, which is a regular module instance used as a placeholder for matching requirements
module_selector = Module.new(module_node.name)
diff --git a/lib/readers/system_reader.rb b/lib/readers/system_reader.rb
index dfc5c299d..d9471a63c 100644
--- a/lib/readers/system_reader.rb
+++ b/lib/readers/system_reader.rb
@@ -64,7 +64,7 @@ def self.read_scenario(scenario_file)
end
# for each module selection
- system_node.xpath('//vulnerability | //service | //utility | //build | //network | //base | //encoder | //generator').each do |module_node|
+ system_node.xpath('//vulnerability | //service | //utility | //forensic | //build | //network | //base | //encoder | //generator').each do |module_node|
# create a selector module, which is a regular module instance used as a placeholder for matching requirements
module_selector = Module.new(module_node.name)
diff --git a/lib/resources/forensic_artefacts/prefetch/CMD.EXE-087B4001.pf b/lib/resources/forensic_artefacts/prefetch/CMD.EXE-087B4001.pf
new file mode 100644
index 000000000..cda0ea11f
Binary files /dev/null and b/lib/resources/forensic_artefacts/prefetch/CMD.EXE-087B4001.pf differ
diff --git a/lib/resources/forensic_artefacts/prefetch/EXPLORER.EXE-082F38A9.pf b/lib/resources/forensic_artefacts/prefetch/EXPLORER.EXE-082F38A9.pf
new file mode 100644
index 000000000..fca9f598e
Binary files /dev/null and b/lib/resources/forensic_artefacts/prefetch/EXPLORER.EXE-082F38A9.pf differ
diff --git a/lib/resources/forensic_artefacts/prefetch/FIREFOX.EXE-28641590.pf b/lib/resources/forensic_artefacts/prefetch/FIREFOX.EXE-28641590.pf
new file mode 100644
index 000000000..1d9414f40
Binary files /dev/null and b/lib/resources/forensic_artefacts/prefetch/FIREFOX.EXE-28641590.pf differ
diff --git a/lib/resources/illegal_images/cats/cat1.jpg b/lib/resources/illegal_images/cats/cat1.jpg
new file mode 100644
index 000000000..921cce346
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat1.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat10.jpg b/lib/resources/illegal_images/cats/cat10.jpg
new file mode 100644
index 000000000..12d40c3a1
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat10.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat11.jpg b/lib/resources/illegal_images/cats/cat11.jpg
new file mode 100644
index 000000000..ce778c20e
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat11.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat12.jpg b/lib/resources/illegal_images/cats/cat12.jpg
new file mode 100644
index 000000000..ba8eca2df
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat12.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat13.jpg b/lib/resources/illegal_images/cats/cat13.jpg
new file mode 100644
index 000000000..9175aa549
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat13.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat14.jpg b/lib/resources/illegal_images/cats/cat14.jpg
new file mode 100644
index 000000000..de195b82a
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat14.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat2.jpg b/lib/resources/illegal_images/cats/cat2.jpg
new file mode 100644
index 000000000..17b22c16a
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat2.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat3.jpg b/lib/resources/illegal_images/cats/cat3.jpg
new file mode 100644
index 000000000..304ae8c95
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat3.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat4.jpg b/lib/resources/illegal_images/cats/cat4.jpg
new file mode 100644
index 000000000..c6b5dfdd2
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat4.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat5.jpg b/lib/resources/illegal_images/cats/cat5.jpg
new file mode 100644
index 000000000..7ee5d83f8
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat5.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat7.jpg b/lib/resources/illegal_images/cats/cat7.jpg
new file mode 100644
index 000000000..8c4e63baf
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat7.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat8.jpg b/lib/resources/illegal_images/cats/cat8.jpg
new file mode 100644
index 000000000..836e92e13
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat8.jpg differ
diff --git a/lib/resources/illegal_images/cats/cat9.jpg b/lib/resources/illegal_images/cats/cat9.jpg
new file mode 100644
index 000000000..766a029b8
Binary files /dev/null and b/lib/resources/illegal_images/cats/cat9.jpg differ
diff --git a/lib/resources/internet_browser_files/chrome_history_file.source b/lib/resources/internet_browser_files/chrome_history_file.source
new file mode 100644
index 000000000..85df9987b
Binary files /dev/null and b/lib/resources/internet_browser_files/chrome_history_file.source differ
diff --git a/lib/resources/urllists/cybercrime_urls b/lib/resources/urllists/cybercrime_urls
new file mode 100644
index 000000000..8fad991ad
--- /dev/null
+++ b/lib/resources/urllists/cybercrime_urls
@@ -0,0 +1,129 @@
+https://www.exploit-db.com/
+https://www.exploit-db.com/search/
+https://www.exploit-db.com/browse/
+https://www.exploit-db.com/webapps/
+https://www.exploit-db.com/remote/
+https://www.exploit-db.com/google-hacking-database/
+https://www.exploit-db.com/local/
+https://www.exploit-db.com/papers/
+https://github.com/offensive-security/exploit-database
+https://www.offensive-security.com/community-projects/the-exploit-database/
+https://www.exploit-db.com/shellcode/
+https://www.exploit-db.com/exploits/40360/
+https://www.exploit-db.com/about/
+https://www.exploit-db.com/submit/
+https://www.exploit-db.com/exploit-database-statistics/
+https://www.rapid7.com/db
+https://www.rapid7.com/db/
+https://www.rapid7.com/db/modules/
+https://twitter.com/exploitdb%3Flang%3Den
+https://null-byte.wonderhowto.com/how-to/hack-like-pro-find-exploits-using-exploit-database-kali-0156399/
+https://www.exploit-db.com/searchsploit/
+https://www.exploit-db.com/exploits/39640/
+https://www.exploit-db.com/docs/39527.pdf
+https://www.exploit-db.com/dos/
+https://www.exploit-db.com/exploits/39821/
+https://www.exploit-db.com/exploits/40839/
+https://www.exploit-db.com/exploits/39777/
+https://www.exploit-db.com/exploits/41570/
+https://www.exploit-db.com/platform/%3Fp%3Dlinux
+https://0day.today/
+https://www.exploit-db.com/google-hacking-database/
+https://github.com/offensive-security/exploit-database
+https://www.offensive-security.com/community-projects/the-exploit-database/
+https://www.rapid7.com/db
+https://www.rapid7.com/db/
+https://www.exploit-db.com/webapps/
+https://www.exploit-db.com/local/
+https://www.exploit-db.com/papers/
+https://www.exploit-db.com/searchsploit/
+https://www.exploit-db.com/exploit-database-statistics/
+https://www.exploit-db.com/submit/
+https://null-byte.wonderhowto.com/how-to/hack-like-pro-find-exploits-using-exploit-database-kali-0156399/
+https://cxsecurity.com/exploit/
+https://www.rapid7.com/db/modules/
+https://0day.today/
+https://www.exploit-db.com/shellcode/
+https://www.exploit-db.com/dos/
+https://www.exploit-db.com/exploits/40889/
+https://www.exploit-db.com/platform/%3Fp%3Dlinux
+https://www.offensive-security.com/offsec/exploit-database-update/
+https://twitter.com/exploitdb%3Flang%3Den
+https://www.rapid7.com/db/search
+https://www.rapid7.com/db/modules%3Fpage%3D3
+https://www.rapid7.com/db/modules%3Fpage%3D6
+https://github.com/offensive-security/exploit-database-bin-sploits
+https://www.kali.org/
+https://www.kali.org/downloads/
+https://www.kali.org/about-us/
+https://en.wikipedia.org/wiki/Kali_Linux
+http://docs.kali.org/
+http://docs.kali.org/introduction/what-is-kali-linux
+https://www.offensive-security.com/kali-linux-vmware-virtualbox-image-download/
+http://lifehacker.com/behind-the-app-the-story-of-kali-linux-1666168491
+https://twitter.com/kalilinux%3Flang%3Den
+http://www.infoworld.com/article/3060814/linux/should-beginners-install-kali-linux-on-their-computers.html
+https://www.kali.org/news/kali-linux-rolling-edition-2016-1/
+https://www.kali.org/kali-linux-documentation/
+https://www.kali.org/releases/kali-linux-20-released/
+https://www.kali.org/kali-linux-features/
+https://www.kali.org/penetration-testing-with-kali-linux/
+https://www.kali.org/kali-linux-releases/
+https://distrowatch.com/kali
+http://docs.kali.org/kali-on-arm/install-kali-linux-arm-raspberry-pi
+http://docs.kali.org/introduction/download-official-kali-linux-images
+http://docs.kali.org/development/live-build-a-custom-kali-iso
+https://www.youtube.com/user/kalinuxx
+https://www.kali.org/kali-linux-dojo-workshop/
+https://www.kali.org/news/introducing-kali-linux-certified-professional/
+https://www.kali.org/news/kali-linux-20162-release/
+https://www.offensive-security.com/kali-linux-arm-images/
+http://docs.kali.org/category/introduction
+http://docs.kali.org/downloading/kali-linux-live-usb-install
+http://tools.kali.org/
+https://hub.docker.com/r/kalilinux/kali-linux-docker/
+http://www.kalitutorials.net/
+https://null-byte.wonderhowto.com/how-to/hack-like-pro-getting-started-with-backtrack-your-new-hacking-system-0146889/
+https://null-byte.wonderhowto.com/how-to/hack-like-pro-install-backtrack-5-with-metasploit-as-dual-boot-hacking-system-0146681/
+http://www.backtrack-linux.org/
+http://www.admin-magazine.com/Articles/BackTrack-Linux-The-Ultimate-Hacker-s-Arsenal
+https://www.udemy.com/learn-hacking-using-backtrack-5/
+http://realhackerspoint.blogspot.com/2013/02/backtrack-5-ethical-hacking-tutorial.html
+https://www.kali.org/
+http://www.google.com/search?q=backtrack+hacking&num=30&sa=N&prmd=ivns&tbm=isch&tbo=u&source=univ&ved=0ahUKEwjc25Czw6HTAhVS1WMKHQFHCU0QsAQIVw
+http://www.hackingarticles.in/backtrack-commands-beginners/
+https://www.quora.com/How-do-I-hack-WPA2-Wi-Fi-password-using-Backtrack
+https://www.wirelessdomination.com/how-to-crack-wpa2-wifi-password-using-reaver-wpa2/
+https://www.amazon.com/Hacking-Backtrack-Linux-Revision-Guide/dp/B0074B7QSC
+https://www.lifewire.com/backtrack-the-hackers-swiss-army-knife-2487287
+http://www.hacking-tutorial.com/hacking-tutorial/15-step-to-hacking-windows-using-social-engineering-toolkit-and-backtrack-5/
+https://null-byte.wonderhowto.com/how-to/hack-like-pro-spear-phish-with-social-engineering-toolkit-set-backtrack-0148571/
+http://www.wirelesshack.org/backtrack-5-download
+http://hackyshacky.com/blog/hack-facebook-accounts-backtrack-5/%3Fm%3D0
+https://www.fullversionforever.com/wifi-hacking-with-backtrack/
+http://macdrug.com/wifi-cracker-how-to-crack-wifi-password-wpawpa2-using-backtrack-5/
+http://ways2hack.com/how-to-crack-wpa2-wifi-password/
+https://exploitpack.com/download.html
+https://exploitpack.com/
+https://github.com/offensive-security/exploit-database
+https://sourceforge.net/directory/%3Fq%3Dexploit
+https://www.metasploit.com/
+https://www.malwarebytes.com/antiexploit/
+https://packetstormsecurity.com/files/tags/exploit
+https://exploit-exercises.com/download
+https://github.com/ratty3697/HackSpy-Trojan-Exploit
+https://packetstormsecurity.com/files/tags/exploit/
+http://thehackernews.com/2011/05/blackhole-exploit-kit-download.html
+https://sourceforge.net/projects/exploittools/
+https://wearedevs.net/releases.html
+https://malwarebytes-anti-exploit.en.softonic.com/
+https://www.owasp.org/index.php/OWASP_Xenotix_XSS_Exploit_Framework
+https://github.com/rfunix/Pompem
+https://github.com/Gioyik/getExploit
+https://sourceforge.net/projects/xcodescanner/
+https://malwarebytes-anti-exploit.en.softonic.com/download
+https://www.symantec.com/security_response/attacksignatures/detail.jsp%3Fasid%3D28426
+https://www.symantec.com/security_response/attacksignatures/detail.jsp%3Fasid%3D26441
+https://www.bleepingcomputer.com/download/malwarebytes-anti-exploit/
+https://www.exploit-db.com/papers/
+https://medium.com/%40msuiche/shadow-brokers-nsa-exploits-of-the-week-3f7e17bdc216
\ No newline at end of file
diff --git a/lib/resources/urllists/generic_urls b/lib/resources/urllists/generic_urls
new file mode 100644
index 000000000..35a1660c8
--- /dev/null
+++ b/lib/resources/urllists/generic_urls
@@ -0,0 +1,643 @@
+http://www.independent.co.uk/voices/aleppo-crisis-syrian-war-bashar-al-assad-isis-more-propaganda-than-news-a7479901.html
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=14&ved=0ahUKEwj9ndTE0MHSAhUMmBoKHQrYB-4Q1ScIbjAN&url=http%3A%2F%2Fwww.independent.co.uk%2Fvoices%2Faleppo-crisis-syrian-war-bashar-al-assad-isis-more-propaganda-than-news-a7479901.html&usg=AFQjCNG6lQ4cMWcGWZ_m4rVrNbYp-mwoIg&bvm=bv.148747831,d.d2s&cad=rja
+http://www.bbc.co.uk/news/uk-politics-32810887
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=13&ved=0ahUKEwj9ndTE0MHSAhUMmBoKHQrYB-4Q1ScIbDAM&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fuk-politics-32810887&usg=AFQjCNEqQpbty5PU_3C4iiZw6AOoZP5rcQ&bvm=bv.148747831,d.d2s&cad=rja
+https://www.theguardian.com/us-news/ng-interactive/2017/jan/20/donald-trump-first-100-days-president-daily-updates
+http://www.express.co.uk/news
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=11&ved=0ahUKEwj9ndTE0MHSAhUMmBoKHQrYB-4QFghjMAo&url=http%3A%2F%2Fwww.express.co.uk%2Fnews&usg=AFQjCNGiC2MV8fS-ERjk3lrYasD0xIDeWA&bvm=bv.148747831,d.d2s&cad=rja
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=12&ved=0ahUKEwj9ndTE0MHSAhUMmBoKHQrYB-4Q1ScIajAL&url=https%3A%2F%2Fwww.theguardian.com%2Fus-news%2Fng-interactive%2F2017%2Fjan%2F20%2Fdonald-trump-first-100-days-president-daily-updates&usg=AFQjCNHand24H2PO66b0OFbQ-6ZYCOyqRA&bvm=bv.148747831,d.d2s&cad=rja
+http://www.dailymail.co.uk/news/index.html
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=10&ved=0ahUKEwj9ndTE0MHSAhUMmBoKHQrYB-4QFghdMAk&url=http%3A%2F%2Fwww.dailymail.co.uk%2Fnews%2Findex.html&usg=AFQjCNGeWFCOn7XJalTOjKGdVTeIQK_T8w&bvm=bv.148747831,d.d2s&cad=rja
+https://www.google.co.uk/search?q=bbc+news&ie=utf-8&oe=utf-8&client=firefox-b-ab&gfe_rd=cr&ei=VDS9WKrJO9DCaOf6r5gI#q=news&*
+http://www.bbc.co.uk/news/business-39175740
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=8&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQqUMIPzAH&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fbusiness-39175740&usg=AFQjCNHTVX1eg93T6kfEoVO5WtqnB9ERGg&cad=rja
+http://www.bbc.co.uk/news/uk-39176538
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=7&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQqUMIOzAG&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fuk-39176538&usg=AFQjCNF1Lsgw73VzGWkAC9a-4t9fOHoB2A&cad=rja
+http://www.bbc.co.uk/news/uk-39176110
+http://www.bbc.co.uk/news/world
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=6&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQqUMINzAF&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fuk-39176110&usg=AFQjCNH75QSNyOTErnsehKkHGwN5WxqgkA&cad=rja
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=3&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQjBAILzAC&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fworld&usg=AFQjCNF5X_xXbi4RdS-n3YXetp2xyKOgZQ&cad=rja
+http://www.bbc.co.uk/news/business
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=5&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQjBAIMTAE&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fbusiness&usg=AFQjCNGbcH7S94Wc1xAlQpYi2mWNP2gLhg&cad=rja
+http://www.bbc.co.uk/news/world/us_and_canada
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQjBAILTAD&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fworld%2Fus_and_canada&usg=AFQjCNHfYWm_9x_ukbMz4b1vRFgm-PD5Pw&cad=rja
+http://www.bbc.co.uk/news/uk
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQjBAIKzAB&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews%2Fuk&usg=AFQjCNHuNSSR7R0oMF6unGocwTpv-iLAgQ&cad=rja
+http://www.bbc.co.uk/news
+https://www.google.co.uk/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0ahUKEwjL95e-0MHSAhUJORoKHXXbCWcQFggjMAA&url=http%3A%2F%2Fwww.bbc.co.uk%2Fnews&usg=AFQjCNEonTvvrrbEptl1n_9nug5piXqeOQ&cad=rja
+https://www.google.co.uk/search?q=bbc+news&ie=utf-8&oe=utf-8&client=firefox-b-ab&gfe_rd=cr&ei=VDS9WKrJO9DCaOf6r5gI
+https://www.google.co.uk/?gws_rd=ssl
+https://www.mozilla.org/en-US/firefox/51.0.1/firstrun/
+https://news.google.com/
+http://www.foxnews.com/
+http://www.cnn.com/
+http://www.bbc.com/news
+https://www.msn.com/en-us/news
+http://www.nbcnews.com/
+http://abcnews.go.com/
+http://www.nydailynews.com/
+http://www.salon.com/category/news/
+http://www.cbsnews.com/
+https://www.yahoo.com/news/
+https://www.rt.com/news/
+http://www.npr.org/sections/news/
+http://www.bbc.com/news/world
+http://www.bbc.com/news/world-us-canada-39587855
+http://www.foxnews.com/us/2017/04/12/fast-and-furious-scandal-suspected-triggerman-in-border-agents-murder-arrested.html
+https://www.cnet.com/news/
+https://www.usatoday.com/news/
+https://weather.com/news
+http://www.nbcnews.com/news
+http://www.cnn.com/cnn10
+https://www.youtube.com/channel/UCYfdidRxbB8Qhf0Nx7ioOYw
+https://www.yahoo.com/news
+http://www.bing.com/news
+http://www.eonline.com/news
+https://www.apple.com/news/
+http://www.bbc.com/news/world-europe-39585071
+https://www.buzzfeed.com/news
+http://theartnewspaper.com/news/
+https://weather.com/
+https://weather.com/weather/tenday/l/Los%2BAngeles%2BCA%2BUSCA0638:1:US
+https://weather.com/weather/today/l/USCA0638:1:US
+https://weather.com/weather/tenday/l/USCA0638:1:US
+https://weather.com/weather/hourbyhour/l/USCA0638:1:US
+https://weather.com/weather/today/l/Los%2BAngeles%2BCA%2BUSCA0638:1:US
+http://www.accuweather.com/en/us/los-angeles-ca/90012/weather-forecast/347625
+https://www.wunderground.com/us/ca/los-angeles
+https://www.wunderground.com/US/CA/
+https://www.theweathernetwork.com/us/weather/california/los-angeles
+http://www.accuweather.com/en/us/los-angeles-ca/90012/hourly-weather-forecast/347625
+http://www.accuweather.com/en/us/san-diego-ca/92101/weather-forecast/347628
+http://www.weather.gov/
+https://www.theweathernetwork.com/us
+https://weather.com/en-GB
+https://weather.com/weather/tenday/l/USCA0982:1:US
+https://weather.com/weather/today/l/USCA0982:1:US
+https://www.wunderground.com/
+http://abc7.com/weather/
+https://www.accuweather.com/
+http://www.accuweather.com/en/us/las-vegas-nv/89101/weather-forecast/329506
+http://www.accuweather.com/en/us/new-orleans-la/70112/weather-forecast/348585
+https://play.google.com/store/apps/details%3Fid%3Dcom.weather.Weather%26hl%3Den
+https://weather.com/weather/today/l/USCA0987:1:US
+https://weather.com/weather/today/l/USTX0057:1:US
+http://w2.weather.gov/
+http://www.noaa.gov/weather
+https://www.msn.com/en-us/weather
+http://www.bbc.com/weather/
+https://www.wikipedia.org/
+https://en.wikipedia.org/
+https://en.wikipedia.org/wiki/Wikipedia
+https://en.wikipedia.org/wiki/Wikipedia:About
+https://en.wikipedia.org/wiki/Wiki
+https://en.m.wikipedia.org/
+https://play.google.com/store/apps/details%3Fid%3Dorg.wikipedia%26hl%3Den
+https://simple.wikipedia.org/
+https://www.facebook.com/wikipedia/
+https://itunes.apple.com/us/app/wikipedia/id324715238%3Fmt%3D8
+https://en.wikipedia.org/wiki/English_Wikipedia
+https://en.wikipedia.org/wiki/Portal:Mathematics
+https://en.wikipedia.org/wiki/Main_Page/
+https://en.wikipedia.org/wiki/Reliability_of_Wikipedia
+https://en.wikipedia.org/wiki/Wikipedia:Your_first_article
+https://en.wikipedia.org/wiki/Logan_(film)
+https://en.wikipedia.org/wiki/Wikipedia:Copyrights
+https://en.wikipedia.org/wiki/Wikipedia:Database_download
+https://en.wikipedia.org/wiki/Get_Out_(film)
+https://twitter.com/wikipedia
+https://en.wikipedia.org/wiki/Podcast
+https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style
+https://en.wikipedia.org/wiki/Shameless_(U.S._TV_series)
+https://en.wikipedia.org/wiki/Supergirl_(TV_series)
+https://en.wikipedia.org/wiki/Wikipedia:Unusual_articles
+https://en.wikipedia.org/wiki/Help:Editing
+https://en.wikipedia.org/wiki/The_Flash_(2014_TV_series)
+https://en.wikipedia.org/wiki/Blog
+https://en.wikipedia.org/wiki/United_Airlines
+https://en.wikipedia.org/wiki/Raccoon
+http://www.imdb.com/chart/tvmeter
+http://www.imdb.com/chart/toptv/
+http://www.imdb.com/search/title%3Ftitle_type%3Dtv_series
+http://www.tv.com/shows/
+https://www.pastemagazine.com/articles/2017/01/the-75-best-tv-shows-on-netflix-february-2017.html
+http://uproxx.com/tv/best-shows-on-netflix-good-tv-series-ranked/
+http://variety.com/gallery/tv-shows-2017-new-returning-watch/
+http://www.metacritic.com/feature/tv-renewal-scorecard-2016-2017-season
+http://www.rollingstone.com/tv/lists/100-greatest-tv-shows-of-all-time-w439520
+http://www.businessinsider.com/best-new-tv-shows-winter-spring-season-critics-reviews-2017-2
+http://www.rollingstone.com/tv/lists/25-most-anticipated-tv-shows-of-2017-w458725
+http://www.rottentomatoes.com/top-tv/
+http://www.metacritic.com/browse/tv/score/metascore/year/filtered
+http://www.metacritic.com/browse/tv/score/metascore/90day/filtered
+http://www.empireonline.com/movies/features/best-tv-shows-ever/
+https://www.hulu.com/tv
+http://www.businessinsider.com/when-shows-premiere-in-2017-1
+https://dvd.netflix.com/BrowseGenres/Television/2197
+http://www.imdb.com/search/title%3Fgenres%3Dcrime%26title_type%3Dtv_series%26sort%3Dmoviemeter,asc
+https://play.google.com/store/apps/details%3Fid%3Dfema.serietv2%26hl%3Den
+https://en.wikipedia.org/wiki/Television_program
+http://www.metacritic.com/browse/tv/release-date/new-series/date
+http://www.wga.org/writers-room/101-best-lists/101-best-written-tv-series/list
+https://theringer.com/the-tv-shows-you-need-to-watch-right-now-4c99e8c4a1f1
+http://giphy.com/search/tv-show
+http://www.fyi.tv/shows
+https://itunes.apple.com/us/genre/tv-shows/id32
+http://www.cnn.com/specials/tv/all-shows
+https://www.hoopladigital.com/browse/television/featured
+https://en.wikipedia.org/wiki/Film
+https://en.wikipedia.org/wiki/List_of_films_set_in_Los_Angeles
+https://www.filmindependent.org/
+http://www.slashfilm.com/
+https://www.theguardian.com/film/2017/apr/13/cannes-film-festival-2017-full-list-of-films
+https://www.theguardian.com/us/film
+http://www.lafilm.edu/
+http://www.afi.com/
+http://www.telegraph.co.uk/films/0/best-movies-2016/
+https://www.sundance.org/festivals/sundance-film-festival/program
+https://mubi.com/films
+http://www.imdb.com/
+http://www.telegraph.co.uk/films/
+http://www.filmsforaction.org/
+https://en.wikipedia.org/wiki/Film_industry
+http://www.films.com/
+https://www.timeout.com/london/film
+http://www.laweekly.com/movies
+http://www.independent.co.uk/arts-entertainment/films
+https://www.filmindependent.org/la-film-festival/submissions/
+http://www.google.com/search?q=films&num=30&sa=N&prmd=ivnsa&tbm=isch&tbo=u&source=univ&ved=0ahUKEwjZno3ewKHTAhUE7CYKHRhRDwwQsAQInAE
+https://www.intofilm.org/festival
+https://www.intofilm.org/
+http://www.film-foundation.org/
+https://www.sxsw.com/festivals/film/
+https://www.theguardian.com/film/2017/apr/13/star-wars-guillermo-del-toro-jabba-splinter-of-the-minds-eye
+https://www.filmindependent.org/spirit-awards/
+https://www.filmcomment.com/
+http://www.rsafilms.com/
+http://money.cnn.com/data/markets/
+http://www.reuters.com/finance/markets
+http://www.wsj.com/news/markets
+http://markets.financialcontent.com/
+http://www.cnbc.com/markets/
+http://www.cnbc.com/us-markets/
+http://www.marketwatch.com/
+http://markets.on.nytimes.com/
+http://markets.wsj.com/
+https://www.bloomberg.com/markets
+http://www.marketwatch.com/markets
+http://www.foxbusiness.com/markets.html
+http://www.nasdaq.com/markets/
+https://www.markets.com/
+http://www.businessinsider.com/moneygame
+http://www.investopedia.com/markets/
+http://www.forbes.com/markets/
+http://money.cnn.com/data/us_markets/
+https://www.benzinga.com/markets
+https://www.msn.com/en-us/money/markets
+http://www.cnbc.com/2017/04/12/asian-markets-to-focus-on-trump-dollar-comments-geopolitical-tensions.html
+http://www.cnbc.com/pre-markets/
+https://twitter.com/markets
+http://markets.businessinsider.com/
+http://www.businessdictionary.com/definition/market.html
+http://money.cnn.com/data/world_markets/europe/
+http://www.morningstar.com/markets.html
+https://en.wikipedia.org/wiki/Market
+http://markets.on.nytimes.com/research/markets/worldmarkets/worldmarkets.asp
+http://www.reuters.com/news/archive/businessNews
+http://www.reuters.com/finance
+http://money.cnn.com/news/
+https://www.wsj.com/news/business
+http://www.bbc.com/news/business
+http://www.nbcnews.com/business/business-news
+https://www.usnews.com/news/business
+http://www.foxbusiness.com/
+http://www.nytimes.com/pages/business/index.html
+http://finance.yahoo.com/news/
+http://www.nbcnews.com/business
+http://www.reuters.com/news/archive/businessNews%3Fview%3Dpage
+https://www.forbes.com/business/
+https://www.theguardian.com/us/business
+http://www.businessnewsdaily.com/
+http://mobile.reuters.com/business
+http://www.latimes.com/business/
+http://www.salon.com/category/business/
+http://hosted.ap.org/dynamic/fronts/BUSINESS%3FSITE%3DAP%26SECTION%3DHOME
+http://www.cnbc.com/world/
+http://www.cnbc.com/us-news/
+http://www.denverpost.com/business/
+http://www.reuters.com/news/archive/ousivMolt
+http://www.bbc.com/news/business-11876811
+https://www.aol.com/business/
+http://www.sfgate.com/business/
+http://www.independent.co.uk/news/business/news
+https://www.usatoday.com/money/business/
+http://www.charlotteobserver.com/news/business/
+http://money.cnn.com/data/markets/
+http://money.cnn.com/data/us_markets/
+http://www.cnbc.com/stocks/
+http://www.cnbc.com/us-markets/
+http://www.marketwatch.com/
+http://www.marketwatch.com/tools/marketsummary
+http://www.investopedia.com/terms/s/stockmarket.asp
+http://www.investors.com/category/market-trend/stock-market-today/
+https://www.nyse.com/
+http://www.nasdaq.com/
+http://money.cnn.com/data/world_markets/americas/
+http://money.cnn.com/data/world_markets/asia/
+http://money.cnn.com/data/world_markets/europe/
+https://www.msn.com/en-us/money/markets
+http://markets.wsj.com/
+https://www.bloomberg.com/markets/stocks
+http://www.reuters.com/finance/markets/us
+http://www.marketwatch.com/story/no-one-is-noticing-this-big-red-flag-for-the-stock-market-2017-04-12
+https://www.google.com/finance
+http://finance.yahoo.com/
+http://www.marketwatch.com/story/yellen-may-have-offered-the-clearest-explanation-for-the-stock-markets-record-run-2017-02-15
+http://www.marketwatch.com/markets
+https://www.thestreet.com/topic/47781/stock-market-today.html
+http://money.cnn.com/data/afterhours/
+http://money.cnn.com/investing/
+https://eresearch.fidelity.com/eresearch/goto/markets_sectors/landing.jhtml
+http://www.howthemarketworks.com/
+http://www.cnbc.com/
+https://www.nyse.com/markets/hours-calendars
+https://www.google.com/finance
+https://www.google.com/finance/portfolio
+http://www.google.com/finance
+https://finance.yahoo.com/
+http://finance.yahoo.com/
+https://en.wikipedia.org/wiki/Finance
+http://www.cnbc.com/finance/
+http://www.reuters.com/finance
+http://fortune.com/section/finance/
+http://www.investopedia.com/terms/f/finance.asp
+http://www.anderson.ucla.edu/faculty-and-research/finance
+https://www.ft.com/
+https://www.statefarm.com/finances
+http://www.investopedia.com/terms/f/financing.asp
+https://www.khanacademy.org/economics-finance-domain/core-finance
+http://www.finance-watch.org/
+https://www.usnews.com/best-colleges/rankings/business-finance
+https://www.tdautofinance.com/
+http://admissions.ucsd.edu/finances/
+http://powersports.honda.com/financing.aspx
+http://brandirectory.com/league_tables/table/global-500-2017
+http://ofn.org/
+https://www.principal.com/
+http://www.dell.com/en-us/learn/finance-and-rewards/financing-details
+http://www.dof.ca.gov/
+http://hbx.hbs.edu/courses/leading-with-finance/
+http://www.hondafinancialservices.com/
+http://onlinelibrary.wiley.com/journal/10.1111/(ISSN)1540-6261
+https://commercialobserver.com/finance/
+https://www.fashionnova.com/
+http://www.refinery29.com/fashion
+http://www.elle.com/fashion/
+https://hypebeast.com/fashion
+http://www.harpersbazaar.com/fashion/
+http://wwd.com/fashion-news/
+http://www.instyle.com/fashion
+http://www.highsnobiety.com/category/fashion/
+http://www.vogue.com/fashion
+https://en.wikipedia.org/wiki/Fashion
+https://www.merriam-webster.com/dictionary/fashion
+https://www.pinterest.com/explore/fashion/
+https://www.fashion.net/
+http://www.qvc.com/fashion/_/N-lglt/c.html
+http://www.marieclaire.com/fashion/
+http://www.manrepeller.com/fashion
+http://people.com/fashion/
+http://www.purewow.com/fashion
+http://www.ebay.com/rpp/fashion-main
+http://www.google.com/search?q=fashion&num=30&sa=N&prmd=ivnslba&tbm=isch&tbo=u&source=univ&ved=0ahUKEwjylOSnwaHTAhXFLmMKHUUNAXYQsAQIkAE
+https://hypebae.com/fashion
+http://weheartit.com/inspirations/fashion
+http://giphy.com/search/fashion
+http://tomandlorenzo.com/category/fashion/
+https://www.fashionnova.com/collections/plus
+http://www.ebay.com/rpp/moda-en
+http://robbreport.com/Fashion
+http://www.thesaurus.com/browse/fashion
+http://www.essence.com/fashion
+https://www.google.com/shopping%3Fhl%3Den
+http://www.discoverlosangeles.com/what-to-do/activities/shopping
+https://www.tripadvisor.com/Attractions-g32655-Activities-c26-Los_Angeles_California.html
+https://www.latourist.com/index.php%3Fpage%3Dshopping-center-links
+http://www.10best.com/destinations/california/los-angeles/shopping/best-shopping/
+http://www.10best.com/destinations/california/los-angeles/shopping/
+http://www.justluxe.com/travel/43__los-angeles/118__shopping.php
+https://www.timeout.com/los-angeles/shopping/best-shops-in-los-angeles
+https://www.airbnb.com/things-to-do/los-angeles/shopping/shopping-mall
+https://en.wikipedia.org/wiki/Shopping
+https://foursquare.com/top-places/los-angeles/best-places-malls
+http://www.shopping.com/
+http://www.racked.com/shopping
+http://www.vogue.com/fashion/shopping
+http://www.thegrovela.com/shop.php
+https://www.vegas.com/shopping/
+https://www.google.com/intl/en/retail/shopping-campaigns/
+https://play.google.com/store/apps/category/SHOPPING%3Fhl%3Den
+https://en.wikipedia.org/wiki/Shopping_mall
+http://www.lasvegas.com/things-to-do/shopping/
+https://www.nytimes.com/2017/04/07/realestate/shopping-for-desks.html
+https://shopping.yahoo.com/
+http://www.google.com/search?q=shopping&num=30&sa=N&prmd=ivnsma&tbm=isch&tbo=u&source=univ&ved=0ahUKEwiqk6KuwaHTAhVY8mMKHegyBlgQsAQIkgE
+https://www.hsn.com/
+https://www.experiencescottsdale.com/shopping/
+http://www.visitfortwayne.com/things-to-do/shopping/
+https://www.timeout.com/newyork/shopping
+https://www.visitalexandriava.com/things-to-do/shopping/
+http://www.skymilesshopping.com/
+http://www.visitorlando.com/things-to-do/shopping/
+http://www.latimes.com/business/realestate/
+http://www.realtor.com/realestateandhomes-search/Los-Angeles_CA
+http://www.realtor.com/
+https://www.zillow.com/los-angeles-ca/
+https://www.trulia.com/CA/Los_Angeles/
+https://www.redfin.com/city/11203/CA/Los-Angeles
+http://www.century21.com/real-estate/los-angeles-ca/LCCALOSANGELES/
+http://www.sothebysrealty.com/eng/sales/los-angeles-ca-usa
+http://la.curbed.com/
+https://losangeles.craigslist.org/search/rea
+http://www.theagencyre.com/los-angeles/luxury-real-estate/
+http://www.realestatetheband.com/
+https://en.wikipedia.org/wiki/Real_estate
+http://www.dre.ca.gov/
+http://www.realtor.com/realestateforsale
+https://www.zillow.com/
+https://lusk.usc.edu/
+https://www.aol.com/real-estate/
+https://twitter.com/realestateband%3Flang%3Den
+http://www.investopedia.com/terms/r/realestate.asp
+https://www.facebook.com/realestateband/
+https://www.wsj.com/news/realestate
+http://realestate.usnews.com/
+http://www.sfgate.com/realestate/
+https://www.msn.com/en-us/money/realestate
+http://www.cnbc.com/real-estate/
+http://www.wrealestate.net/
+https://www.yahoo.com/news/tagged/realestate/
+https://www.recenter.tamu.edu/
+https://www.topuniversities.com/universities
+https://www.topuniversities.com/
+https://www.topuniversities.com/courses
+https://www.usnews.com/best-colleges/rankings/national-universities
+https://www.usnews.com/best-colleges/rankings/national-universities/top-public
+http://www.thebestschools.org/features/100-best-universities-in-world-today/
+https://www.niche.com/colleges/rankings/top-private-universities/
+https://www.niche.com/colleges/rankings/top-public-universities/s/california/
+https://www.thecompleteuniversityguide.co.uk/universities/
+https://en.wikipedia.org/wiki/University
+https://www.usnews.com/education/best-global-universities/rankings%3Fpage%3D3
+https://www.usnews.com/best-colleges/rankings/regional-universities-north
+https://www.usnews.com/best-colleges/rankings/regional-universities-west
+https://www.usnews.com/education/best-global-universities/rankings%3Fpage%3D4
+http://www.shanghairanking.com/Search.html
+http://www.shanghairanking.com/Search.jsp
+http://www.aau.edu/
+https://univ.cc/
+http://www.clas.ufl.edu/au/
+http://www.sfgate.com/education/article/Here-s-what-to-study-at-Bay-Area-universities-11066967.php
+http://www.shanghairanking.com/
+https://www.usnews.com/best-colleges/rankings/regional-universities-south
+https://www.timeshighereducation.com/student/best-universities/best-universities-asia
+https://www.timeshighereducation.com/student/best-universities/worlds-best-small-universities-2017
+http://www.universitiesuk.ac.uk/
+https://www.niche.com/colleges/rankings/top-public-universities/s/new-york/
+https://www.niche.com/colleges/rankings/top-public-universities/methodology/
+https://www.nirfindia.org/univ
+https://studyinsweden.se/universities/
+https://app.studyisland.com/cfw/login
+http://www.studyisland.com/
+http://www.dictionary.com/browse/study
+https://www.studyisland.com/login
+https://www.merriam-webster.com/dictionary/study
+http://www.thesaurus.com/browse/study
+http://studyjams.scholastic.com/
+https://en.wikipedia.org/wiki/Study
+http://www.cse.buffalo.edu/~rapaport/howtostudy.html
+https://en.wiktionary.org/wiki/study
+https://www.studyblue.com/
+https://www.study.net/
+http://www.thestudyatyale.com/
+https://www.iie.org/Programs/Generation-Study-Abroad
+http://www.cscc.unc.edu/hchs/
+https://www.studystack.com/
+https://russia.study/en
+https://www.ml.com/articles/age-wave-survey.html
+https://www.accenture.com/us-en/insight-un-global-compact-ceo-study
+https://heatst.com/culture-wars/study-babies-start-being-racist-at-six-months-old/
+http://learningenglish.voanews.com/a/education-tips-avoid-ineffective-study-methods/3795956.html
+http://www.studyabroadfunding.org/
+http://www.studygateway.com/
+http://www.sciencemag.org/news/2017/04/study-finds-significant-differences-brains-men-and-women
+http://womenintvfilm.sdsu.edu/research/
+https://www1.nyc.gov/assets/dca/downloads/pdf/partners/Study-of-Gender-Pricing-in-NYC.pdf
+http://www.npr.org/sections/health-shots/2017/04/11/523388406/spinal-manipulation-can-alleviate-back-pain-study-concludes
+http://pubs.acs.org/doi/abs/10.1021/acs.estlett.7b00043
+https://www.studyportals.com/
+https://abcdstudy.org/
+https://en.wikipedia.org/wiki/Mathematics
+https://www.math.ucla.edu/
+https://math.mit.edu/
+https://www.hackerrank.com/domains/mathematics
+https://math.umn.edu/
+https://math.duke.edu/
+https://mathematics.stanford.edu/
+http://www.maa.org/
+https://www.math.princeton.edu/
+http://www.ams.org/
+https://www.math.cornell.edu/
+http://www.math.harvard.edu/
+http://pma.caltech.edu/content/math
+https://play.google.com/store/apps/details%3Fid%3Dde.daboapps.mathematics%26hl%3Den
+https://www.theguardian.com/science/mathematics
+http://www.maa.org/press/periodicals/mathematics-magazine
+https://www.quantamagazine.org/category/mathematics-2/
+http://www.mdpi.com/journal/mathematics
+https://en.wiktionary.org/wiki/mathematics
+http://www.math.uiuc.edu/
+http://mathematics.jhu.edu/
+http://www.csun.edu/science-mathematics/mathematics
+https://math.iastate.edu/
+https://math.berkeley.edu/
+https://www.ma.utexas.edu/
+https://www.math.toronto.edu/
+http://annals.math.princeton.edu/
+https://ocw.mit.edu/courses/mathematics/
+https://www.nctm.org/
+https://en.wikipedia.org/wiki/Physics
+https://www.physics.org/
+https://www.khanacademy.org/science/physics
+https://physics.aps.org/
+https://physics.stanford.edu/
+http://www.pa.ucla.edu/
+http://physicsworld.com/
+https://www.physics.harvard.edu/
+http://www.physicsclassroom.com/
+http://web.mit.edu/physics/
+http://physics.berkeley.edu/
+http://www.physicscentral.com/
+https://www.edx.org/course/subject/physics
+http://physics.illinois.edu/
+https://www.aip.org/
+http://physics.yale.edu/
+http://www.physics.ucsb.edu/
+http://physics.ucdavis.edu/
+https://physics.uoregon.edu/
+https://www.reddit.com/r/Physics/
+https://jobs.physicstoday.org/
+http://www.pma.caltech.edu/content/physics
+https://phys.org/physics-news/
+https://www.phys.washington.edu/
+https://ocw.mit.edu/courses/physics/
+https://www.physics.uci.edu/
+http://www.physics.sunysb.edu/
+https://www.phy.duke.edu/
+https://physics.uchicago.edu/
+https://en.wikipedia.org/wiki/Chemistry
+https://www.khanacademy.org/science/chemistry
+https://www.chemistry.com/
+http://www.dictionary.com/browse/chemistry
+http://chemistry.mit.edu/
+http://chemistry.dartmouth.edu/
+https://ocw.mit.edu/courses/chemistry/
+https://chemistry.stanford.edu/
+https://www.acs.org/
+http://www.rsc.org/
+http://chemistry.berkeley.edu/
+https://chem.umn.edu/
+http://www.pearsonmylabandmastering.com/masteringchemistry/
+http://chemistry.stackexchange.com/
+https://apstudent.collegeboard.org/apcourse/ap-chemistry
+http://www.chemistry.ucla.edu/
+http://www.chem.cmu.edu/
+http://www.chem.colostate.edu/
+https://www.usnews.com/best-graduate-schools/top-science-schools/chemistry-rankings
+http://chem.berkeley.edu/
+https://chemistry.uoregon.edu/
+https://www.chem.uci.edu/
+https://clep.collegeboard.org/science-and-mathematics/chemistry
+https://chemistry.princeton.edu/
+http://www.google.com/search?q=chemistry&num=30&sa=N&prmd=ivnsb&tbm=isch&tbo=u&source=univ&ved=0ahUKEwjskKHfwaHTAhUB8mMKHWljDGAQsAQItgE
+https://www.nsf.gov/div/index.jsp%3Fdiv%3DCHE
+https://www.coursera.org/learn/chemistry-1
+http://www.chemistry.ucla.edu/chemistry-graduate-program
+https://www.pomona.edu/academics/departments/chemistry
+https://en.wikipedia.org/wiki/Biology
+https://www.khanacademy.org/science/biology
+https://biology.stanford.edu/
+https://biology.duke.edu/
+http://www.latimes.com/topic/science/scientific-research/biology/13004008-topic.html
+http://www.sciencemag.org/category/biology
+http://www.biology.arizona.edu/
+https://www.sciencedaily.com/news/plants_animals/biology/
+http://www.cell.com/current-biology/home
+https://biology.mit.edu/
+http://www.bbe.caltech.edu/
+http://www.mdpi.com/journal/biology
+https://ocw.mit.edu/courses/biology/
+http://journals.plos.org/plosbiology/
+https://biology.uoregon.edu/
+https://apstudent.collegeboard.org/apcourse/ap-biology
+https://clep.collegeboard.org/science-and-mathematics/biology
+http://www.biology-online.org/
+http://bio.jhu.edu/
+http://www.biology.colostate.edu/
+https://www.pomona.edu/academics/departments/biology
+https://www.eeb.ucla.edu/
+http://www.swarthmore.edu/biology
+https://www.reddit.com/r/biology/
+http://rsbl.royalsocietypublishing.org/
+https://www.biology.washington.edu/
+http://bio.biologists.org/
+https://bmcbiol.biomedcentral.com/
+http://jobs.sciencecareers.org/jobs/biology/
+https://en.wikipedia.org/wiki/Quantum_mechanics
+https://en.wikipedia.org/wiki/Introduction_to_quantum_mechanics
+http://www.forbes.com/sites/chadorzel/2015/07/08/six-things-everyone-should-know-about-quantum-physics/
+https://www.khanacademy.org/science/physics/quantum-physics
+http://www.bbc.com/earth/story/20170215-the-strange-link-between-the-human-mind-and-quantum-physics
+http://www.nature.com/news/quantum-physics-what-is-really-real-1.17585
+http://whatis.techtarget.com/definition/quantum-theory
+https://www.britannica.com/science/quantum-mechanics-physics
+http://www.pbs.org/transistor/science/info/quantum.html
+http://www.livescience.com/33816-quantum-mechanics-explanation.html
+https://www.sciencedaily.com/news/matter_energy/quantum_physics/
+http://www.nybooks.com/articles/2017/01/19/trouble-with-quantum-mechanics/
+https://phys.org/physics-news/quantum-physics/
+https://ocw.mit.edu/courses/physics/8-04-quantum-physics-i-spring-2013/
+https://physics.aps.org/synopsis-for/10.1103/PhysRevLett.118.150601
+https://www.theatlantic.com/science/archive/2016/11/quantum-brain/506768/
+https://simple.wikipedia.org/wiki/Quantum_mechanics
+https://arxiv.org/archive/quant-ph
+https://www.wired.com/2013/12/amplituhedron-jewel-quantum-physics/
+https://en.wikipedia.org/wiki/Interpretations_of_quantum_mechanics
+https://en.wikipedia.org/wiki/History_of_quantum_mechanics
+http://www.google.com/search?q=quantum+physics&num=30&sa=N&prmd=ivnsb&tbm=isch&tbo=u&source=univ&ved=0ahUKEwivqPj3waHTAhVX92MKHe2sCfMQsAQIowE
+http://quantumphysics.iop.org/
+http://www.nature.com/subjects/quantum-physics
+http://hyperphysics.phy-astr.gsu.edu/hbase/quacon.html
+https://arxiv.org/list/quant-ph/recent
+https://arxiv.org/list/quant-ph/new
+http://nautil.us/issue/29/scaling/will-quantum-mechanics-swallow-relativity
+https://en.wikipedia.org/wiki/Aeronautics
+https://www.nasa.gov/topics/aeronautics/
+https://www.nasa.gov/aeroresearch
+https://www.nasa.gov/audience/foreducators/topnav/materials/listbytype/Aeronautics.html
+https://www.grc.nasa.gov/www/k-12/UEET/StudentSite/aeronautics.html
+https://erau.edu/degrees/bachelor/aeronautics/
+http://www.dictionary.com/browse/aeronautics
+http://erau.edu/degrees/bachelor/aeronautics%3Fcampus%3Dww
+https://www.merriam-webster.com/dictionary/aeronautics
+http://www.lockheedmartin.com/us/aeronautics.html
+https://www.aiaa.org/
+http://daytonabeach.erau.edu/degrees/bachelor/aeronautics/
+http://erau.edu/degrees/associate/aeronautics%3Fcampus%3Dww
+http://aeronautics-sys.com/
+https://erau.edu/
+http://www.galcit.caltech.edu/
+http://www.dictionary.com/browse/aeronautical
+https://www.nasa.gov/
+https://www.theguardian.com/science/aeronautics
+https://www.groupon.com/deals/south-coast-aeronautics
+https://erau.edu/degrees/associate/aeronautics/
+https://www.edx.org/course/introduction-aeronautical-engineering-delftx-ae1110x-2
+https://www.imperial.ac.uk/aeronautics/
+http://erau.edu/degrees/master/aeronautics
+http://prescott.erau.edu/degrees/bachelor/aeronautics/
+http://worldwide.erau.edu/colleges/aeronautics/
+https://www.nasa.gov/centers/glenn/aeronautics/
+https://www.nasa.gov/centers/langley/aeronautics/
+http://dictionary.cambridge.org/dictionary/english/aeronautics
+https://www.aerosociety.com/careers-education/schools-outreach/cool-aeronautics/
+http://food-la.com/
+https://www.yelp.com/biz/food-los-angeles-2
+https://www.yelp.com/c/la/food
+http://www.foodnetwork.com/
+http://www.latimes.com/food/
+http://www.food.com/
+https://losangeles.craigslist.org/search/fbh
+https://foodforward.org/
+https://en.wikipedia.org/wiki/Food
+http://www.today.com/food
+https://www.reddit.com/r/FoodLosAngeles/
+https://www.buzzfeed.com/food
+http://www.truefoodkitchen.com/
+https://www.thrillist.com/eat/los-angeles/the-50-best-things-to-eat-in-la-iconic-foods-bucket-list
+https://savethefood.com/
+https://www.fda.gov/food/
+http://www.foodday.org/
+http://www.wholefoodsmarket.com/
+http://people.com/food/
+http://www.lamag.com/food/
+http://www.foodmatters.com/
+http://www.foodrepublic.com/
+http://www.google.com/search?q=food&num=30&sa=N&prmd=ivnsml&tbm=isch&tbo=u&source=univ&ved=0ahUKEwjz4cuIwqHTAhUBDWMKHXSsD7QQsAQImgE
+https://foodtank.com/
+http://foodbabe.com/
+https://foodfirst.org/
+https://www.buzzfeed.com/melissaharrison/best-cheap-eats-in-los-angeles
+https://www.supertracker.usda.gov/foodtracker.aspx
+https://www.foodrecoverynetwork.org/
+http://www.foodandwaterwatch.org/
diff --git a/lib/schemas/forensic_metadata_schema.xsd b/lib/schemas/forensic_metadata_schema.xsd
new file mode 100644
index 000000000..24a3ea748
--- /dev/null
+++ b/lib/schemas/forensic_metadata_schema.xsd
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/schemas/scenario_schema.xsd b/lib/schemas/scenario_schema.xsd
index 2dcf001b3..bbd2c4771 100644
--- a/lib/schemas/scenario_schema.xsd
+++ b/lib/schemas/scenario_schema.xsd
@@ -15,12 +15,13 @@
-
-
+
+
+
-
-
-
+
+
+
@@ -43,9 +44,10 @@
-
-
-
+
+
+
+
@@ -105,7 +107,7 @@
-
+
diff --git a/lib/templates/Puppetfile.erb b/lib/templates/Puppetfile.erb
index e495057c7..05da9c2a8 100644
--- a/lib/templates/Puppetfile.erb
+++ b/lib/templates/Puppetfile.erb
@@ -8,12 +8,14 @@
forge "https://forgeapi.puppetlabs.com"
-mod 'puppetlabs-stdlib', '4.18.0' # stdlib enables parsejson() in manifests and other useful functions
+mod 'puppetlabs-stdlib', '4.6.0' # stdlib enables parsejson() in manifests and other useful functions
+mod 'puppetlabs-powershell', '2.1.0'
+mod 'puppetlabs-registry', '1.0.0'
mod 'SecGen-secgen_functions', :path => '<%= SECGEN_FUNCTIONS_PUPPET_DIR %>'
<% @currently_processing_system.module_selections.each do |selected_module| -%>
<% case selected_module.module_type
- when 'vulnerability', 'service', 'utility', 'build' -%>
+ when 'vulnerability', 'service', 'utility', 'build', 'forensic' -%>
mod 'SecGen-<%= selected_module.module_path_name %>/<%= selected_module.module_path_end %>', :path => '<%="#{ROOT_DIR}/#{selected_module.module_path}"%>'
<% end -%>
<% end -%>
diff --git a/lib/templates/Vagrantfile.erb b/lib/templates/Vagrantfile.erb
index 96a1d884d..7b8c17f4f 100644
--- a/lib/templates/Vagrantfile.erb
+++ b/lib/templates/Vagrantfile.erb
@@ -150,7 +150,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
<%= system.name %>.vm.network :<%= selected_module.attributes['type'].first %>, ip: "<%= resolve_network(selected_module)%>"
<% end %>
<% end -%>
-<% when 'vulnerability', 'service', 'utility', 'build' -%>
+<% when 'vulnerability', 'service', 'utility', 'build', 'forensic' -%>
<% module_name = selected_module.module_path_name -%>
<%= system.name %>.vm.provision "puppet" do | <%=module_name%> |
<% # if there are facter variables to define
@@ -172,7 +172,11 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
<%=module_name%>.module_path = "<%="puppet/#{system.name}/modules"%>"
<%=module_name%>.environment_path = "environments/"
<%=module_name%>.environment = "production"
+ <% if (selected_module.attributes['platform'].first.downcase == 'windows') -%>
+ #<%=module_name%>.synced_folder_type = "smb"
+ <% else -%>
<%=module_name%>.synced_folder_type = "rsync"
+ <% end -%>
<%=module_name%>.manifests_path = "<%="puppet/#{system.name}/modules/#{selected_module.module_path_end}"%>"
<%=module_name%>.manifest_file = "<%="#{selected_module.module_path_end}.pp"%>"
end
diff --git a/modules/bases/windows_server_2008_r2_amd64/Packerfile.erb b/modules/bases/windows_server_2008_r2_amd64/Packerfile.erb
index 7b1ef9500..9dbc9fdad 100644
--- a/modules/bases/windows_server_2008_r2_amd64/Packerfile.erb
+++ b/modules/bases/windows_server_2008_r2_amd64/Packerfile.erb
@@ -6,7 +6,7 @@
<%= if (@options.has_key? :memory_per_vm)
"[ \"modifyvm\", \"{{.Name}}\", \"--memory\", \"#{@options[:memory_per_vm]}\" ],"
elsif (@options.has_key? :total_memory)
- "[ \"modifyvm\", \"{{.Name}}\", \"--memory\", \"#{@options[:total_memory]/@systems.length}\" ],"
+ "[ \"modifyvm\", \"{{.Name}}\", \"--memory\", \"#{@options[:total_memory].to_i/@systems.length}\" ],"
end %>
<%= if (@options.has_key? :cpu_cores)
"[ \"modifyvm\", \"{{.Name}}\", \"--cpus\", \"#{@options[:cpu_cores]}\" ],"
diff --git a/modules/encoders/hash/md5_file/manifests/.no_puppet b/modules/encoders/hash/md5_file/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/md5_file/md5_file.pp b/modules/encoders/hash/md5_file/md5_file.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/md5_file/secgen_local/local.rb b/modules/encoders/hash/md5_file/secgen_local/local.rb
new file mode 100644
index 000000000..4d97cb99d
--- /dev/null
+++ b/modules/encoders/hash/md5_file/secgen_local/local.rb
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require 'digest'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class MD5Encoder < StringEncoder
+ def initialize
+ super
+ self.module_name = 'MD5 hash'
+ end
+
+ def encode(file_path)
+ Digest::MD5.file file_path
+ end
+
+end
+
+MD5Encoder.new.run
diff --git a/modules/encoders/hash/md5_file/secgen_metadata.xml b/modules/encoders/hash/md5_file/secgen_metadata.xml
new file mode 100644
index 000000000..16ab03493
--- /dev/null
+++ b/modules/encoders/hash/md5_file/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ MD5 hash encoder file
+ Jason Keighley
+ Apache v2
+ MD5 hash encoder module
+
+ hash
+ windows
+
+ strings_to_encode
+
+ hash
+
diff --git a/modules/encoders/hash/md5_string/manifests/.no_puppet b/modules/encoders/hash/md5_string/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/md5_string/md5_string.pp b/modules/encoders/hash/md5_string/md5_string.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/md5_string/secgen_local/local.rb b/modules/encoders/hash/md5_string/secgen_local/local.rb
new file mode 100644
index 000000000..4d73161fa
--- /dev/null
+++ b/modules/encoders/hash/md5_string/secgen_local/local.rb
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require 'digest'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class MD5Encoder < StringEncoder
+ def initialize
+ super
+ self.module_name = 'MD5 hash'
+ end
+
+ def encode(str)
+ Digest::MD5.base64digest str
+ end
+
+end
+
+MD5Encoder.new.run
diff --git a/modules/encoders/hash/md5_string/secgen_metadata.xml b/modules/encoders/hash/md5_string/secgen_metadata.xml
new file mode 100644
index 000000000..1d03453cd
--- /dev/null
+++ b/modules/encoders/hash/md5_string/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ MD5 hash encoder string
+ Jason Keighley
+ Apache v2
+ MD5 hash encoder module
+
+ hash
+ windows
+
+ strings_to_encode
+
+ hash
+
diff --git a/modules/encoders/hash/sha1_file/manifests/.no_puppet b/modules/encoders/hash/sha1_file/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha1_file/secgen_local/local.rb b/modules/encoders/hash/sha1_file/secgen_local/local.rb
new file mode 100644
index 000000000..cd8df4d70
--- /dev/null
+++ b/modules/encoders/hash/sha1_file/secgen_local/local.rb
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require 'digest'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class MD5Encoder < StringEncoder
+ def initialize
+ super
+ self.module_name = 'MD5 hash'
+ end
+
+ def encode(file_path)
+ Digest::SHA1.file file_path
+ end
+
+end
+
+MD5Encoder.new.run
diff --git a/modules/encoders/hash/sha1_file/secgen_metadata.xml b/modules/encoders/hash/sha1_file/secgen_metadata.xml
new file mode 100644
index 000000000..206508bfa
--- /dev/null
+++ b/modules/encoders/hash/sha1_file/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ SHA1 hash encoder file
+ Jason Keighley
+ Apache v2
+ SHA1 hash encoder module
+
+ hash
+ windows
+
+ strings_to_encode
+
+ hash
+
diff --git a/modules/encoders/hash/sha1_file/sha1_file.pp b/modules/encoders/hash/sha1_file/sha1_file.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha1_string/manifests/.no_puppet b/modules/encoders/hash/sha1_string/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha1_string/secgen_local/local.rb b/modules/encoders/hash/sha1_string/secgen_local/local.rb
new file mode 100644
index 000000000..0cfb7de4e
--- /dev/null
+++ b/modules/encoders/hash/sha1_string/secgen_local/local.rb
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require 'digest'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class SHA1Encoder < StringEncoder
+ def initialize
+ super
+ self.module_name = 'MD5 hash'
+ end
+
+ def encode(str)
+ Digest::SHA1.base64digest str
+ end
+
+end
+
+SHA1Encoder.new.run
diff --git a/modules/encoders/hash/sha1_string/secgen_metadata.xml b/modules/encoders/hash/sha1_string/secgen_metadata.xml
new file mode 100644
index 000000000..facabd2e8
--- /dev/null
+++ b/modules/encoders/hash/sha1_string/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ SHA1 hash encoder string
+ Jason Keighley
+ Apache v2
+ SHA1 hash encoder module
+
+ hash
+ windows
+
+ strings_to_encode
+
+ hash
+
diff --git a/modules/encoders/hash/sha1_string/sha1_string.pp b/modules/encoders/hash/sha1_string/sha1_string.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha256_string/manifests/.no_puppet b/modules/encoders/hash/sha256_string/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha256_string/secgen_local/local.rb b/modules/encoders/hash/sha256_string/secgen_local/local.rb
new file mode 100644
index 000000000..44a1acc5b
--- /dev/null
+++ b/modules/encoders/hash/sha256_string/secgen_local/local.rb
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require 'digest'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class SHA256Encoder < StringEncoder
+ def initialize
+ super
+ self.module_name = 'MD5 hash'
+ end
+
+ def encode(str)
+ Digest::SHA256.base64digest str
+ end
+
+end
+
+SHA256Encoder.new.run
diff --git a/modules/encoders/hash/sha256_string/secgen_metadata.xml b/modules/encoders/hash/sha256_string/secgen_metadata.xml
new file mode 100644
index 000000000..d70d4f0be
--- /dev/null
+++ b/modules/encoders/hash/sha256_string/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ SHA256 hash encoder string
+ Jason Keighley
+ Apache v2
+ SHA256 hash encoder module
+
+ hash
+ windows
+
+ strings_to_encode
+
+ hash
+
diff --git a/modules/encoders/hash/sha256_string/sha256_string.pp b/modules/encoders/hash/sha256_string/sha256_string.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha384_string/manifests/.no_puppet b/modules/encoders/hash/sha384_string/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha384_string/secgen_local/local.rb b/modules/encoders/hash/sha384_string/secgen_local/local.rb
new file mode 100644
index 000000000..72398f9cc
--- /dev/null
+++ b/modules/encoders/hash/sha384_string/secgen_local/local.rb
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require 'digest'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class SHA256Encoder < StringEncoder
+ def initialize
+ super
+ self.module_name = 'MD5 hash'
+ end
+
+ def encode(str)
+ Digest::SHA384.base64digest str
+ end
+
+end
+
+SHA256Encoder.new.run
diff --git a/modules/encoders/hash/sha384_string/secgen_metadata.xml b/modules/encoders/hash/sha384_string/secgen_metadata.xml
new file mode 100644
index 000000000..76da3e080
--- /dev/null
+++ b/modules/encoders/hash/sha384_string/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ SHA384 hash encoder string
+ Jason Keighley
+ Apache v2
+ SHA384 hash encoder module
+
+ hash
+ windows
+
+ strings_to_encode
+
+ hash
+
diff --git a/modules/encoders/hash/sha384_string/sha384_string.pp b/modules/encoders/hash/sha384_string/sha384_string.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha512_string/manifests/.no_puppet b/modules/encoders/hash/sha512_string/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/hash/sha512_string/secgen_local/local.rb b/modules/encoders/hash/sha512_string/secgen_local/local.rb
new file mode 100644
index 000000000..743e66f64
--- /dev/null
+++ b/modules/encoders/hash/sha512_string/secgen_local/local.rb
@@ -0,0 +1,17 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require 'digest'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class SHA512Encoder < StringEncoder
+ def initialize
+ super
+ self.module_name = 'MD5 hash'
+ end
+
+ def encode(str)
+ Digest::SHA2.new(512).base64digest str
+ end
+
+end
+
+SHA512Encoder.new.run
diff --git a/modules/encoders/hash/sha512_string/secgen_metadata.xml b/modules/encoders/hash/sha512_string/secgen_metadata.xml
new file mode 100644
index 000000000..2a8c10c8f
--- /dev/null
+++ b/modules/encoders/hash/sha512_string/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ SHA512 hash encoder string
+ Jason Keighley
+ Apache v2
+ SHA512 hash encoder module
+
+ hash
+ windows
+
+ strings_to_encode
+
+ hash
+
diff --git a/modules/encoders/hash/sha512_string/sha512_string.pp b/modules/encoders/hash/sha512_string/sha512_string.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/utility/save_file_to_storage_module/manifests/.no_puppet b/modules/encoders/utility/save_file_to_storage_module/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/utility/save_file_to_storage_module/save_file_to_storage_module.pp b/modules/encoders/utility/save_file_to_storage_module/save_file_to_storage_module.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/encoders/utility/save_file_to_storage_module/secgen_local/local.rb b/modules/encoders/utility/save_file_to_storage_module/secgen_local/local.rb
new file mode 100644
index 000000000..33bc13d12
--- /dev/null
+++ b/modules/encoders/utility/save_file_to_storage_module/secgen_local/local.rb
@@ -0,0 +1,33 @@
+#!/usr/bin/ruby
+require 'fileutils'
+require_relative '../../../../../lib/objects/local_string_encoder.rb'
+class SaveFileToStorageModule < StringEncoder
+ attr_accessor :file_path
+
+ def initialize
+ super
+ self.module_name = 'Save file to storage module'
+ self.file_path = ''
+ end
+
+ def encode_all
+ filename = Base64.decode64(file_path).split('/').last
+ # filename = file_path.split('/').last
+ FileUtils.cp Base64.decode64(file_path), "#{FILE_TRANSFER_STORAGE_MODULE_DIR}/files/#{filename}"
+ self.outputs << filename
+ end
+
+ def get_options_array
+ super + [['--file_path', GetoptLong::REQUIRED_ARGUMENT]]
+ end
+
+ def process_options(opt, arg)
+ super
+ case opt
+ when '--file_path'
+ self.file_path << arg;
+ end
+ end
+end
+
+SaveFileToStorageModule.new.run
diff --git a/modules/encoders/utility/save_file_to_storage_module/secgen_metadata.xml b/modules/encoders/utility/save_file_to_storage_module/secgen_metadata.xml
new file mode 100644
index 000000000..4a682363a
--- /dev/null
+++ b/modules/encoders/utility/save_file_to_storage_module/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ Save file to storage module
+ Jason Keighley
+ Apache v2
+ Saves the given file to the storage module be used in transfer via the file { source => puppet:/// } puppet resource
+
+ utility
+ windows
+
+ file_path
+
+ file_path
+
diff --git a/modules/forensics/windows/file_manipulation/create_directory/create_directory.pp b/modules/forensics/windows/file_manipulation/create_directory/create_directory.pp
new file mode 100644
index 000000000..f2f7ca2ef
--- /dev/null
+++ b/modules/forensics/windows/file_manipulation/create_directory/create_directory.pp
@@ -0,0 +1,7 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$new_directory_path=$secgen_parameters['new_directory_path'][0]
+
+class { 'create_directory':
+ directory_path => $new_directory_path,
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/file_manipulation/create_directory/manifests/init.pp b/modules/forensics/windows/file_manipulation/create_directory/manifests/init.pp
new file mode 100644
index 000000000..4da2b660e
--- /dev/null
+++ b/modules/forensics/windows/file_manipulation/create_directory/manifests/init.pp
@@ -0,0 +1,6 @@
+class create_directory ($directory_path) {
+ file { 'create_directory':
+ path => $directory_path,
+ ensure => 'directory',
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/file_manipulation/create_directory/secgen_metadata.xml b/modules/forensics/windows/file_manipulation/create_directory/secgen_metadata.xml
new file mode 100644
index 000000000..baf7c260e
--- /dev/null
+++ b/modules/forensics/windows/file_manipulation/create_directory/secgen_metadata.xml
@@ -0,0 +1,23 @@
+
+
+
+ Create directory
+ Jason Keighley
+ Apache v2
+ Create new directory
+
+ file_manipulation
+ windows
+
+
+
+
+ new_directory_path
+
+
+ C:\Users\vagrant\Desktop\Hello
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/file_manipulation/create_file/create_file.pp b/modules/forensics/windows/file_manipulation/create_file/create_file.pp
new file mode 100644
index 000000000..66f79fb4a
--- /dev/null
+++ b/modules/forensics/windows/file_manipulation/create_file/create_file.pp
@@ -0,0 +1,9 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$new_file_path=$secgen_parameters['new_file_path'][0]
+$new_file_contents=$secgen_parameters['new_file_contents'][0]
+
+class { 'create_file':
+ file_path => $new_file_path,
+ file_contents => $new_file_contents
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/file_manipulation/create_file/manifests/init.pp b/modules/forensics/windows/file_manipulation/create_file/manifests/init.pp
new file mode 100644
index 000000000..59c06b08c
--- /dev/null
+++ b/modules/forensics/windows/file_manipulation/create_file/manifests/init.pp
@@ -0,0 +1,7 @@
+class create_file ($file_path, $file_contents) {
+ file { 'create_file':
+ path => $file_path,
+ ensure => 'file',
+ content => $file_contents,
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/file_manipulation/create_file/secgen_metadata.xml b/modules/forensics/windows/file_manipulation/create_file/secgen_metadata.xml
new file mode 100644
index 000000000..23d615ed4
--- /dev/null
+++ b/modules/forensics/windows/file_manipulation/create_file/secgen_metadata.xml
@@ -0,0 +1,28 @@
+
+
+
+ Create file
+ Jason Keighley
+ Apache v2
+ Create new file
+
+ file_manipulation
+ windows
+
+
+
+
+ new_file_path
+ new_file_contents
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+ Test Test Test Test Test
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/file_transfer_storage_module.pp b/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/file_transfer_storage_module.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/files/.no_files b/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/files/.no_files
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/manifests/.no_puppet b/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/secgen_metadata.xml b/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/secgen_metadata.xml
new file mode 100644
index 000000000..c43f4ed2b
--- /dev/null
+++ b/modules/forensics/windows/file_transfer_storage/file_transfer_storage_module/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ Store files for transfer
+ Jason Keighley
+ Apache v2
+ Store files to be grabbed via the file { source => puppet:/// } resource to avoid file transfer issues via the source and content tags
+
+ file_transfer_storage
+ windows
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/illegal_images/add_illegal_images_cats/add_illegal_images_cats.pp b/modules/forensics/windows/illegal_images/add_illegal_images_cats/add_illegal_images_cats.pp
new file mode 100644
index 000000000..985f51594
--- /dev/null
+++ b/modules/forensics/windows/illegal_images/add_illegal_images_cats/add_illegal_images_cats.pp
@@ -0,0 +1,21 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$image_path=$secgen_parameters['image_path'][0]
+$original_image_filename=$secgen_parameters['original_image_filename'][0]
+
+class { 'add_illegal_images_cats':
+ image_path => $image_path,
+ image_binary => $original_image_filename
+}
+
+# $image_binary = base64('decode', $image_binary)
+
+# file { 'add_cat_image':
+# path => "$image_path",
+# ensure => 'file',
+# # content => $image_binary,
+# # content => base64('decode', $image_binary),
+# # source => '/media/user/3TB_internal_drive/Documents/SecGen/lib/resources/internet_browser_files/chrome_history_file.source',
+# # source => 'puppet:///modules/add_illegal_images_cats/chrome_history_file.source'
+# source => "puppet:///modules/file_transfer_storage_module/$original_image_filename"
+# }
\ No newline at end of file
diff --git a/modules/forensics/windows/illegal_images/add_illegal_images_cats/manifests/init.pp b/modules/forensics/windows/illegal_images/add_illegal_images_cats/manifests/init.pp
new file mode 100644
index 000000000..1b7498f9a
--- /dev/null
+++ b/modules/forensics/windows/illegal_images/add_illegal_images_cats/manifests/init.pp
@@ -0,0 +1,11 @@
+class add_illegal_images_cats ($image_path, $image_binary) {
+ file { 'add_cat_image':
+ path => "$image_path",
+ ensure => 'file',
+ # content => $image_binary,
+ # content => base64('decode', $image_binary),
+ # source => '/media/user/3TB_internal_drive/Documents/SecGen/lib/resources/internet_browser_files/chrome_history_file.source',
+ # source => 'puppet:///modules/add_illegal_images_cats/chrome_history_file.source'
+ source => "puppet:///modules/file_transfer_storage_module/$original_image_filename"
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/illegal_images/add_illegal_images_cats/secgen_metadata.xml b/modules/forensics/windows/illegal_images/add_illegal_images_cats/secgen_metadata.xml
new file mode 100644
index 000000000..24706de7b
--- /dev/null
+++ b/modules/forensics/windows/illegal_images/add_illegal_images_cats/secgen_metadata.xml
@@ -0,0 +1,41 @@
+
+
+
+ Add illegal images cats
+ Jason Keighley
+ Apache v2
+ Add illegal cat images (simulation of illegal images)
+
+ illegal_images
+ windows
+
+
+
+
+ image_path
+
+
+ C:\Users\vagrant\Desktop\Hello.jpg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Store files for transfer
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/internet_artifacts/internet_history_chrome/internet_history_chrome.pp b/modules/forensics/windows/internet_artifacts/internet_history_chrome/internet_history_chrome.pp
new file mode 100644
index 000000000..2b48c362e
--- /dev/null
+++ b/modules/forensics/windows/internet_artifacts/internet_history_chrome/internet_history_chrome.pp
@@ -0,0 +1,60 @@
+notice("THE MODULE internet history chrome was LOADED")
+
+# include internet_history_chrome::init
+
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$chrome_history_file_name=$secgen_parameters['chrome_history_file_name'][0]
+
+# notice("Chrome history file value")
+# notice($chrome_history_file_name)
+# notice("Chrome history file value end")
+#
+#
+# notice("THE MODULE internet history chrome was LOADED #2")
+#
+# $user_account = 'vagrant'
+# $url_paths = ["https://www.offensive-security.com/backtrack/exploit-db-updates"]
+#
+# file { "C:\Users\{$user_account}\AppData\Roaming\Mozilla\Firefox\Profiles\{$mozilla_profile_number}.default\places.sqlite":
+#
+# }
+#
+# exec { "add-chrome-history":
+# command => "",
+# }
+#
+# file { 'add-chrome-history':
+# ensure => 'file',
+# ### path => "C:/Users/$user_account/AppData/Local/Google/Chrome/User Data/Default/History",
+# path => "C:/Users/$user_account/Desktop/test_file_here.txt",
+# ### content => template('internet_history_chrome/insert_history.erb')
+# # content => inline_template('evidence_windows_cybercrime_internet_history_chrome/insert_history.erb')
+# source => 'puppet:///modules/internet_history_chrome/chrome_history_file'
+# }
+
+class { 'internet_history_chrome':
+ user_account => 'vagrant',
+ history_file_name => 'History',
+}
+
+# file { "add-chrome-history-source":
+# path => "C:/Users/$user_account/Desktop/History",
+# ensure => 'file',
+# # source => "puppet:///modules/internet_history_chrome/chrome_history_file",
+# # source => "puppet:///modules/file_transfer_storage_module/$chrome_history_file_name",
+# source => "puppet:///modules/file_transfer_storage_module/History",
+# }
+
+# file { "add-chrome-history-source":
+# path => "C:/Users/$user_account/Desktop/test_file_here_source.txt",
+# ensure => 'file',
+# # source => "puppet:///modules/internet_history_chrome/chrome_history_file",
+# source => "puppet:///modules/internet_history_chrome/History_test_source_move",
+# }
+
+# file { 'add-chrome-history-content':
+# ensure => 'file',
+# path => "C:/Users/$user_account/Desktop/test_file_here_content.txt",
+# content => inline_template('evidence_windows_cybercrime_internet_history_chrome/insert_history.erb')
+# }
\ No newline at end of file
diff --git a/modules/forensics/windows/internet_artifacts/internet_history_chrome/manifests/init.pp b/modules/forensics/windows/internet_artifacts/internet_history_chrome/manifests/init.pp
new file mode 100644
index 000000000..31c5f6353
--- /dev/null
+++ b/modules/forensics/windows/internet_artifacts/internet_history_chrome/manifests/init.pp
@@ -0,0 +1,23 @@
+class internet_history_chrome ($user_account, $history_file_name) {
+ # notice("THE MODULE internet history chrome was LOADED #2")
+
+ # $user_account = 'vagrant'
+ # $url_paths = ["https://www.offensive-security.com/backtrack/exploit-db-updates"]
+
+ # file { "C:\Users\{$user_account}\AppData\Roaming\Mozilla\Firefox\Profiles\{$mozilla_profile_number}.default\places.sqlite":
+ #
+ # }
+
+ # exec { "add-chrome-history":
+ # command => "",
+ # }
+
+ file { 'add-chrome-history':
+ ensure => 'present',
+ path => "C:/Users/$user_account/AppData/Local/Google/Chrome/User Data/Default/History",
+ # path => 'C:/Users/vagrant/AppData/Local/Google/Chrome/User Data/Default/History',
+ # content => template('internet_history_chrome/insert_history.erb')
+ # content => inline_template('evidence_windows_cybercrime_internet_history_chrome/insert_history.erb')
+ source => "puppet:///modules/file_transfer_storage_module/History",
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/internet_artifacts/internet_history_chrome/secgen_metadata.xml b/modules/forensics/windows/internet_artifacts/internet_history_chrome/secgen_metadata.xml
new file mode 100644
index 000000000..e6ee2638a
--- /dev/null
+++ b/modules/forensics/windows/internet_artifacts/internet_history_chrome/secgen_metadata.xml
@@ -0,0 +1,51 @@
+
+
+
+ Internet history chrome
+ Jason Keighley
+ Apache v2
+ Create internet history for the Google Chrome browser
+
+ internet_artifacts
+ windows
+
+
+
+
+ chrome_history_file_name
+
+
+
+
+ 100
+
+
+ 3rd july 2013 15:16:20
+
+
+ 5th june 2015 15:16:20
+
+
+ 10
+
+
+ 4th july 2013 12:00:00
+
+
+ 4th july 2013 15:00:00
+
+
+
+
+
+ Powershell install local
+
+
+ Google Chrome install
+
+
+ Store files for transfer
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/internet_artifacts/internet_history_chrome/templates/insert_history.erb b/modules/forensics/windows/internet_artifacts/internet_history_chrome/templates/insert_history.erb
new file mode 100644
index 000000000..fe32e704a
--- /dev/null
+++ b/modules/forensics/windows/internet_artifacts/internet_history_chrome/templates/insert_history.erb
@@ -0,0 +1,5 @@
+<%require 'json' -%>
+<%require 'base64' -%>
+<%$parsed_inputs = JSON.parse(@json_inputs) -%>
+<%$chrome_history_file_binary = $parsed_inputs['chrome_history_file'].first -%>
+<%=Base64.decode64($chrome_history_file_binary) %>
\ No newline at end of file
diff --git a/modules/forensics/windows/internet_artifacts/internet_history_chrome/templates/insert_history.erb.old b/modules/forensics/windows/internet_artifacts/internet_history_chrome/templates/insert_history.erb.old
new file mode 100644
index 000000000..523fba220
--- /dev/null
+++ b/modules/forensics/windows/internet_artifacts/internet_history_chrome/templates/insert_history.erb.old
@@ -0,0 +1,17 @@
+<% #puts "Hello World!" %>
+<%
+ require 'sqlite3'
+
+ local_user = 'vagrant'
+ chrome_user = 'Default'
+
+ SQLite3::Database.new( "C:\\Users\\#{local_user}\\AppData\\Local\\Google\\Chrome\\User Data\\#{chrome_user}\\History" ) do |db|
+ db.execute( "select * from urls" ) do |row|
+ p row
+ end
+ db.execute(
+ "INSERT INTO urls(id, url, title, visit_count, typed_count, last_visit_time, hidden, favicon_id)
+VALUES ('37', 'test_url', 'test_title', '1', '1', '1', '0', '0');"
+ )
+ end
+%>
\ No newline at end of file
diff --git a/modules/forensics/windows/prefetch/insert_prefetch_file/insert_prefetch_file.pp b/modules/forensics/windows/prefetch/insert_prefetch_file/insert_prefetch_file.pp
new file mode 100644
index 000000000..2caa06701
--- /dev/null
+++ b/modules/forensics/windows/prefetch/insert_prefetch_file/insert_prefetch_file.pp
@@ -0,0 +1,7 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$prefetch_file_name=$secgen_parameters['prefetch_file_name']
+
+class { 'insert_prefetch_file':
+ prefetch_file_name => $prefetch_file_name[0],
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/prefetch/insert_prefetch_file/manifests/init.pp b/modules/forensics/windows/prefetch/insert_prefetch_file/manifests/init.pp
new file mode 100644
index 000000000..24d6a6dd2
--- /dev/null
+++ b/modules/forensics/windows/prefetch/insert_prefetch_file/manifests/init.pp
@@ -0,0 +1,13 @@
+class insert_prefetch_file ($prefetch_file_name) {
+ file { 'ensure_prefetch_directory_exists_test':
+ path => "$system32\\Prefetch",
+ ensure => directory,
+ }
+
+ file { 'add_prefetch_file':
+ path => "$system32\\Prefetch\\$prefetch_file_name",
+ ensure => 'file',
+ source => "puppet:///modules/file_transfer_storage_module/$prefetch_file_name",
+ source_permissions => ignore,
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/prefetch/insert_prefetch_file/secgen_metadata.xml b/modules/forensics/windows/prefetch/insert_prefetch_file/secgen_metadata.xml
new file mode 100644
index 000000000..f495cdacc
--- /dev/null
+++ b/modules/forensics/windows/prefetch/insert_prefetch_file/secgen_metadata.xml
@@ -0,0 +1,32 @@
+
+
+
+ Add prefetch file
+ Jason Keighley
+ Apache v2
+ Insert prefetch files
+
+ prefetch
+ forensic_artefact
+ windows
+
+
+
+
+ prefetch_file_name
+
+
+
+
+
+
+
+
+
+
+ Store files for transfer
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/registry/add_registry_keys/add_registry_keys.pp b/modules/forensics/windows/registry/add_registry_keys/add_registry_keys.pp
new file mode 100644
index 000000000..b05d24169
--- /dev/null
+++ b/modules/forensics/windows/registry/add_registry_keys/add_registry_keys.pp
@@ -0,0 +1,7 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$key_locations=$secgen_parameters['key_locations']
+
+class { 'add_registry_keys':
+ key_locations => $key_locations[0],
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/registry/add_registry_keys/manifests/init.pp b/modules/forensics/windows/registry/add_registry_keys/manifests/init.pp
new file mode 100644
index 000000000..8f92b3a04
--- /dev/null
+++ b/modules/forensics/windows/registry/add_registry_keys/manifests/init.pp
@@ -0,0 +1,7 @@
+class add_registry_keys ($key_locations) {
+ # $key_locations.each | Integer $index, String $key_location | {
+ registry_key { $key_locations:
+ ensure => present,
+ }
+ # }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/registry/add_registry_keys/secgen_metadata.xml b/modules/forensics/windows/registry/add_registry_keys/secgen_metadata.xml
new file mode 100644
index 000000000..f97537ddd
--- /dev/null
+++ b/modules/forensics/windows/registry/add_registry_keys/secgen_metadata.xml
@@ -0,0 +1,27 @@
+
+
+
+ Add registry keys
+ Jason Keighley
+ Apache v2
+ Add registry keys to Windows registry
+
+ registry
+ windows
+
+
+
+
+ key_locations
+
+
+ HKLM\System\CurrentControlSet\Services\Puppet
+
+
+
+ Registry library
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/registry/add_registry_values/add_registry_values.pp b/modules/forensics/windows/registry/add_registry_values/add_registry_values.pp
new file mode 100644
index 000000000..8871fd80d
--- /dev/null
+++ b/modules/forensics/windows/registry/add_registry_values/add_registry_values.pp
@@ -0,0 +1,11 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$key_locations=$secgen_parameters['key_locations']
+$key_value_type=$secgen_parameters['key_value_type']
+$key_value=$secgen_parameters['key_value']
+
+class { 'add_registry_values':
+ key_location => $key_locations[0],
+ key_value_type => $key_value_type[0],
+ key_value => $key_value[0],
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/registry/add_registry_values/manifests/init.pp b/modules/forensics/windows/registry/add_registry_values/manifests/init.pp
new file mode 100644
index 000000000..14ec1d9eb
--- /dev/null
+++ b/modules/forensics/windows/registry/add_registry_values/manifests/init.pp
@@ -0,0 +1,14 @@
+class add_registry_values($key_location, $key_value_type, $key_value) {
+
+ # $key_locations.each | Integer $index, String $key_location | {
+ registry_key { $key_location:
+ ensure => present,
+ }
+
+ registry_value { $key_location:
+ ensure => present,
+ type => $key_value_type,
+ data => $key_value,
+ }
+ # }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/registry/add_registry_values/secgen_metadata.xml b/modules/forensics/windows/registry/add_registry_values/secgen_metadata.xml
new file mode 100644
index 000000000..823669ce2
--- /dev/null
+++ b/modules/forensics/windows/registry/add_registry_values/secgen_metadata.xml
@@ -0,0 +1,37 @@
+
+
+
+ Add registry values
+ Jason Keighley
+ Apache v2
+ Add registry values to Windows registry
+
+ registry
+ windows
+
+
+
+
+ key_locations
+ key_value_type
+ key_value
+
+
+ HKLM\System\CurrentControlSet\Services\Puppet
+
+
+
+ string
+
+
+
+ String to demonstrate the module
+
+
+
+ Registry library
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_all_main_times/change_timestamp_all_main_times.pp b/modules/forensics/windows/timestamps/change_timestamp_all_main_times/change_timestamp_all_main_times.pp
new file mode 100644
index 000000000..d11eb1b0c
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_all_main_times/change_timestamp_all_main_times.pp
@@ -0,0 +1,14 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$all_times_file_path=$secgen_parameters['all_times_file_path'][0]
+$all_times_date=$secgen_parameters['all_times_date'][0]
+
+file { 'ensure_path_present':
+ path => $all_times_file_path,
+ ensure => 'present'
+}
+
+class { 'change_timestamp_all_main_times':
+ file_path => $all_times_file_path,
+ file_time => $all_times_date
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_all_main_times/manifests/init.pp b/modules/forensics/windows/timestamps/change_timestamp_all_main_times/manifests/init.pp
new file mode 100644
index 000000000..2bf1c3ab4
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_all_main_times/manifests/init.pp
@@ -0,0 +1,16 @@
+class change_timestamp_all_main_times ($file_path, $file_time) {
+ exec { 'change_last_write_time':
+ command => "$((ls ${file_path}).LastWriteTime = '${file_time}')",
+ provider => powershell,
+ }
+
+ exec { 'change_last_access_time':
+ command => "$((ls ${file_path}).LastAccessTime = '${file_time}')",
+ provider => powershell,
+ }
+
+ exec { 'change_creation_time':
+ command => "$((ls ${file_path}).CreationTime = '${file_time}')",
+ provider => powershell,
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_all_main_times/secgen_metadata.xml b/modules/forensics/windows/timestamps/change_timestamp_all_main_times/secgen_metadata.xml
new file mode 100644
index 000000000..33b2aee41
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_all_main_times/secgen_metadata.xml
@@ -0,0 +1,32 @@
+
+
+
+ Change timestamp all main times
+ Jason Keighley
+ Apache v2
+ Change timestamp last write time
+
+ timestamps
+ windows
+
+
+
+
+ all_times_file_path
+ all_times_date
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+
+
+
+
+ Powershell install local
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_creation_time/change_timestamp_creation_time.pp b/modules/forensics/windows/timestamps/change_timestamp_creation_time/change_timestamp_creation_time.pp
new file mode 100644
index 000000000..8e4ce3f8b
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_creation_time/change_timestamp_creation_time.pp
@@ -0,0 +1,14 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$creation_time_file_path=$secgen_parameters['creation_time_file_path'][0]
+$creation_time_date=$secgen_parameters['creation_time_date'][0]
+
+file { 'ensure_path_present':
+ path => $creation_time_file_path,
+ ensure => 'present'
+}
+
+class { 'change_timestamp_creation_time':
+ file_path => $creation_time_file_path,
+ file_time => $creation_time_date
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_creation_time/manifests/init.pp b/modules/forensics/windows/timestamps/change_timestamp_creation_time/manifests/init.pp
new file mode 100644
index 000000000..ffd21a20e
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_creation_time/manifests/init.pp
@@ -0,0 +1,9 @@
+# $file_path = 'C:\Users\vagrant\Desktop\Hello.txt'
+# $file_time = '11/25/2000 11:12:13'
+
+class change_timestamp_creation_time ($file_path, $file_time) {
+ exec { 'change_creation_time':
+ command => "$((ls ${file_path}).CreationTime = '${file_time}')",
+ provider => powershell,
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_creation_time/secgen_metadata.xml b/modules/forensics/windows/timestamps/change_timestamp_creation_time/secgen_metadata.xml
new file mode 100644
index 000000000..baafdf0f3
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_creation_time/secgen_metadata.xml
@@ -0,0 +1,32 @@
+
+
+
+ Change timestamp creation time
+ Jason Keighley
+ Apache v2
+ Change timestamp creation time
+
+ timestamps
+ windows
+
+
+
+
+ creation_time_file_path
+ creation_time_date
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+
+
+
+
+ Powershell install local
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_last_access_time/change_timestamp_last_access_time.pp b/modules/forensics/windows/timestamps/change_timestamp_last_access_time/change_timestamp_last_access_time.pp
new file mode 100644
index 000000000..3281b50ec
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_last_access_time/change_timestamp_last_access_time.pp
@@ -0,0 +1,14 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$last_access_time_file_path=$secgen_parameters['last_access_time_file_path'][0]
+$last_access_time_date=$secgen_parameters['last_access_time_date'][0]
+
+file { 'ensure_path_present':
+ path => $last_access_time_file_path,
+ ensure => 'present'
+}
+
+class { 'change_timestamp_last_access_time':
+ file_path => $last_access_time_file_path,
+ file_time => $last_access_time_date
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_last_access_time/manifests/init.pp b/modules/forensics/windows/timestamps/change_timestamp_last_access_time/manifests/init.pp
new file mode 100644
index 000000000..1ee31ce98
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_last_access_time/manifests/init.pp
@@ -0,0 +1,9 @@
+# $file_path = 'C:\Users\vagrant\Desktop\Hello.txt'
+# $file_time = '11/25/2000 11:12:13'
+
+class change_timestamp_last_access_time ($file_path, $file_time) {
+ exec { 'change_last_access_time':
+ command => "$((ls ${file_path}).LastAccessTime = '${file_time}')",
+ provider => powershell,
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_last_access_time/secgen_metadata.xml b/modules/forensics/windows/timestamps/change_timestamp_last_access_time/secgen_metadata.xml
new file mode 100644
index 000000000..1b5eeac0f
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_last_access_time/secgen_metadata.xml
@@ -0,0 +1,32 @@
+
+
+
+ Change timestamp last access time
+ Jason Keighley
+ Apache v2
+ Change timestamp last access time
+
+ timestamps
+ windows
+
+
+
+
+ last_access_time_file_path
+ last_access_time_date
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+
+
+
+
+ Powershell install local
+
+
+
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_last_write_time/change_timestamp_last_write_time.pp b/modules/forensics/windows/timestamps/change_timestamp_last_write_time/change_timestamp_last_write_time.pp
new file mode 100644
index 000000000..e9b0b98cf
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_last_write_time/change_timestamp_last_write_time.pp
@@ -0,0 +1,14 @@
+$json_inputs = base64('decode', $::base64_inputs)
+$secgen_parameters=parsejson($json_inputs)
+$last_write_time_file_path=$secgen_parameters['last_write_time_file_path'][0]
+$last_write_time_date=$secgen_parameters['last_write_time_date'][0]
+
+file { 'ensure_path_present':
+ path => $last_write_time_file_path,
+ ensure => 'present'
+}
+
+class { 'change_timestamp_last_write_time':
+ file_path => $last_write_time_file_path,
+ file_time => $last_write_time_date
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_last_write_time/manifests/init.pp b/modules/forensics/windows/timestamps/change_timestamp_last_write_time/manifests/init.pp
new file mode 100644
index 000000000..e6d31faf0
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_last_write_time/manifests/init.pp
@@ -0,0 +1,9 @@
+# $file_path = 'C:\Users\vagrant\Desktop\Hello.txt'
+# $file_time = '11/25/2000 11:12:13'
+
+class change_timestamp_last_write_time ($file_path, $file_time) {
+ exec { 'change_last_write_time':
+ command => "$((ls ${file_path}).LastWriteTime = '${file_time}')",
+ provider => powershell,
+ }
+}
\ No newline at end of file
diff --git a/modules/forensics/windows/timestamps/change_timestamp_last_write_time/secgen_metadata.xml b/modules/forensics/windows/timestamps/change_timestamp_last_write_time/secgen_metadata.xml
new file mode 100644
index 000000000..57179a9bf
--- /dev/null
+++ b/modules/forensics/windows/timestamps/change_timestamp_last_write_time/secgen_metadata.xml
@@ -0,0 +1,32 @@
+
+
+
+ Change timestamp last write time
+ Jason Keighley
+ Apache v2
+ Change timestamp last write time
+
+ timestamps
+ windows
+
+
+
+
+ last_write_time_file_path
+ last_write_time_date
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+
+
+
+
+ Powershell install local
+
+
+
\ No newline at end of file
diff --git a/modules/generators/forensics/illegal_images/select_cat_image/manifests/.no_puppet b/modules/generators/forensics/illegal_images/select_cat_image/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/illegal_images/select_cat_image/secgen_local/local.rb b/modules/generators/forensics/illegal_images/select_cat_image/secgen_local/local.rb
new file mode 100644
index 000000000..1b4a14414
--- /dev/null
+++ b/modules/generators/forensics/illegal_images/select_cat_image/secgen_local/local.rb
@@ -0,0 +1,32 @@
+#!/usr/bin/ruby
+require_relative '../../../../../../lib/objects/local_string_generator.rb'
+require 'date'
+
+class SelectCatImage < StringGenerator
+ attr_accessor :selected_image_path
+
+ def initialize
+ super
+ self.module_name = 'Random cat image selector'
+ self.selected_image_path = Dir["#{ILLEGAL_IMAGES_DIR}/cats/*"].sample
+ end
+
+ def get_options_array
+ super + [['--selected_image_path', GetoptLong::OPTIONAL_ARGUMENT]]
+ end
+
+ def process_options(opt, arg)
+ super
+ case opt
+ when '--selected_image_path'
+ self.selected_image_path << arg;
+ end
+ end
+
+ def generate
+ file_contents = File.binread(self.selected_image_path)
+ self.outputs << Base64.strict_encode64(file_contents)
+ end
+end
+
+SelectCatImage.new.run
\ No newline at end of file
diff --git a/modules/generators/forensics/illegal_images/select_cat_image/secgen_metadata.xml b/modules/generators/forensics/illegal_images/select_cat_image/secgen_metadata.xml
new file mode 100644
index 000000000..f081f02e7
--- /dev/null
+++ b/modules/generators/forensics/illegal_images/select_cat_image/secgen_metadata.xml
@@ -0,0 +1,19 @@
+
+
+
+ Select illegal image cat
+ Jason Keighley
+ Apache v2
+ Selects an illegal image (cat image) to use
+
+ illegal_image
+ illegal_image_generator
+ illegal_image_selector
+ windows
+
+
+
+ illegal_image
+
\ No newline at end of file
diff --git a/modules/generators/forensics/illegal_images/select_cat_image/select_cat_image.pp b/modules/generators/forensics/illegal_images/select_cat_image/select_cat_image.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/illegal_images/select_cat_image_path/manifests/.no_puppet b/modules/generators/forensics/illegal_images/select_cat_image_path/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/illegal_images/select_cat_image_path/secgen_local/local.rb b/modules/generators/forensics/illegal_images/select_cat_image_path/secgen_local/local.rb
new file mode 100644
index 000000000..35ed55081
--- /dev/null
+++ b/modules/generators/forensics/illegal_images/select_cat_image_path/secgen_local/local.rb
@@ -0,0 +1,32 @@
+#!/usr/bin/ruby
+require_relative '../../../../../../lib/objects/local_string_generator.rb'
+require 'date'
+
+class SelectCatImagePath < StringGenerator
+ attr_accessor :selected_image_path
+
+ def initialize
+ super
+ self.module_name = 'Random cat image selector'
+ self.selected_image_path = Dir["#{ILLEGAL_IMAGES_DIR}/cats/*"].sample
+ end
+
+ def get_options_array
+ super + [['--selected_image_path', GetoptLong::OPTIONAL_ARGUMENT]]
+ end
+
+ def process_options(opt, arg)
+ super
+ case opt
+ when '--selected_image_path'
+ self.selected_image_path << arg;
+ end
+ end
+
+ def generate
+ selected_image_path = Base64.strict_encode64(self.selected_image_path)
+ self.outputs << selected_image_path
+ end
+end
+
+SelectCatImagePath.new.run
\ No newline at end of file
diff --git a/modules/generators/forensics/illegal_images/select_cat_image_path/secgen_metadata.xml b/modules/generators/forensics/illegal_images/select_cat_image_path/secgen_metadata.xml
new file mode 100644
index 000000000..49ea41934
--- /dev/null
+++ b/modules/generators/forensics/illegal_images/select_cat_image_path/secgen_metadata.xml
@@ -0,0 +1,19 @@
+
+
+
+ Select illegal image cat paths
+ Jason Keighley
+ Apache v2
+ Selects an illegal image (cat image) path to use
+
+ illegal_image
+ illegal_image_generator
+ illegal_image_selector
+ windows
+
+
+
+ illegal_image
+
\ No newline at end of file
diff --git a/modules/generators/forensics/illegal_images/select_cat_image_path/select_cat_image_path.pp b/modules/generators/forensics/illegal_images/select_cat_image_path/select_cat_image_path.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/chrome_history_file_generator.pp b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/chrome_history_file_generator.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/manifests/.no_puppet b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/secgen_local/local.rb b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/secgen_local/local.rb
new file mode 100644
index 000000000..dbb5ba44e
--- /dev/null
+++ b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/secgen_local/local.rb
@@ -0,0 +1,125 @@
+#!/usr/bin/ruby
+require_relative '../../../../../../lib/objects/local_string_generator.rb'
+require 'date'
+require 'sqlite3'
+require 'fileutils'
+
+class ChromeHistoryFileGenerator < StringGenerator
+ attr_accessor :number_of_generic_urls
+ attr_accessor :generic_urls_start_time
+ attr_accessor :generic_urls_end_time
+
+ attr_accessor :number_of_cybercrime_urls
+ attr_accessor :cybercrime_urls_start_time
+ attr_accessor :cybercrime_urls_end_time
+
+ def initialize
+ super
+ self.module_name = 'Chrome history file generator'
+ # self.history_urls = ''
+ self.number_of_generic_urls = ''
+ self.generic_urls_start_time = ''
+ self.generic_urls_end_time = ''
+
+ self.number_of_cybercrime_urls = ''
+ self.cybercrime_urls_start_time = ''
+ self.cybercrime_urls_end_time = ''
+ end
+
+ def get_options_array
+ super + [['--number_of_generic_urls', GetoptLong::REQUIRED_ARGUMENT],
+ ['--generic_urls_start_time', GetoptLong::REQUIRED_ARGUMENT],
+ ['--generic_urls_end_time', GetoptLong::REQUIRED_ARGUMENT],
+ ['--number_of_cybercrime_urls', GetoptLong::REQUIRED_ARGUMENT],
+ ['--cybercrime_urls_start_time', GetoptLong::REQUIRED_ARGUMENT],
+ ['--cybercrime_urls_end_time', GetoptLong::REQUIRED_ARGUMENT]]
+ end
+
+ def process_options(opt, arg)
+ super
+ case opt
+ when '--number_of_generic_urls'
+ self.number_of_generic_urls << arg;
+ when '--generic_urls_start_time'
+ self.generic_urls_start_time << arg;
+ when '--generic_urls_end_time'
+ self.generic_urls_end_time << arg;
+ when '--number_of_cybercrime_urls'
+ self.number_of_cybercrime_urls << arg;
+ when '--cybercrime_urls_start_time'
+ self.cybercrime_urls_start_time << arg;
+ when '--cybercrime_urls_end_time'
+ self.cybercrime_urls_end_time << arg;
+ end
+ end
+
+
+ def generate
+ local_user = 'vagrant'
+ chrome_user = 'Default'
+ chrome_history_file_tmp_path = "#{FILE_TRANSFER_STORAGE_MODULE_DIR}/files/History"
+
+ urls = Hash.new
+
+ # self.number_of_generic_urls = 100
+ # self.generic_urls_start_time = '3rd july 2013 15:16:20'
+ # self.generic_urls_end_time = '5th june 2015 15:16:20'
+ #
+ # self.number_of_cybercrime_urls = 10
+ # self.cybercrime_urls_start_time = '4th july 2013 12:00:00'
+ # self.cybercrime_urls_end_time = '4th july 2013 15:00:00'
+
+ # Generic filler urls
+ generic_urls = File.readlines("#{URLLISTS_DIR}/generic_urls").sample(self.number_of_generic_urls.to_i)
+
+ # Crime url start
+ cybercrime_urls = File.readlines("#{URLLISTS_DIR}/cybercrime_urls").sample(self.number_of_cybercrime_urls.to_i)
+
+ generic_urls.each do |url|
+ date_start = Time.parse(self.generic_urls_start_time)
+ date_end = Time.parse(self.generic_urls_end_time)
+
+ urls[url] = {
+ :url => url,
+ :title => url[/\/\/.*\//].gsub(/(\/)/,''),
+ :visit_count => rand(0..100).to_i,
+ :typed_count => rand(0..100).to_i,
+ :last_visit_time => (date_start.to_f + rand * (date_end.to_f - date_start.to_f)).to_i,
+ :hidden => '0',
+ :favicon_id => '0'
+ }
+ end
+
+ cybercrime_urls.each do |url|
+ date_start = Time.parse(self.generic_urls_start_time)
+ date_end = Time.parse(self.generic_urls_end_time)
+
+ urls[url] = {
+ :url => url,
+ :title => url[/\/\/.*\//].gsub(/(\/)/,''),
+ :visit_count => rand(0..100).to_i,
+ :typed_count => rand(0..100).to_i,
+ :last_visit_time => (date_start.to_f + rand * (date_end.to_f - date_start.to_f)).to_i,
+ :hidden => '0',
+ :favicon_id => '0'
+ }
+ end
+
+ history_urls = Hash[urls.to_a.shuffle]
+
+ FileUtils.cp("#{INTERNET_BROWSER_FILES_DIR}/chrome_history_file.source", chrome_history_file_tmp_path)
+
+ database = SQLite3::Database.new( chrome_history_file_tmp_path ) do |db|
+ history_urls.each_value do |details|
+ db.execute(
+ "INSERT INTO urls(url, title, visit_count, typed_count, last_visit_time, hidden, favicon_id)
+VALUES ('#{details[:url]}', '#{details[:title]}', '#{details[:visit_count]}', '#{details[:typed_count]}', '#{details[:last_visit_time]}', '#{details[:hidden]}', '#{details[:favicon_id]}');"
+ )
+ end
+ end
+
+ self.outputs << Base64.strict_encode64(chrome_history_file_tmp_path).split(/[\\\/]/).last
+ end
+end
+
+ChromeHistoryFileGenerator.new.run
\ No newline at end of file
diff --git a/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/secgen_metadata.xml b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/secgen_metadata.xml
new file mode 100644
index 000000000..d53cb7d26
--- /dev/null
+++ b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/secgen_metadata.xml
@@ -0,0 +1,28 @@
+
+
+
+ Chrome history file generator
+ Jason Keighley
+ Apache v2
+ Randomly selects urls from pool of crime urls dependent on inputted crime type.
+
+ internet_history_file_generator
+ windows
+
+
+
+
+
+
+ number_of_generic_urls
+ generic_urls_start_time
+ generic_urls_end_time
+
+ number_of_cybercrime_urls
+ cybercrime_urls_start_time
+ cybercrime_urls_end_time
+
+ urls
+
\ No newline at end of file
diff --git a/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/templates/History.source b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/templates/History.source
new file mode 100644
index 000000000..85df9987b
Binary files /dev/null and b/modules/generators/forensics/internet_artifacts/chrome_history_file_generator/templates/History.source differ
diff --git a/modules/generators/forensics/internet_artifacts/url_generator/manifests/.no_puppet b/modules/generators/forensics/internet_artifacts/url_generator/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/internet_artifacts/url_generator/secgen_local/local.rb b/modules/generators/forensics/internet_artifacts/url_generator/secgen_local/local.rb
new file mode 100644
index 000000000..2b2d5328e
--- /dev/null
+++ b/modules/generators/forensics/internet_artifacts/url_generator/secgen_local/local.rb
@@ -0,0 +1,54 @@
+#!/usr/bin/ruby
+require_relative '../../../../../../lib/objects/local_string_generator.rb'
+require 'time'
+require 'json'
+
+class UrlGenerator < StringGenerator
+ attr_accessor :number_of_generic_urls
+ attr_accessor :generic_urls_start_time
+ attr_accessor :generic_urls_end_time
+
+ def initialize
+ super
+ self.module_name = 'Url generator'
+ # self.number_of_generic_urls = ''
+ # self.generic_urls_start_time = ''
+ # self.generic_urls_end_time = ''
+ # self.number_of_cybercrime_urls = ''
+ # self.cybercrime_urls_start_time = ''
+ end
+
+ def generate
+ urls = Hash.new
+
+ self.number_of_generic_urls = 10
+ self.generic_urls_start_time = '3rd july 2013 15:16:20'
+ self.generic_urls_end_time = '5rd june 2015 15:16:20'
+
+ # Generic filler urls
+ generic_urls = File.readlines("#{URLLISTS_DIR}/generic_urls").sample(self.number_of_generic_urls.to_int)
+
+ # Crime url start
+ # cybercrime_urls = File.readlines("#{URLLISTS_DIR}/cybercrime_urls").sample(self.number_of_cybercrime_urls).chomp
+
+ generic_urls.each do |url|
+ date_start = Time.parse(self.generic_urls_start_time)
+ date_end = Time.parse(self.generic_urls_end_time)
+
+ urls[url] = {
+ :url => url,
+ :title => url[/\/\/.*\//].gsub(/(\/)/),
+ :visit_count => rand,
+ :typed_count => rand,
+ :last_visit_time => date_start.to_f + rand * (date_end.to_f - date_start.to_f),
+ :hidden => '0',
+ :favicon_id => '0'
+ }
+ end
+
+ # self.outputs << Base64.strict_encode64(urls.to_s)
+ self.outputs << Base64.encode64(urls)
+ end
+end
+
+UrlGenerator.new.run
\ No newline at end of file
diff --git a/modules/generators/forensics/internet_artifacts/url_generator/secgen_metadata.xml b/modules/generators/forensics/internet_artifacts/url_generator/secgen_metadata.xml
new file mode 100644
index 000000000..0f3991c54
--- /dev/null
+++ b/modules/generators/forensics/internet_artifacts/url_generator/secgen_metadata.xml
@@ -0,0 +1,21 @@
+
+
+
+ Url generator
+ Jason Keighley
+ Apache v2
+ Randomly selects urls from pool of crime urls dependent on inputted crime type.
+
+ url_generator
+ windows
+
+
+
+
+ number_of_generic_urls
+ number_of_cybercrime_urls
+
+ urls
+
\ No newline at end of file
diff --git a/modules/generators/forensics/internet_artifacts/url_generator/url_generator.pp b/modules/generators/forensics/internet_artifacts/url_generator/url_generator.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/prefetch/select_prefetch_file/manifests/.no_puppet b/modules/generators/forensics/prefetch/select_prefetch_file/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/prefetch/select_prefetch_file/secgen_local/local.rb b/modules/generators/forensics/prefetch/select_prefetch_file/secgen_local/local.rb
new file mode 100644
index 000000000..75beb75ed
--- /dev/null
+++ b/modules/generators/forensics/prefetch/select_prefetch_file/secgen_local/local.rb
@@ -0,0 +1,19 @@
+#!/usr/bin/ruby
+require_relative '../../../../../../lib/objects/local_string_generator.rb'
+require 'date'
+
+class SelectPrefetchFile < StringGenerator
+ attr_accessor :selected_file_path
+
+ def initialize
+ super
+ self.module_name = 'Random cat image selector'
+ self.selected_file_path = Dir["#{FORENSIC_ARTEFACTS_DIR}/prefetch/*"].sample
+ end
+
+ def generate
+ self.outputs << Base64.strict_encode64(self.selected_file_path)
+ end
+end
+
+SelectPrefetchFile.new.run
\ No newline at end of file
diff --git a/modules/generators/forensics/prefetch/select_prefetch_file/secgen_metadata.xml b/modules/generators/forensics/prefetch/select_prefetch_file/secgen_metadata.xml
new file mode 100644
index 000000000..bab3e70f5
--- /dev/null
+++ b/modules/generators/forensics/prefetch/select_prefetch_file/secgen_metadata.xml
@@ -0,0 +1,18 @@
+
+
+
+ Select prefetch file paths
+ Jason Keighley
+ Apache v2
+ Select prefetch file paths from prefetch resources
+
+ prefetch
+ forensic_artefact
+ windows
+
+
+
+ prefetch
+
\ No newline at end of file
diff --git a/modules/generators/forensics/prefetch/select_prefetch_file/select_prefetch_file.pp b/modules/generators/forensics/prefetch/select_prefetch_file/select_prefetch_file.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/time/generate_random_time/generate_random_time.pp b/modules/generators/forensics/time/generate_random_time/generate_random_time.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/time/generate_random_time/manifests/.no_puppet b/modules/generators/forensics/time/generate_random_time/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/generators/forensics/time/generate_random_time/secgen_local/local.rb b/modules/generators/forensics/time/generate_random_time/secgen_local/local.rb
new file mode 100644
index 000000000..7dcfba6ce
--- /dev/null
+++ b/modules/generators/forensics/time/generate_random_time/secgen_local/local.rb
@@ -0,0 +1,40 @@
+#!/usr/bin/ruby
+require_relative '../../../../../../lib/objects/local_string_generator.rb'
+require 'time'
+
+class GenerateRandomTime < StringGenerator
+ attr_accessor :date_start
+ attr_accessor :date_end
+
+ def initialize
+ super
+ self.module_name = 'Random date generator'
+ self.date_start = ''
+ self.date_end = ''
+ end
+
+ def get_options_array
+ super + [['--date_start', GetoptLong::OPTIONAL_ARGUMENT],
+ ['--date_end', GetoptLong::OPTIONAL_ARGUMENT]]
+ end
+
+ def process_options(opt, arg)
+ super
+ case opt
+ when '--date_start'
+ self.date_start << arg;
+ when '--date_end'
+ self.date_end << arg;
+ end
+ end
+
+ def generate
+ self.date_start.empty? ? self.date_start = 0.0: self.date_start = Time.parse(self.date_start)
+ self.date_end.empty? ? self.date_end = Time.now: self.date_end = Time.parse(self.date_end)
+
+ random_date = Time.at(self.date_start.to_f + rand * (self.date_end.to_f - self.date_start.to_f))
+ self.outputs << random_date.strftime("%m/%d/%Y %H:%M:%S")
+ end
+end
+
+GenerateRandomTime.new.run
\ No newline at end of file
diff --git a/modules/generators/forensics/time/generate_random_time/secgen_metadata.xml b/modules/generators/forensics/time/generate_random_time/secgen_metadata.xml
new file mode 100644
index 000000000..09037d0ec
--- /dev/null
+++ b/modules/generators/forensics/time/generate_random_time/secgen_metadata.xml
@@ -0,0 +1,21 @@
+
+
+
+ Generate random time
+ Jason Keighley
+ Apache v2
+ Generates a random time between the given dates
+
+ time_generator
+ windows
+
+
+
+
+ date_start
+ date_end
+
+ time
+
\ No newline at end of file
diff --git a/modules/services/unix/database/mysql/lib/puppet/type/mysql_database.rb b/modules/services/unix/database/mysql/lib/puppet/type/mysql_database.rb
index 1f94d5f88..53f7f91e1 100644
--- a/modules/services/unix/database/mysql/lib/puppet/type/mysql_database.rb
+++ b/modules/services/unix/database/mysql/lib/puppet/type/mysql_database.rb
@@ -3,7 +3,7 @@
ensurable
- autorequire(:file) { '/root/.my.cnf' }
+ autorequire(:utility) { '/root/.my.cnf' }
autorequire(:class) { 'mysql::server' }
newparam(:name, :namevar => true) do
diff --git a/modules/services/unix/database/mysql/lib/puppet/type/mysql_grant.rb b/modules/services/unix/database/mysql/lib/puppet/type/mysql_grant.rb
index 999100a0c..b7d591d5f 100644
--- a/modules/services/unix/database/mysql/lib/puppet/type/mysql_grant.rb
+++ b/modules/services/unix/database/mysql/lib/puppet/type/mysql_grant.rb
@@ -3,7 +3,7 @@
@doc = "Manage a MySQL user's rights."
ensurable
- autorequire(:file) { '/root/.my.cnf' }
+ autorequire(:utility) { '/root/.my.cnf' }
autorequire(:mysql_user) { self[:user] }
def initialize(*args)
diff --git a/modules/services/unix/database/mysql/lib/puppet/type/mysql_plugin.rb b/modules/services/unix/database/mysql/lib/puppet/type/mysql_plugin.rb
index e8279209f..9c97cd089 100644
--- a/modules/services/unix/database/mysql/lib/puppet/type/mysql_plugin.rb
+++ b/modules/services/unix/database/mysql/lib/puppet/type/mysql_plugin.rb
@@ -3,7 +3,7 @@
ensurable
- autorequire(:file) { '/root/.my.cnf' }
+ autorequire(:utility) { '/root/.my.cnf' }
newparam(:name, :namevar => true) do
desc 'The name of the MySQL plugin to manage.'
diff --git a/modules/services/unix/database/mysql/lib/puppet/type/mysql_user.rb b/modules/services/unix/database/mysql/lib/puppet/type/mysql_user.rb
index 94f36858b..8c720b98e 100644
--- a/modules/services/unix/database/mysql/lib/puppet/type/mysql_user.rb
+++ b/modules/services/unix/database/mysql/lib/puppet/type/mysql_user.rb
@@ -4,7 +4,7 @@
ensurable
- autorequire(:file) { '/root/.my.cnf' }
+ autorequire(:utility) { '/root/.my.cnf' }
autorequire(:class) { 'mysql::server' }
newparam(:name, :namevar => true) do
diff --git a/modules/services/unix/http/apache_wheezy_compatible/apache/Gemfile b/modules/services/unix/http/apache_wheezy_compatible/apache/Gemfile
index 3d46720d2..839cfc68a 100644
--- a/modules/services/unix/http/apache_wheezy_compatible/apache/Gemfile
+++ b/modules/services/unix/http/apache_wheezy_compatible/apache/Gemfile
@@ -7,7 +7,7 @@ def gem_type(place_or_version)
if place_or_version =~ /^git:/
:git
elsif place_or_version =~ /^file:/
- :file
+ :utility
else
:gem
end
diff --git a/modules/utilities/windows/database_editor/sqlite_browser/manifests/install.pp b/modules/utilities/windows/database_editor/sqlite_browser/manifests/install.pp
new file mode 100644
index 000000000..49c428d97
--- /dev/null
+++ b/modules/utilities/windows/database_editor/sqlite_browser/manifests/install.pp
@@ -0,0 +1,12 @@
+class sqlite_browser::install {
+ include chocolatey
+
+ notice('Installing Sqlite browser')
+
+ package { 'sqlitebrowser':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+
+ notice('Sqlite browser install finished')
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/database_editor/sqlite_browser/secgen_metadata.xml b/modules/utilities/windows/database_editor/sqlite_browser/secgen_metadata.xml
new file mode 100644
index 000000000..7d215d5e3
--- /dev/null
+++ b/modules/utilities/windows/database_editor/sqlite_browser/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Sqlite browser install
+ Jason Keighley
+ Apache v2
+ A Sqlite browser installation
+
+ database_editor
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/database_editor/sqlite_browser/sqlite_browser.pp b/modules/utilities/windows/database_editor/sqlite_browser/sqlite_browser.pp
new file mode 100644
index 000000000..f5a3887a6
--- /dev/null
+++ b/modules/utilities/windows/database_editor/sqlite_browser/sqlite_browser.pp
@@ -0,0 +1 @@
+include sqlite_browser::install
\ No newline at end of file
diff --git a/modules/utilities/windows/languages/python/manifests/install.pp b/modules/utilities/windows/languages/python/manifests/install.pp
new file mode 100644
index 000000000..74e75b016
--- /dev/null
+++ b/modules/utilities/windows/languages/python/manifests/install.pp
@@ -0,0 +1,10 @@
+class python::install {
+ include chocolatey
+
+ notice('Installing python')
+
+ package { 'python':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/languages/python/python.pp b/modules/utilities/windows/languages/python/python.pp
new file mode 100644
index 000000000..0a11b98f1
--- /dev/null
+++ b/modules/utilities/windows/languages/python/python.pp
@@ -0,0 +1 @@
+include python::install
\ No newline at end of file
diff --git a/modules/utilities/windows/languages/python/secgen_metadata.xml b/modules/utilities/windows/languages/python/secgen_metadata.xml
new file mode 100644
index 000000000..79fb6d3c3
--- /dev/null
+++ b/modules/utilities/windows/languages/python/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Python programming language
+ Jason Keighley
+ Apache v2
+ A Python installation
+
+ languages
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/languages/ruby/manifests/install.pp b/modules/utilities/windows/languages/ruby/manifests/install.pp
new file mode 100644
index 000000000..6a21fd72d
--- /dev/null
+++ b/modules/utilities/windows/languages/ruby/manifests/install.pp
@@ -0,0 +1,10 @@
+class ruby::install {
+ include chocolatey
+
+ notice('Installing ruby')
+
+ package { 'ruby':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/languages/ruby/ruby.pp b/modules/utilities/windows/languages/ruby/ruby.pp
new file mode 100644
index 000000000..782d52619
--- /dev/null
+++ b/modules/utilities/windows/languages/ruby/ruby.pp
@@ -0,0 +1 @@
+include ruby::install
\ No newline at end of file
diff --git a/modules/utilities/windows/languages/ruby/secgen_metadata.xml b/modules/utilities/windows/languages/ruby/secgen_metadata.xml
new file mode 100644
index 000000000..39c1d4718
--- /dev/null
+++ b/modules/utilities/windows/languages/ruby/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Ruby programming language
+ Jason Keighley
+ Apache v2
+ A Ruby installation
+
+ languages
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/prefetch/enable_prefetch/enable_prefetch.pp b/modules/utilities/windows/prefetch/enable_prefetch/enable_prefetch.pp
new file mode 100644
index 000000000..3e59e897f
--- /dev/null
+++ b/modules/utilities/windows/prefetch/enable_prefetch/enable_prefetch.pp
@@ -0,0 +1 @@
+include enable_prefetch
\ No newline at end of file
diff --git a/modules/utilities/windows/prefetch/enable_prefetch/manifests/init.pp b/modules/utilities/windows/prefetch/enable_prefetch/manifests/init.pp
new file mode 100644
index 000000000..bbe5e369b
--- /dev/null
+++ b/modules/utilities/windows/prefetch/enable_prefetch/manifests/init.pp
@@ -0,0 +1,33 @@
+class enable_prefetch {
+ # exec { 'add_prefetch_parameter_keys_to_registry':
+ # # command => 'C:\windows\system32\cmd.exe /C reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" /v EnablePrefetcher /t REG_DWORD /d 3 /f',
+ # }
+ #
+ # exec { 'add_prefetcher_keys_to_registry':
+ # require => Exec['add_prefetch_parameter_keys_to_registry'],
+ # command => 'C:\windows\system32\cmd.exe /C reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Prefetcher" /v MaxPrefetchFiles /t REG_DWORD /d 8192 /f',
+ # }
+ #
+ # exec { 'enable_MMAgent -OperationAPI':
+ # require => Exec['add_prefetcher_keys_to_registry'],
+ # command => 'C:\windows\system32\cmd.exe /C Enable-MMAgent -OperationAPI',
+ # }
+ #
+ # exec { 'start_sysmain':
+ # require => Exec['enable_MMAgent -OperationAPI'],
+ # command => 'C:\windows\system32\cmd.exe /C net start sysmain',
+ # }
+
+ exec { 'enable_prefetcher':
+ command => 'C:\windows\system32\cmd.exe /C reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" /v EnableSuperfetch /t REG_DWORD /d 3 /f',
+ }
+
+ exec { 'enable_superfetcher':
+ command => 'C:\windows\system32\cmd.exe /C reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" /v EnablePrefetcher /t REG_DWORD /d 1 /f',
+ }
+
+ exe { 'reboot':
+ command => 'C:\windows\system32\cmd.exe /C shutdown /g /t 0',
+ }
+
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/prefetch/enable_prefetch/secgen_metadata.xml b/modules/utilities/windows/prefetch/enable_prefetch/secgen_metadata.xml
new file mode 100644
index 000000000..05ed9ff05
--- /dev/null
+++ b/modules/utilities/windows/prefetch/enable_prefetch/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ Enable prefetch
+ Jason Keighley
+ Apache v2
+ Enable prefetch files on a system
+
+ prefetch
+ windows
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/prefetch/place_prefetch_file/manifests/init.pp b/modules/utilities/windows/prefetch/place_prefetch_file/manifests/init.pp
new file mode 100644
index 000000000..12295ed54
--- /dev/null
+++ b/modules/utilities/windows/prefetch/place_prefetch_file/manifests/init.pp
@@ -0,0 +1,5 @@
+class place_prefetch_files ($prefetch_files) {
+ file { $prefetch_files:
+ path => $prefetch_files
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/prefetch/place_prefetch_file/place_prefetch_file.pp b/modules/utilities/windows/prefetch/place_prefetch_file/place_prefetch_file.pp
new file mode 100644
index 000000000..41cc66289
--- /dev/null
+++ b/modules/utilities/windows/prefetch/place_prefetch_file/place_prefetch_file.pp
@@ -0,0 +1 @@
+include place_prefetch_files
\ No newline at end of file
diff --git a/modules/utilities/windows/prefetch/place_prefetch_file/secgen_metadata.xml b/modules/utilities/windows/prefetch/place_prefetch_file/secgen_metadata.xml
new file mode 100644
index 000000000..7e38301ad
--- /dev/null
+++ b/modules/utilities/windows/prefetch/place_prefetch_file/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ Place prefetch file
+ Jason Keighley
+ Apache v2
+ Place a prefetch file onto the system
+
+ prefetch
+ windows
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/registry/access_data_registry_viewer/access_data_registry_viewer.pp b/modules/utilities/windows/registry/access_data_registry_viewer/access_data_registry_viewer.pp
new file mode 100644
index 000000000..0e68c21d3
--- /dev/null
+++ b/modules/utilities/windows/registry/access_data_registry_viewer/access_data_registry_viewer.pp
@@ -0,0 +1 @@
+include access_data_registry_viewer::install
\ No newline at end of file
diff --git a/modules/utilities/windows/registry/access_data_registry_viewer/manifests/install.pp b/modules/utilities/windows/registry/access_data_registry_viewer/manifests/install.pp
new file mode 100644
index 000000000..edf1f5cea
--- /dev/null
+++ b/modules/utilities/windows/registry/access_data_registry_viewer/manifests/install.pp
@@ -0,0 +1,8 @@
+class access_data_registry_viewer::install {
+ include chocolatey
+
+ package { 'access-data-registry-viewer':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/registry/access_data_registry_viewer/secgen_metadata.xml b/modules/utilities/windows/registry/access_data_registry_viewer/secgen_metadata.xml
new file mode 100644
index 000000000..61a6c6192
--- /dev/null
+++ b/modules/utilities/windows/registry/access_data_registry_viewer/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Access data registry viewer install
+ Jason Keighley
+ Apache v2
+ An installation of the access data registry viewer
+
+ registry
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/CHANGELOG.md b/modules/utilities/windows/registry/puppetlabs_registry_library/CHANGELOG.md
new file mode 100644
index 000000000..55c0122d4
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/CHANGELOG.md
@@ -0,0 +1,267 @@
+## 2017-03-06 - Supported Release 1.1.4
+### Summary
+
+This release allows `HKEY_USER` registry keys to be managed, removes Windows Server 2003 support and applies a few minor bugfixes.
+
+#### Features
+- Allow keys and values in `HKEY_USERS` (`hku`) to be managed ([MODULES-3865](https://tickets.puppetlabs.com/browse/MODULES-3865))
+- Remove Windows Server 2003 as a supported Operating System
+
+#### Bugfixes
+- Use double quotes so $key is interpolated ([FM-5236](https://tickets.puppetlabs.com/browse/FM-5236))
+- Fix WOW64 Constant Definition ([MODULES-3195](https://tickets.puppetlabs.com/browse/MODULES-3195))
+- Fix UNSET no longer available as a bareword ([MODULES-4331](https://tickets.puppetlabs.com/browse/MODULES-4331))
+
+## 2015-12-08 - Supported Release 1.1.3
+### Summary
+
+Small release for support of newer PE versions.
+
+## 2015-08-13 - Supported Release 1.1.2
+### Summary
+
+Fix critical bug when writing dword and qword values.
+
+#### Bugfixes
+- Fix the way we write dword and qword values [MODULES-2409](https://tickets.puppet.com/browse/MODULES-2409)
+- changed byte conversion to use pack instead
+- Added tests to catch scenario
+
+## ~~2015-08-12 - Supported Release 1.1.1~~ - Deleted
+### Summary
+
+This release adds Puppet Enterprise 2015.2.0 to metadata
+
+#### Features
+- Testcase fixes
+- Gemfile updates
+- Updated the logic used to convert to byte arrays
+- [MODULES-1921](https://tickets.puppet.com/browse/MODULES-1921) Fixes for:
+-- Ruby registry writes corrupt string [PR # 93](https://github.com/puppetlabs/puppetlabs-registry/commit/0b99718bc7f2d48752aa976d1ba30e49803e97f1)
+
+## 2015-03-24 - Supported Release 1.1.0
+### Summary
+
+This release adds support for Ruby 2.1.5 and issues with how Ruby reads back the registry in certain scenarios, see MODULES-1723 for more details.
+
+#### Bugfixes
+- Additional tests for purge_values
+- Use wide character registry APIs
+- Test Ruby Registry methods uncalled
+- Introduce Ruby 2.1.5 failing tests
+
+
+## 2014-08-25 - Supported Release 1.0.3
+### Summary
+
+This release adds support for native x64 ruby and puppet 3.7, and bugfixes issues with non-leading-zero binary values in registry keys.
+
+## 2014-07-15 - Supported Release 1.0.2
+### Summary
+
+This release merely updates metadata.json so the module can be uninstalled and
+upgraded via the puppet module command.
+
+## 2014-05-20 - Supported Release 1.0.1
+#### Bugfixes
+- Add zero padding to binary single character inputs
+
+## 2014-03-04 - Supported Release 1.0.0
+### Summary
+This is a supported release.
+
+#### Bugfixes
+- Documentation updates
+- Add license file
+
+#### Known Bugs
+
+* This module does not work if run as non-root. Please see [PE-2772](https://tickets.puppet.com/browse/PE-2772)
+
+---
+
+## 2013-08-01 - Release 0.1.2
+### Summary:
+This is a bugfix release that allows the module to work more reliably on x64
+systems and on older systems such as 2003. Also fixes compilation errors due
+to windows library loading on *nix masters.
+
+#### Bugfixes:
+- Refactored code into PuppetX namespace
+- Fixed unhandled exception when loading windows code on *nix
+- Updated README and manifest documentation
+- Only manage redirected keys on 64 bit systems
+- Only use /sysnative filesystem when available
+- Use class accessor method instead of class instance variable
+- Add geppetto project file
+
+---
+
+##### 2012-05-21 - 0.1.1 - Jeff McCune
+
+ * (#14517) Improve error handling when writing values (27223db)
+ * (#14572) Fix management of the default value (f29bdc5)
+
+##### 2012-05-16 - 0.1.0 - Jeff McCune
+
+ * (#14529) Add registry::value defined type (bf44208)
+
+##### 2012-05-16 - Josh Cooper
+
+ * Update README.markdown (2e9e45e)
+
+##### 2012-05-16 - Josh Cooper
+
+ * Update README.markdown (3904838)
+
+##### 2012-05-15 - Josh Cooper
+
+ * (Maint) Add type documentation (82205ad)
+
+##### 2012-05-15 - Josh Cooper
+
+ * Remove note about case-sensitivity, as that is no longer an issue (5440a0e)
+
+##### 2012-05-15 - Jeff McCune
+
+ * (#14501) Fix autorequire case sensitivity (d5c12f0)
+
+##### 2012-05-15 - Jeff McCune
+
+ * (maint) Remove RegistryKeyPath#{valuename,default?} methods (29db478)
+
+##### 2012-05-14 - Jeff McCune
+
+ * Add acceptance tests for registry_value provider (6285f4a)
+
+##### 2012-05-14 - Jeff McCune
+
+ * Eliminate RegistryPathBas#(default?,valuename) from base class (2234f96)
+
+##### 2012-05-14 - Jeff McCune
+
+ * Memoize the filter_path method for performance (6139b7d)
+
+##### 2012-05-11 - Jeff McCune
+
+ * Add Registry_key ensure => absent and purge_values coverage (cfd3789)
+
+##### 2012-05-11 - Jeff McCune
+
+ * Fix cannot alias error when managing 32 and 64 bit versions of a key (3a2f260)
+
+##### 2012-05-11 - Jeff McCune
+
+ * Add registry_key creation acceptance test (0e68654)
+
+##### 2012-05-09 - Jeff McCune
+
+ * Add acceptance tests for the registry type (0a01b11)
+
+##### 2012-05-08 - Jeff McCune
+
+ * Update type description strings (c69bf2d)
+
+##### 2012-05-05 - Jeff McCune
+
+ * Separate the implementation of the type and provider (4e06ae5)
+
+##### 2012-05-04 - Jeff McCune
+
+ * Add watchr script to automatically run tests (d5bce2d)
+
+##### 2012-05-04 - Jeff McCune
+
+ * Add registry::compliance_example class to test compliance (0aa8a68)
+
+##### 2012-05-03 - Jeff McCune
+
+ * Allow values associated with a registry key to be purged (27eaee3)
+
+##### 2012-05-01 - Jeff McCune
+
+ * Update README with info about the types provided (b9b2d11)
+
+##### 2012-04-30 - Jeff McCune
+
+ * Add registry::service defined resource example (57c5b59)
+
+##### 2012-04-25 - Jeff McCune
+
+ * Add REG_MULTI_SZ (type => array) implementation (1b17c6f)
+
+##### 2012-04-26 - Jeff McCune
+
+ * Work around #3947, #4248, #14073; load our utility code (a8d9402)
+
+##### 2012-04-24 - Josh Cooper
+
+ * Handle binary registry values (4353642)
+
+##### 2012-04-24 - Josh Cooper
+
+ * Fix puppet resource registry_key (f736cff)
+
+##### 2012-04-23 - Josh Cooper
+
+ * Registry keys and values were autorequiring all ancestors (0de7a0a)
+
+##### 2012-04-24 - Jeff McCune
+
+ * Add examples of current registry key and value types (bb7e4f4)
+
+##### 2012-04-23 - Josh Cooper
+
+ * Add the ability to manage 32 and 64-bit keys/values (9a16a9b)
+
+##### 2012-04-23 - Josh Cooper
+
+ * Remove rspec deprecation warning (94063d5)
+
+##### 2012-04-23 - Josh Cooper
+
+ * Rename registry-specific util code (cd2aaa1)
+
+##### 2012-04-20 - Josh Cooper
+
+ * Fix autorequiring when using different root key forms (b7a1c39)
+
+##### 2012-04-19 - Josh Cooper
+
+ * Refactor key and value paths (74ebc80)
+
+##### 2012-04-19 - Josh Cooper
+
+ * Encode default-ness in the registry path (64bba67)
+
+##### 2012-04-19 - Josh Cooper
+
+ * Better validation and testing of key paths (d05d1e6)
+
+##### 2012-04-19 - Josh Cooper
+
+ * Maint: Remove more crlf line endings (e9f00c1)
+
+##### 2012-04-19 - Josh Cooper
+
+ * Maint: remove windows cr line endings (0138a1d)
+
+##### 2012-04-18 - Josh Cooper
+
+ * Rename `default` parameter (f45af86)
+
+##### 2012-04-18 - Josh Cooper
+
+ * Fix modifying existing registry values (d06be98)
+
+##### 2012-04-18 - Josh Cooper
+
+ * Remove debugging (8601e92)
+
+##### 2012-04-18 - Josh Cooper
+
+ * Always split the path (de66832)
+
+##### 2012-04-18 - Josh Cooper
+
+ * Initial registry key and value types and providers (065d43d)
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/CONTRIBUTING.md b/modules/utilities/windows/registry/puppetlabs_registry_library/CONTRIBUTING.md
new file mode 100644
index 000000000..990edba7e
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/CONTRIBUTING.md
@@ -0,0 +1,217 @@
+Checklist (and a short version for the impatient)
+=================================================
+
+ * Commits:
+
+ - Make commits of logical units.
+
+ - Check for unnecessary whitespace with "git diff --check" before
+ committing.
+
+ - Commit using Unix line endings (check the settings around "crlf" in
+ git-config(1)).
+
+ - Do not check in commented out code or unneeded files.
+
+ - The first line of the commit message should be a short
+ description (50 characters is the soft limit, excluding ticket
+ number(s)), and should skip the full stop.
+
+ - Associate the issue in the message. The first line should include
+ the issue number in the form "(#XXXX) Rest of message".
+
+ - The body should provide a meaningful commit message, which:
+
+ - uses the imperative, present tense: "change", not "changed" or
+ "changes".
+
+ - includes motivation for the change, and contrasts its
+ implementation with the previous behavior.
+
+ - Make sure that you have tests for the bug you are fixing, or
+ feature you are adding.
+
+ - Make sure the test suites passes after your commit:
+ `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below
+
+ - When introducing a new feature, make sure it is properly
+ documented in the README.md
+
+ * Submission:
+
+ * Pre-requisites:
+
+ - Make sure you have a [GitHub account](https://github.com/join)
+
+ - [Create a ticket](https://tickets.puppet.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppet.com/browse/) you are patching for.
+
+ * Preferred method:
+
+ - Fork the repository on GitHub.
+
+ - Push your changes to a topic branch in your fork of the
+ repository. (the format ticket/1234-short_description_of_change is
+ usually preferred for this project).
+
+ - Submit a pull request to the repository in the puppetlabs
+ organization.
+
+The long version
+================
+
+ 1. Make separate commits for logically separate changes.
+
+ Please break your commits down into logically consistent units
+ which include new or changed tests relevant to the rest of the
+ change. The goal of doing this is to make the diff easier to
+ read for whoever is reviewing your code. In general, the easier
+ your diff is to read, the more likely someone will be happy to
+ review it and get it into the code base.
+
+ If you are going to refactor a piece of code, please do so as a
+ separate commit from your feature or bug fix changes.
+
+ We also really appreciate changes that include tests to make
+ sure the bug is not re-introduced, and that the feature is not
+ accidentally broken.
+
+ Describe the technical detail of the change(s). If your
+ description starts to get too long, that is a good sign that you
+ probably need to split up your commit into more finely grained
+ pieces.
+
+ Commits which plainly describe the things which help
+ reviewers check the patch and future developers understand the
+ code are much more likely to be merged in with a minimum of
+ bike-shedding or requested changes. Ideally, the commit message
+ would include information, and be in a form suitable for
+ inclusion in the release notes for the version of Puppet that
+ includes them.
+
+ Please also check that you are not introducing any trailing
+ whitespace or other "whitespace errors". You can do this by
+ running "git diff --check" on your changes before you commit.
+
+ 2. Sending your patches
+
+ To submit your changes via a GitHub pull request, we _highly_
+ recommend that you have them on a topic branch, instead of
+ directly on "master".
+ It makes things much easier to keep track of, especially if
+ you decide to work on another thing before your first change
+ is merged in.
+
+ GitHub has some pretty good
+ [general documentation](http://help.github.com/) on using
+ their site. They also have documentation on
+ [creating pull requests](http://help.github.com/send-pull-requests/).
+
+ In general, after pushing your topic branch up to your
+ repository on GitHub, you can switch to the branch in the
+ GitHub UI and click "Pull Request" towards the top of the page
+ in order to open a pull request.
+
+
+ 3. Update the related GitHub issue.
+
+ If there is a GitHub issue associated with the change you
+ submitted, then you should update the ticket to include the
+ location of your branch, along with any other commentary you
+ may wish to make.
+
+Testing
+=======
+
+Getting Started
+---------------
+
+Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby
+package manager such as [bundler](http://bundler.io/) what Ruby packages,
+or Gems, are required to build, develop, and test this software.
+
+Please make sure you have [bundler installed](http://bundler.io/#getting-started)
+on your system, then use it to install all dependencies needed for this project,
+by running
+
+```shell
+% bundle install
+Fetching gem metadata from https://rubygems.org/........
+Fetching gem metadata from https://rubygems.org/..
+Using rake (10.1.0)
+Using builder (3.2.2)
+-- 8><-- many more --><8 --
+Using rspec-system-puppet (2.2.0)
+Using serverspec (0.6.3)
+Using rspec-system-serverspec (1.0.0)
+Using bundler (1.3.5)
+Your bundle is complete!
+Use `bundle show [gemname]` to see where a bundled gem is installed.
+```
+
+NOTE some systems may require you to run this command with sudo.
+
+If you already have those gems installed, make sure they are up-to-date:
+
+```shell
+% bundle update
+```
+
+With all dependencies in place and up-to-date we can now run the tests:
+
+```shell
+% bundle exec rake spec
+```
+
+This will execute all the [rspec tests](http://rspec-puppet.com/) tests
+under [spec/defines](./spec/defines), [spec/classes](./spec/classes),
+and so on. rspec tests may have the same kind of dependencies as the
+module they are testing. While the module defines in its [Modulefile](./Modulefile),
+rspec tests define them in [.fixtures.yml](./fixtures.yml).
+
+Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker)
+tests. These tests spin up a virtual machine under
+[VirtualBox](https://www.virtualbox.org/)) with, controlling it with
+[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test
+scenarios. In order to run these, you will need both of those tools
+installed on your system.
+
+You can run them by issuing the following command
+
+```shell
+% bundle exec rake spec_clean
+% bundle exec rspec spec/acceptance
+```
+
+This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml),
+install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb)
+and then run all the tests under [spec/acceptance](./spec/acceptance).
+
+Writing Tests
+-------------
+
+XXX getting started writing tests.
+
+If you have commit access to the repository
+===========================================
+
+Even if you have commit access to the repository, you will still need to
+go through the process above, and have someone else review and merge
+in your changes. The rule is that all changes must be reviewed by a
+developer on the project (that did not write the code) to ensure that
+all changes go through a code review process.
+
+Having someone other than the author of the topic branch recorded as
+performing the merge is the record that they performed the code
+review.
+
+
+Additional Resources
+====================
+
+* [Getting additional help](http://puppet.com/community/get-help)
+
+* [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing)
+
+* [General GitHub documentation](http://help.github.com/)
+
+* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/Gemfile b/modules/utilities/windows/registry/puppetlabs_registry_library/Gemfile
new file mode 100644
index 000000000..d16218590
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/Gemfile
@@ -0,0 +1,167 @@
+#This file is generated by ModuleSync, do not edit.
+
+source ENV['GEM_SOURCE'] || "https://rubygems.org"
+
+# Determines what type of gem is requested based on place_or_version.
+def gem_type(place_or_version)
+ if place_or_version =~ /^git:/
+ :git
+ elsif place_or_version =~ /^file:/
+ :file
+ else
+ :gem
+ end
+end
+
+# Find a location or specific version for a gem. place_or_version can be a
+# version, which is most often used. It can also be git, which is specified as
+# `git://somewhere.git#branch`. You can also use a file source location, which
+# is specified as `file://some/location/on/disk`.
+def location_for(place_or_version, fake_version = nil)
+ if place_or_version =~ /^(git[:@][^#]*)#(.*)/
+ [fake_version, { :git => $1, :branch => $2, :require => false }].compact
+ elsif place_or_version =~ /^file:\/\/(.*)/
+ ['>= 0', { :path => File.expand_path($1), :require => false }]
+ else
+ [place_or_version, { :require => false }]
+ end
+end
+
+# Used for gem conditionals
+supports_windows = true
+
+# The following gems are not included by default as they require DevKit on Windows.
+# You should probably include them in a Gemfile.local or a ~/.gemfile
+#gem 'pry' #this may already be included in the gemfile
+#gem 'pry-stack_explorer', :require => false
+#if RUBY_VERSION =~ /^2/
+# gem 'pry-byebug'
+#else
+# gem 'pry-debugger'
+#end
+
+group :development do
+ gem 'puppet-lint', :require => false
+ gem 'metadata-json-lint', :require => false, :platforms => 'ruby'
+ gem 'puppet_facts', :require => false
+ gem 'puppet-blacksmith', '>= 3.4.0', :require => false, :platforms => 'ruby'
+ gem 'puppetlabs_spec_helper', '>= 1.2.1', :require => false
+ gem 'rspec-puppet', '>= 2.3.2', :require => false
+ gem 'rspec-puppet-facts', :require => false, :platforms => 'ruby'
+ gem 'mocha', '< 1.2.0', :require => false
+ gem 'simplecov', :require => false, :platforms => 'ruby'
+ gem 'parallel_tests', '< 2.10.0', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0')
+ gem 'parallel_tests', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0')
+ gem 'rubocop', '0.41.2', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0')
+ gem 'rubocop', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.0.0')
+ gem 'rubocop-rspec', '~> 1.6', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3.0')
+ gem 'pry', :require => false
+ gem 'json_pure', '<= 2.0.1', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0')
+ gem 'fast_gettext', '1.1.0', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.1.0')
+ gem 'fast_gettext', :require => false if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.1.0')
+ gem 'rainbow', '< 2.2.0', :require => false
+end
+
+group :system_tests do
+ gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '>= 3')
+ gem 'beaker-pe', :require => false
+ gem 'beaker-rspec', *location_for(ENV['BEAKER_RSPEC_VERSION'])
+ gem 'beaker-puppet_install_helper', :require => false
+ gem 'beaker-module_install_helper', :require => false
+ gem 'master_manipulator', :require => false
+ gem 'beaker-hostgenerator', *location_for(ENV['BEAKER_HOSTGENERATOR_VERSION'])
+ gem 'beaker-abs', *location_for(ENV['BEAKER_ABS_VERSION'] || '~> 0.1')
+end
+
+gem 'puppet', *location_for(ENV['PUPPET_GEM_VERSION'])
+
+# Only explicitly specify Facter/Hiera if a version has been specified.
+# Otherwise it can lead to strange bundler behavior. If you are seeing weird
+# gem resolution behavior, try setting `DEBUG_RESOLVER` environment variable
+# to `1` and then run bundle install.
+gem 'facter', *location_for(ENV['FACTER_GEM_VERSION']) if ENV['FACTER_GEM_VERSION']
+gem 'hiera', *location_for(ENV['HIERA_GEM_VERSION']) if ENV['HIERA_GEM_VERSION']
+
+# For Windows dependencies, these could be required based on the version of
+# Puppet you are requiring. Anything greater than v3.5.0 is going to have
+# Windows-specific dependencies dictated by the gem itself. The other scenario
+# is when you are faking out Puppet to use a local file path / git path.
+explicitly_require_windows_gems = false
+puppet_gem_location = gem_type(ENV['PUPPET_GEM_VERSION'])
+# This is not a perfect answer to the version check
+if puppet_gem_location != :gem || (ENV['PUPPET_GEM_VERSION'] && Gem::Version.correct?(ENV['PUPPET_GEM_VERSION']) && Gem::Requirement.new('< 3.5.0').satisfied_by?(Gem::Version.new(ENV['PUPPET_GEM_VERSION'].dup)))
+ if Gem::Platform.local.os == 'mingw32'
+ explicitly_require_windows_gems = true
+ end
+ if puppet_gem_location == :gem
+ # If facterversion hasn't been specified and we are
+ # looking for a Puppet Gem version less than 3.5.0, we
+ # need to ensure we get a good Facter for specs.
+ gem "facter",">= 1.6.11","<= 1.7.5",:require => false unless ENV['FACTER_GEM_VERSION']
+ # If hieraversion hasn't been specified and we are
+ # looking for a Puppet Gem version less than 3.5.0, we
+ # need to ensure we get a good Hiera for specs.
+ gem "hiera",">= 1.0.0","<= 1.3.0",:require => false unless ENV['HIERA_GEM_VERSION']
+ end
+end
+
+if explicitly_require_windows_gems
+ # This also means Puppet Gem less than 3.5.0 - this has been tested back
+ # to 3.0.0. Any further back is likely not supported.
+ if puppet_gem_location == :gem
+ gem "ffi", "1.9.0", :require => false
+ gem "win32-eventlog", "0.5.3","<= 0.6.5", :require => false
+ gem "win32-process", "0.6.5","<= 0.7.5", :require => false
+ gem "win32-security", "~> 0.1.2","<= 0.2.5", :require => false
+ gem "win32-service", "0.7.2","<= 0.8.8", :require => false
+ gem "minitar", "0.5.4", :require => false
+ else
+ gem "ffi", "~> 1.9.0", :require => false
+ gem "win32-eventlog", "~> 0.5","<= 0.6.5", :require => false
+ gem "win32-process", "~> 0.6","<= 0.7.5", :require => false
+ gem "win32-security", "~> 0.1","<= 0.2.5", :require => false
+ gem "win32-service", "~> 0.7","<= 0.8.8", :require => false
+ gem "minitar", "~> 0.5.4", :require => false
+ end
+
+ gem "win32-dir", "~> 0.3","<= 0.4.9", :require => false
+ gem "win32console", "1.3.2", :require => false if RUBY_VERSION =~ /^1\./
+
+ # sys-admin was removed in Puppet 3.7.0+, and doesn't compile
+ # under Ruby 2.3 - so restrict it to Ruby 1.x
+ gem "sys-admin", "1.5.6", :require => false if RUBY_VERSION =~ /^1\./
+
+ # Puppet less than 3.7.0 requires these.
+ # Puppet 3.5.0+ will control the actual requirements.
+ # These are listed in formats that work with all versions of
+ # Puppet from 3.0.0 to 3.6.x. After that, these were no longer used.
+ # We do not want to allow newer versions than what came out after
+ # 3.6.x to be used as they constitute some risk in breaking older
+ # functionality. So we set these to exact versions.
+ gem "win32-api", "1.4.8", :require => false
+ gem "win32-taskscheduler", "0.2.2", :require => false
+ gem "windows-api", "0.4.3", :require => false
+ gem "windows-pr", "1.2.3", :require => false
+else
+ if Gem::Platform.local.os == 'mingw32'
+ # If we're using a Puppet gem on windows, which handles its own win32-xxx gem dependencies (Pup 3.5.0 and above), set maximum versions
+ # Required due to PUP-6445
+ gem "win32-dir", "<= 0.4.9", :require => false
+ gem "win32-eventlog", "<= 0.6.5", :require => false
+ gem "win32-process", "<= 0.7.5", :require => false
+ gem "win32-security", "<= 0.2.5", :require => false
+ gem "win32-service", "<= 0.8.8", :require => false
+ end
+end
+
+# Evaluate Gemfile.local if it exists
+if File.exists? "#{__FILE__}.local"
+ eval(File.read("#{__FILE__}.local"), binding)
+end
+
+# Evaluate ~/.gemfile if it exists
+if File.exists?(File.join(Dir.home, '.gemfile'))
+ eval(File.read(File.join(Dir.home, '.gemfile')), binding)
+end
+
+# vim:ft=ruby
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/LICENSE b/modules/utilities/windows/registry/puppetlabs_registry_library/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/MAINTAINERS.md b/modules/utilities/windows/registry/puppetlabs_registry_library/MAINTAINERS.md
new file mode 100644
index 000000000..6ef3c581b
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/MAINTAINERS.md
@@ -0,0 +1,6 @@
+## Maintenance
+
+Maintainers:
+ - Puppet Windows Team `windows |at| puppet |dot| com`
+
+Tickets: https://tickets.puppet.com/browse/MODULES. Make sure to set component to `registry`.
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/NOTICE b/modules/utilities/windows/registry/puppetlabs_registry_library/NOTICE
new file mode 100644
index 000000000..324523d7b
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/NOTICE
@@ -0,0 +1,15 @@
+Puppet Module - puppetlabs-registry
+
+Copyright 2012 - 2017 Puppet, Inc.
+
+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.
\ No newline at end of file
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/README.markdown b/modules/utilities/windows/registry/puppetlabs_registry_library/README.markdown
new file mode 100644
index 000000000..dc4fb1cb0
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/README.markdown
@@ -0,0 +1,251 @@
+# registry
+[![Build Status](https://travis-ci.org/puppetlabs/puppetlabs-registry.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-registry)
+
+#### Table of Contents
+
+1. [Overview - What is the registry module?](#overview)
+2. [Module Description - What registry does and why it is useful](#module-description)
+3. [Setup - The basics of getting started with registry](#setup)
+ * [Beginning with registry](#beginning-with-registry)
+4. [Usage - Configuration options and additional functionality](#usage)
+5. [Reference](#reference)
+ * [Public Defines](#public-defines)
+ * [Public Types](#public-types)
+ * [Parameters](#parameters)
+6. [Limitations](#limitations)
+7. [Development - Guide for contributing to registry](#development)
+
+## Overview
+
+This module supplies the types and providers you'll need to manage the Registry on your Windows nodes.
+
+## Module Description
+
+The Registry is a hierarchical database built into Microsoft Windows. It stores settings and other information for the operating system and a wide range of applications. This module lets Puppet manage individual Registry keys and values, and provides a simplified way to manage Windows services.
+
+## Setup
+
+This module must be installed on your Puppet master. We've tested it with Puppet agents running on Windows Server 2008 R2, 2012, and 2012 R2.
+
+### Beginning with registry
+
+Use the `registry_key` type to manage a single registry key:
+
+``` puppet
+registry_key { 'HKLM\System\CurrentControlSet\Services\Puppet':
+ ensure => present,
+}
+```
+
+## Usage
+
+The registry module works mainly through two types: `registry_key` and `registry_value`. These types combine to let you specify a Registry container and its intended contents.
+
+### Manage a single Registry value
+
+``` puppet
+registry_value { 'HKLM\System\CurrentControlSet\Services\Puppet\Description':
+ ensure => present,
+ type => string,
+ data => "The Puppet Agent service periodically manages your configuration",
+}
+```
+
+### Manage a Registry value and its parent key in one declaration
+
+``` puppet
+class myapp {
+ registry::value { 'puppetmaster':
+ key => 'HKLM\Software\Vendor\PuppetLabs',
+ data => 'puppet.puppet.com',
+ }
+}
+```
+
+Puppet looks up the key 'HKLM\Software\Vendor\PuppetLabs' and makes sure it contains a value named 'puppetmaster' containing the string 'puppet.puppet.com'.
+
+**Note:** the `registry::value` define only manages keys and values in the system-native architecture. In other words, 32-bit keys applied in a 64-bit OS aren't managed by this define; instead, you must use the types, [`registry_key`](#type-registry_key) and [`registry_value`](#type-registry_value) individually.
+
+Within this define, you can specify multiple Registry values for one Registry key and manage them all at once.
+
+### Set the default value for a key
+
+``` puppet
+registry::value { 'Setting0':
+ key  => 'HKLM\System\CurrentControlSet\Services\Puppet',
+ value => '(default)',
+ data  => "Hello World!",
+}
+```
+
+You can still add values in a string (or array) beyond the default, but you can only set one default value per key.
+
+
+### Purge existing values
+
+By default, if a key includes additional values besides the ones you specify through this module, Puppet leaves those extra values in place. To change that, use the `purge_values => true` parameter of the `registry_key` resource. **Enabling this feature deletes any values in the key that are not managed by Puppet.**
+
+The `registry::purge_example` class provides a quick and easy way to see a demonstration of how this works. This example class has two modes of operation determined by the Facter fact `PURGE_EXAMPLE_MODE`: 'setup' and 'purge'.
+
+To run the demonstration, make sure the `registry::purge_example` class is included in your node catalog, then set an environment variable in PowerShell. This sets up a Registry key that contains six values.
+
+``` powershell
+ PS C:\> $env:FACTER_PURGE_EXAMPLE_MODE = 'setup'
+ PS C:\> puppet agent --test
+
+ notice: /Stage[main]/Registry::Purge_example/Registry_key[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value3]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value2]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_key[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\SubKey]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value5]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value6]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\SubKey\Value1]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value1]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\SubKey\Value2]/ensure: created
+ notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value4]/ensure: created
+ notice: Finished catalog run in 0.14 seconds
+```
+
+Switching the mode to 'purge' causes the class to only manage three of the six `registry_value` resources. The other three are purged because they are not specifically declared in the manifest.
+Notice how `Value4`, `Value5` and `Value6` are being removed.
+
+``` powershell
+PS C:\> $env:FACTER_PURGE_EXAMPLE_MODE = 'purge'
+PS C:\> puppet agent --test
+
+notice: /Registry_value[hklm\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value4]/ensure: removed
+notice: /Registry_value[hklm\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value6]/ensure: removed
+notice: /Registry_value[hklm\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value5]/ensure: removed
+notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value3]/data: data changed 'key3' to 'should not be purged'
+notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value2]/data: data changed '2' to '0'
+notice: /Stage[main]/Registry::Purge_example/Registry_value[HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge\Value1]/data: data changed '1' to '0'
+notice: Finished catalog run in 0.16 seconds
+```
+
+### Manage Windows services
+
+The `registry::service` define manages entries in the Microsoft service control framework by automatically manipulating values in the key `HKLM\System\CurrentControlSet\Services\$name\`.
+
+This is an alternative approach to using INSTSRV.EXE [1](http://support.microsoft.com/kb/137890).
+
+``` powershell
+registry::service { puppet:
+ ensure => present,
+ display_name => "Puppet Agent",
+ description => "Periodically fetches and applies configurations from a Puppet master server.",
+ command => 'C:\PuppetLabs\Puppet\service\daemon.bat',
+}
+```
+
+## Reference
+
+### Public Defines
+* `registry::value`: Manages the parent key for a particular value. If the parent key doesn't exist, Puppet automatically creates it.
+* `registry::service`: Manages entries in the Microsoft service control framework by manipulating values in the key `HKLM\System\CurrentControlSet\Services\$name\`.
+
+### Public Types
+* `registry_key`: Manages individual Registry keys.
+* `registry_value`: Manages individual Registry values.
+
+### Parameters
+
+#### `registry::value`:
+
+All parameters are required unless otherwise stated.
+
+##### `key`
+
+Specifies a Registry key for Puppet to manage. Note that if any of the parent keys in the path do not exist, the resource raises an error. Use the `registry_key` to create the parent key prior to setting a registry value. Valid options: a string containing a Registry path.
+
+##### `data`
+
+Provides the contents of the specified value. Valid options: a string by default; an array if specified through the `type` parameter.
+
+##### `type`
+
+*Optional.* Sets the data type of the specified value. Valid options: 'string', 'array', 'dword', 'qword', 'binary' and 'expand'. Default value: 'string'.
+
+##### `value`
+
+*Optional.* Determines what Registry value(s) to manage within the specified key. To set a Registry value as the default value for its parent key, name the value '(default)'. Valid options: a string. Default value: the title of your declared resource.
+
+#### `registry_key`
+
+##### `ensure`
+
+Tells Puppet whether the key should or shouldn't exist. Valid options: 'present' and 'absent'. Default value: 'present'.
+
+##### `path`
+
+Specifies a Registry key for Puppet to manage. If any of the parent keys in the path don't exist, Puppet creates them automatically. Valid options: a string containing a Registry path. For example: `HKLM\Software` or `HKEY_LOCAL_MACHINE\Software\Vendor`.
+
+If Puppet is running on a 64-bit system, the 32-bit Registry key can be explicitly managed using a prefix. For example: `32:HKLM\Software`.
+
+##### `purge_values`
+
+*Optional.* Specifies whether to delete any values in the specified key that are not managed by Puppet. Valid options: `true` and `false`. Default value: `false`.
+
+For more on this parameter, see the [Purge existing values section](#purge-existing-values) under Usage.
+
+#### `registry_value`
+
+##### `path`
+
+*Optional.* Specifies a Registry value for Puppet to manage. Valid options: a string containing a Registry path. If any of the parent keys in the path don't exist, Puppet creates them automatically. For example: `HKLM\Software` or `HKEY_LOCAL_MACHINE\Software\Vendor`. Default value: the title of your declared resource.
+
+If Puppet is running on a 64-bit system, the 32-bit Registry key can be explicitly managed using a prefix. For example: `32:HKLM\Software\Value3`.
+
+##### `ensure`
+
+Tells Puppet whether the value should or shouldn't exist. Valid options: 'present' and 'absent'. Default value: 'present'.
+
+##### `type`
+
+*Optional.* Sets the data type of the specified value. Valid options: 'string', 'array', 'dword', 'qword', 'binary' and 'expand'. Default value: 'string'.
+
+##### `data`
+
+Provides the contents of the specified value. Valid options: a string by default; an array if specified through the `type` parameter.
+
+#### `registry::service`
+
+##### `ensure`
+
+Tells Puppet whether the service should or shouldn't exist. Valid options: 'present' and 'absent'. Default value: 'present'.
+
+##### `display_name`
+
+*Optional.* Provides a Display Name for the service. Valid options: a string. Default value: the title of your declared resource.
+
+##### `description`
+
+*Optional.* Provides a description of the service. Valid options: a string. Default value: blank.
+
+##### `command`
+
+Specifies the command to execute when starting the service. Valid options: a string containing the absolute path to an executable file.
+
+##### `start`
+
+Specifies the starting mode of the service. Valid options: 'automatic', 'manual' and 'disabled'.
+
+Puppet's [native service resource](http://docs.puppet.com/references/latest/type.html#service) can also be used to manage this setting.
+
+## Limitations
+
+* Keys within `HKEY_LOCAL_MACHINE` (`hklm`), `HKEY_CLASSES_ROOT` (`hkcr`) or `HKEY_USERS` (`hku`) are supported. Other predefined root keys (e.g., `HKEY_CURRENT_USER`) are not currently supported.
+* Puppet doesn't recursively delete Registry keys.
+
+Please report any issues through our [Module Issue Tracker](https://tickets.puppet.com/browse/MODULES).
+
+## Development
+
+Puppet Inc modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can't access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
+
+We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.
+
+For more information, see our [module contribution guide.](https://docs.puppet.com/forge/contributing.html)
+
+### Contributors
+
+To see who's already involved, see the [list of contributors.](https://github.com/puppetlabs/puppetlabs-registry/graphs/contributors)
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/Rakefile b/modules/utilities/windows/registry/puppetlabs_registry_library/Rakefile
new file mode 100644
index 000000000..24644f7fe
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/Rakefile
@@ -0,0 +1,38 @@
+ require 'puppetlabs_spec_helper/rake_tasks'
+ require 'puppet-lint/tasks/puppet-lint'
+ require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any?
+begin
+ require 'beaker/tasks/test'
+rescue LoadError
+ #Do nothing, rescue for Windows as beaker does not work and will not be installed
+end
+
+#Due to puppet-lint not ignoring tests folder or the ignore paths attribute
+#we have to ignore many things
+# #Due to bug in puppet-lint we have to clear and redo the lint tasks to achieve ignore paths
+ Rake::Task[:lint].clear
+ PuppetLint::RakeTask.new(:lint) do |config|
+ config.pattern = 'manifests/**/*.pp'
+ config.fail_on_warnings = true
+ config.disable_checks = [
+ '80chars',
+ 'class_inherits_from_params_class',
+ 'class_parameter_defaults',
+ 'documentation',
+ 'single_quote_string_with_variables']
+ config.ignore_paths = ["tests/*.pp", "spec/**/*.pp", "pkg/**/*.pp"]
+ end
+
+task :default => [:test]
+
+desc 'Run RSpec'
+RSpec::Core::RakeTask.new(:test) do |t|
+ t.pattern = 'spec/{unit}/**/*.rb'
+ #t.rspec_opts = ['--color']
+end
+
+desc 'Generate code coverage'
+RSpec::Core::RakeTask.new(:coverage) do |t|
+ t.rcov = true
+ t.rcov_opts = ['--exclude', 'spec']
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/.beaker-git.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/.beaker-git.cfg
new file mode 100644
index 000000000..50c628773
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/.beaker-git.cfg
@@ -0,0 +1,11 @@
+{
+ :load_path => "./acceptance/lib/",
+ :hosts_file => './acceptance/config/windows-2012-x86_64.cfg',
+ :type => "foss",
+ :pre_suite => ["./acceptance/setup/install_puppet.rb"],
+ :tests => "./acceptance/tests",
+ :log_level => "debug",
+ :timeout => 6000,
+ :ntp => true,
+ :keyfile => "~/.ssh/id_rsa-acceptance"
+}
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/.beaker-pe.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/.beaker-pe.cfg
new file mode 100644
index 000000000..b0eaa19d9
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/.beaker-pe.cfg
@@ -0,0 +1,11 @@
+{
+ :load_path => "./acceptance/lib/",
+ :hosts_file => './acceptance/config/windows-2012-x86_64.cfg',
+ :type => "pe",
+ :pre_suite => ['./acceptance/setup/install_puppet.rb'],
+ :tests => "./acceptance/tests/",
+ :log_level => "debug",
+ :timeout => 6000,
+ :ntp => true,
+ :keyfile => "~/.ssh/id_rsa-acceptance"
+}
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2008-x86_64.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2008-x86_64.cfg
new file mode 100644
index 000000000..74ddd3ac6
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2008-x86_64.cfg
@@ -0,0 +1,17 @@
+HOSTS:
+ w2008:
+ roles:
+ - agent
+ - default
+ platform: windows-2008-x86_64
+ template: win-2008-x86_64
+ hypervisor: vcloud
+CONFIG:
+ type: foss
+ keyfile: ~/.ssh/id_rsa-acceptance
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2008r2-x86_64.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2008r2-x86_64.cfg
new file mode 100644
index 000000000..86c6fe073
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2008r2-x86_64.cfg
@@ -0,0 +1,17 @@
+HOSTS:
+ w2008r2:
+ roles:
+ - agent
+ - default
+ platform: windows-2008r2-x86_64
+ template: win-2008r2-x86_64
+ hypervisor: vcloud
+CONFIG:
+ type: foss
+ keyfile: ~/.ssh/id_rsa-acceptance
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2012-x86_64.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2012-x86_64.cfg
new file mode 100644
index 000000000..37a273d70
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2012-x86_64.cfg
@@ -0,0 +1,17 @@
+HOSTS:
+ w2012:
+ roles:
+ - agent
+ - default
+ platform: windows-2012-x86_64
+ template: win-2012-x86_64
+ hypervisor: vcloud
+CONFIG:
+ type: foss
+ keyfile: ~/.ssh/id_rsa-acceptance
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2012r2-x86_64.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2012r2-x86_64.cfg
new file mode 100644
index 000000000..fbad38cbe
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/masterless-windows-2012r2-x86_64.cfg
@@ -0,0 +1,17 @@
+HOSTS:
+ w2012r2:
+ roles:
+ - agent
+ - default
+ platform: windows-2012r2-x86_64
+ template: win-2012r2-x86_64
+ hypervisor: vcloud
+CONFIG:
+ type: foss
+ keyfile: ~/.ssh/id_rsa-acceptance
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2003r2-i386.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2003r2-i386.cfg
new file mode 100644
index 000000000..3879e144b
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2003r2-i386.cfg
@@ -0,0 +1,23 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ w2k3r2:
+ roles:
+ - agent
+ platform: windows-2003r2-i386
+ template: win-2003r2-i386
+ hypervisor: vcloud
+CONFIG:
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2003r2-x86_64.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2003r2-x86_64.cfg
new file mode 100644
index 000000000..51b621005
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2003r2-x86_64.cfg
@@ -0,0 +1,23 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ w2k3r2:
+ roles:
+ - agent
+ platform: windows-2003r2-x86_64
+ template: win-2003r2-x86_64
+ hypervisor: vcloud
+CONFIG:
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2008r2-x86_64.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2008r2-x86_64.cfg
new file mode 100644
index 000000000..af8788263
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2008r2-x86_64.cfg
@@ -0,0 +1,23 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ w2k8r2:
+ roles:
+ - agent
+ platform: windows-2008r2-x86_64
+ template: win-2008r2-x86_64
+ hypervisor: vcloud
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2012-x86_64.cfg b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2012-x86_64.cfg
new file mode 100644
index 000000000..1fdc6a043
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/config/windows-2012-x86_64.cfg
@@ -0,0 +1,23 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ w2012:
+ roles:
+ - agent
+ - default
+ platform: windows-2012-x86_64
+ template: win-2012-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/FOSS/Dynamic
+ resourcepool: delivery/Quality Assurance/FOSS/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest.rb
new file mode 100644
index 000000000..d7513b8b0
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest.rb
@@ -0,0 +1,2 @@
+module Systest
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest/util.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest/util.rb
new file mode 100644
index 000000000..61da9151e
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest/util.rb
@@ -0,0 +1,4 @@
+require 'pathname'
+require Pathname.new(__FILE__).dirname
+module Systest::Util
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest/util/registry.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest/util/registry.rb
new file mode 100644
index 000000000..ecf86604a
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/lib/systest/util/registry.rb
@@ -0,0 +1,232 @@
+require 'pathname'
+
+require Pathname.new(__FILE__).dirname
+# This module is meant to be mixed into the individual test cases for the
+# registry module.
+module Systest::Util::Registry
+ FUTURE_PARSER = ENV['FUTURE_PARSER'] == 'true'
+
+ # Given a relative path, returns an absolute path for a test file.
+ # Basically, this just prepends the a unique temp dir path (specific to the
+ # current test execution) to your relative path.
+ def get_test_file_path(host, file_rel_path)
+ File.join(host_test_tmp_dirs[host.name], file_rel_path)
+ end
+
+ def cur_test_file
+ @path
+ end
+
+ def cur_test_file_shortname
+ File.basename(cur_test_file, File.extname(cur_test_file))
+ end
+
+ def tmpdir(host, basename)
+ host_tmpdir = host.tmpdir(basename)
+ # we need to make sure that the puppet user can traverse this directory...
+ chmod(host, "755", host_tmpdir)
+ host_tmpdir
+ end
+
+ def mkdirs(host, dir_path)
+ on(host, "mkdir -p #{dir_path}")
+ end
+
+ def chown(host, owner, group, path)
+ on(host, "chown #{owner}:#{group} #{path}")
+ end
+
+ def chmod(host, mode, path)
+ on(host, "chmod #{mode} #{path}")
+ end
+
+ def all_hosts
+ # we need one list of all of the hosts, to assist in managing temp dirs. It's possible
+ # that the master is also an agent, so this will consolidate them into a unique set
+ hosts
+ end
+
+ def host_test_tmp_dirs
+ # now we can create a hash of temp dirs--one per host, and unique to this test--without worrying about
+ # doing it twice on any individual host
+ @host_test_tmp_dirs ||= Hash[all_hosts.map do |host|
+ [host.name, tmpdir(host, cur_test_file_shortname)]
+ end]
+ end
+
+ def master_manifest_dir
+ @master_manifest_dir ||= "master_manifest"
+ end
+
+ def master_module_dir
+ @master_module_dir ||= "master_modules"
+ end
+
+ def master_manifest_file
+ @master_manifest_file ||= "#{master_manifest_dir}/site.pp"
+ end
+
+ def agent_args
+ @agent_args ||= "--trace --libdir=\"%s\" --pluginsync --no-daemonize --verbose --onetime --test --server #{master}"
+ end
+
+ def agent_lib_dir
+ @agent_lib_dir ||= "agent_lib"
+ end
+
+ def masters
+ @masters ||= hosts.select { |host| host['roles'].include? 'master' } || []
+ end
+
+ def windows_agents
+ agents.select { |agent| agent['platform'].include?('windows') }
+ end
+
+ def master_options
+ @master_options ||= "--manifest=\"#{get_test_file_path(master, master_manifest_file)}\" " +
+ "--modulepath=\"#{get_test_file_path(master, master_module_dir)}\" " +
+ "--autosign true --pluginsync"
+ end
+
+ def master_options_hash
+ @master_options_hash ||= {
+ :manifest => "#{get_test_file_path(master, master_manifest_file)}",
+ :modulepath => "#{get_test_file_path(master, master_module_dir)} ",
+ :autosign => true,
+ :pluginsync => true
+ }
+ end
+
+ def agent_exit_codes
+ # legal exit codes whenever we run the agent
+ # we need to allow exit code 2, which means "changes were applied" on the agent
+ @agent_exit_codes ||= [0, 2]
+ end
+
+ def x64?(agent)
+ on(agent, facter('architecture')).stdout.chomp == 'x64'
+ end
+
+ def native_sysdir(agent)
+ if x64?(agent)
+ if on(agent, 'ls /cygdrive/c/windows/sysnative', :acceptable_exit_codes => (0..255)).exit_code == 0
+ '`cygpath -W`/sysnative'
+ else
+ nil
+ end
+ else
+ '`cygpath -S`'
+ end
+ end
+
+ def randomstring(length)
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ str = ""
+ 1.upto(length) { |i| str << chars[rand(chars.size-1)] }
+ return str
+ end
+
+ # Create a file on the host.
+ # Parameters:
+ # [host] the host to create the file on
+ # [file_path] the path to the file to be created
+ # [file_content] a string containing the contents to be written to the file
+ # [options] a hash containing additional behavior options. Currently supported:
+ # * :mkdirs (default false) if true, attempt to create the parent directories on the remote host before writing
+ # the file
+ # * :owner (default 'root') the username of the user that the file should be owned by
+ # * :group (default 'puppet') the name of the group that the file should be owned by
+ # * :mode (default '644') the mode (file permissions) that the file should be created with
+ def create_test_file(host, file_rel_path, file_content, options)
+
+ # set default options
+ options[:mkdirs] ||= false
+ options[:owner] ||= (host['user'] || "root")
+ options[:group] ||= (host['group'] || "puppet")
+ options[:mode] ||= "755"
+
+ file_path = get_test_file_path(host, file_rel_path)
+
+ mkdirs(host, File.dirname(file_path)) if (options[:mkdirs] == true)
+ create_remote_file(host, file_path, file_content)
+ #
+ # NOTE: we need these `chown/chmod calls because the acceptance framework connects to the nodes as "root", but
+ # puppet 'master' runs as user 'puppet'. Therefore, in order for puppet master to be able to read any files
+ # that we've created, we have to carefully set their permissions
+ #
+ chown(host, options[:owner], options[:group], file_path)
+ chmod(host, options[:mode], file_path)
+ end
+
+ def puppet_module_install(host = nil, source = nil, module_name = nil, module_path = '/etc/puppet/modules')
+ opts = {:source => source, :module_name => module_name, :target_module_path => module_path}
+ copy_root_module_to(host, opts)
+ end
+
+ def setup_master(master_manifest_content="# Intentionally Blank\n")
+ step "Setup Puppet Master Manifest" do
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../'))
+ if any_hosts_as?('master') do
+ masters.each do |host|
+ puppet_module_install(host, proj_root, 'registry', File.join(host['puppetpath'], "modules"))
+ create_test_file(host, master_manifest_file, master_manifest_content, :mkdirs => true)
+ puppet_conf_update_ini = <<-MANIFEST
+ ini_setting{'Update Puppet.Conf':
+ ensure => present,
+ section => 'main',
+ key_val_separator => '=',
+ path => '#{host['puppetpath']}/puppet.conf',
+ setting => 'manifestdir',
+ value => '#{host_test_tmp_dirs[host.name]}/master_manifest/' }
+ MANIFEST
+ on host, puppet('apply', '--debug'), :stdin => puppet_conf_update_ini
+ end
+ end
+ end
+ end
+ step "Symlink the module(s) into the master modulepath" do
+ if any_hosts_as?('master') do
+ masters.each do |host|
+ moddir = get_test_file_path(host, master_module_dir)
+ mkdirs(host, moddir)
+ #on host, "ln -s /opt/puppet-git-repos/stdlib \"#{moddir}/stdlib\"; ln -s /opt/puppet-git-repos/registry \"#{moddir}/registry\""
+ end
+ end
+ end
+ end
+ end
+
+ def clean_up
+ step "Clean Up" do
+ if any_hosts_as?(:master)
+ masters.each do |host|
+ puppet_conf_update_ini = <<-MANIFEST
+ ini_setting{'Revert Puppet.Conf':
+ ensure => absent,
+ section => 'main',
+ key_val_separator => '=',
+ path => '#{host['puppetpath']}/puppet.conf',
+ setting => 'manifestdir' }
+ MANIFEST
+ on host, puppet('apply', '--debug'), :stdin => puppet_conf_update_ini
+ on host, "rm -rf \"%s\"" % get_test_file_path(host, '')
+ end
+ end
+ agents.each do |host|
+ on host, "rm -rf \"%s\"" % get_test_file_path(host, '')
+ end
+ end
+ end
+
+ def get_apply_opts(environment_hash = nil, acceptable_exit_codes = agent_exit_codes)
+ opts = {
+ :catch_failures => true,
+ :future_parser => FUTURE_PARSER,
+ :acceptable_exit_codes => agent_exit_codes,
+ }
+ opts.merge!(:environment => environment_hash) if environment_hash
+ opts
+ end
+
+end
+
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/setup/install_puppet.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/setup/install_puppet.rb
new file mode 100644
index 000000000..8ec9483d4
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/setup/install_puppet.rb
@@ -0,0 +1,19 @@
+require 'beaker/puppet_install_helper'
+
+run_puppet_install_helper
+
+test_name "Installing Puppet Modules" do
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '../..'))
+ hosts.each do |host|
+ if host['platform'] =~ /windows/
+ on host, "mkdir -p #{host['distmoduledir']}/registry"
+ target = (on host, "echo #{host['distmoduledir']}/registry").raw_output.chomp
+
+ %w(lib manifests metadata.json).each do |file|
+ scp_to host, "#{proj_root}/#{file}", target
+ end
+ on host, 'curl -k -o c:/puppetlabs-stdlib-4.6.0.tar.gz https://forgeapi.puppetlabs.com/v3/files/puppetlabs-stdlib-4.6.0.tar.gz'
+ on host, puppet('module install c:/puppetlabs-stdlib-4.6.0.tar.gz --force --ignore-dependencies'), {:acceptable_exit_codes => [0, 1]}
+ end
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_create_key.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_create_key.rb
new file mode 100644
index 000000000..c9c9b9417
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_create_key.rb
@@ -0,0 +1,161 @@
+require 'tempfile'
+require 'pathname'
+require 'systest/util/registry'
+# Include our utility methods in the singleton class of the test case instance.
+class << self
+ include Systest::Util::Registry
+end
+
+test_name "Registry Key Management"
+
+# Generate a unique key name
+keyname = "PuppetLabsTest_#{randomstring(8)}"
+# This is the keypath we'll use for this entire test. We will actually create this key and delete it.
+keypath = "HKLM\\Software\\Vendor\\#{keyname}"
+
+master_manifest_content = < "fact_phase: $fact_phase" }
+
+registry_key { 'HKLM\\Software\\Vendor': ensure => present }
+case $fact_phase {
+ '2': { include phase2 }
+ '3': { include phase3 }
+ default: { include phase1 }
+}
+HERE
+
+# Setup keys to purge
+phase1 = < present }
+ Registry_key { ensure => present }
+ Registry_value { ensure => present, data => 'Puppet Default Data' }
+
+ registry_key { '#{keypath}': }
+ registry_key { '#{keypath}\\SubKey1': }
+
+ if $architecture == 'x64' {
+ registry_key { '32:#{keypath}': }
+ registry_key { '32:#{keypath}\\SubKey1': }
+ }
+
+ registry_key { '#{keypath}\\SubKeyToPurge': }
+ registry_value { '#{keypath}\\SubKeyToPurge\\Value1': }
+ registry_value { '#{keypath}\\SubKeyToPurge\\Value2': }
+ registry_value { '#{keypath}\\SubKeyToPurge\\Value3': }
+
+ if $architecture == 'x64' {
+ registry_key { '32:#{keypath}\\SubKeyToPurge': }
+ registry_value { '32:#{keypath}\\SubKeyToPurge\\Value1': }
+ registry_value { '32:#{keypath}\\SubKeyToPurge\\Value2': }
+ registry_value { '32:#{keypath}\\SubKeyToPurge\\Value3': }
+ }
+PHASE1
+
+# Purge the keys in a subsequent run
+phase2 = < present }
+ Registry_key { ensure => present, purge_values => true }
+
+ registry_key { '#{keypath}\\SubKeyToPurge': }
+ if $architecture == 'x64' {
+ registry_key { '32:#{keypath}\\SubKeyToPurge': }
+ }
+PHASE2
+
+# Delete our keys
+phase3 = < present }
+ Registry_key { ensure => absent }
+
+ # These have relationships because autorequire break things when
+ # ensure is absent. REVISIT: Make this not a requirement.
+ # REVISIT: This appears to work with explicit relationships but not with ->
+ # notation.
+ registry_key { '#{keypath}\\SubKey1': }
+ registry_key { '#{keypath}\\SubKeyToPurge': }
+ registry_key { '#{keypath}':
+ require => Registry_key['#{keypath}\\SubKeyToPurge', '#{keypath}\\SubKey1'],
+ }
+
+ if $architecture == 'x64' {
+ registry_key { '32:#{keypath}\\SubKey1': }
+ registry_key { '32:#{keypath}\\SubKeyToPurge': }
+ registry_key { '32:#{keypath}':
+ require => Registry_key['32:#{keypath}\\SubKeyToPurge', '32:#{keypath}\\SubKey1'],
+ }
+ }
+PHASE3
+
+
+# Setup the master to use the modules specified in the --modules option
+
+
+step "Start should_create_key test" do
+ # setup_master master_manifest_content
+ # with_puppet_running_on master, :__commandline_args__ => master_options do
+ # A set of keys we expect Puppet to create
+ keys_created_native = [
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest\w+\].ensure: created/,
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\].ensure: created/
+ ]
+
+ keys_created_wow = [
+ /Registry_key\[32:HKLM.Software.Vendor.PuppetLabsTest\w+\].ensure: created/,
+ /Registry_key\[32:HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\].ensure: created/
+ ]
+
+ # A set of regular expression of values to be purged in phase 2.
+ values_purged_native = [
+ /Registry_value\[hklm.Software.Vendor.PuppetLabsTest\w+.SubKeyToPurge.Value1\].ensure: removed/,
+ /Registry_value\[hklm.Software.Vendor.PuppetLabsTest\w+.SubKeyToPurge.Value2\].ensure: removed/,
+ /Registry_value\[hklm.Software.Vendor.PuppetLabsTest\w+.SubKeyToPurge.Value3\].ensure: removed/
+ ]
+
+ values_purged_wow = [
+ /Registry_value\[32:hklm.Software.Vendor.PuppetLabsTest\w+.SubKeyToPurge.Value1\].ensure: removed/,
+ /Registry_value\[32:hklm.Software.Vendor.PuppetLabsTest\w+.SubKeyToPurge.Value2\].ensure: removed/,
+ /Registry_value\[32:hklm.Software.Vendor.PuppetLabsTest\w+.SubKeyToPurge.Value3\].ensure: removed/
+ ]
+
+ windows_agents.each do |agent|
+ is_x64 = x64?(agent)
+
+ keys_created = keys_created_native + (is_x64 ? keys_created_wow : [])
+ values_purged = values_purged_native + (is_x64 ? values_purged_wow : [])
+
+ # Do the first run and make sure the key gets created.
+ step "Registry - Phase 1.a - Create some keys"
+ apply_manifest_on(agent, phase1, get_apply_opts) do
+ keys_created.each do |key_re|
+ assert_match(key_re, result.stdout,
+ "Expected #{key_re.inspect} to match the output. (First Run)")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Registry - Phase 1.b - Make sure Puppet is idempotent"
+ # Do a second run and make sure the key isn't created a second time.
+ apply_manifest_on(agent, phase1, get_apply_opts) do
+ keys_created.each do |key_re|
+ assert_no_match(key_re, result.stdout,
+ "Expected #{key_re.inspect} NOT to match the output. (First Run)")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Registry - Phase 2 - Make sure purge_values works"
+ apply_manifest_on(agent, phase2, get_apply_opts({'FACTER_FACT_PHASE' => '2'})) do
+ values_purged.each do |val_re|
+ assert_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Registry - Phase 3 - Clean up"
+ apply_manifest_on(agent, phase3, get_apply_opts({'FACTER_FACT_PHASE' => '3'})) do
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+ end
+end
+
+# clean_up
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_have_defined_type.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_have_defined_type.rb
new file mode 100644
index 000000000..183cb0b2d
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_have_defined_type.rb
@@ -0,0 +1,89 @@
+require 'pathname'
+require 'systest/util/registry'
+# Include our utility methods in the singleton class of the test case instance.
+class << self
+ include Systest::Util::Registry
+end
+
+test_name "registry::value defined type"
+
+# Generate a unique key name
+keyname = "PuppetLabsTest_MixedCase_#{randomstring(8)}"
+# This is the keypath we'll use for this entire test. We will actually create this key and delete it.
+vendor_path = "HKLM\\Software\\Vendor"
+keypath = "#{vendor_path}\\#{keyname}"
+
+manifest = < present }
+
+ registry::value { 'Setting1':
+ key => '#{keypath}',
+ value => 'Setting1',
+ data => "fact_phase=${fact_phase}",
+ }
+ registry::value { 'Setting2':
+ key => '#{keypath}',
+ data => "fact_phase=${fact_phase}",
+ }
+ registry::value { 'Setting3':
+ key => '#{keypath}',
+ value => 'Setting3',
+ data => "fact_phase=${fact_phase}",
+ }
+ registry::value { 'Setting0':
+ key => '#{keypath}',
+ value => '(default)',
+ data => "fact_phase=${fact_phase}",
+ }
+HERE
+
+step "Start testing should_have_defined_type" do
+ # A set of keys we expect Puppet to create
+ phase1_resources_created = [
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest\w+\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\Setting1\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\Setting2\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\Setting3\].ensure: created/,
+ ]
+
+ phase2_resources_changed = [
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\\].data: data changed 'fact_phase=1' to 'fact_phase=2'/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\Setting1\].data: data changed 'fact_phase=1' to 'fact_phase=2'/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\Setting2\].data: data changed 'fact_phase=1' to 'fact_phase=2'/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest\w+\\Setting3\].data: data changed 'fact_phase=1' to 'fact_phase=2'/,
+ ]
+ windows_agents.each do |agent|
+ step "Phase 1.a - Create some values"
+ apply_manifest_on agent, manifest, get_apply_opts({'FACTER_FACT_PHASE' => '1'}, agent_exit_codes) do
+ phase1_resources_created.each do |val_re|
+ assert_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Phase 1.b - Make sure Puppet is idempotent"
+ apply_manifest_on agent, manifest, get_apply_opts({'FACTER_FACT_PHASE' => '1'}, agent_exit_codes) do
+ phase1_resources_created.each do |val_re|
+ assert_no_match(val_re, result.stdout, "Expected output not to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Phase 2.a - Change some values"
+ apply_manifest_on agent, manifest, get_apply_opts({'FACTER_FACT_PHASE' => '2'}, agent_exit_codes) do
+ phase2_resources_changed.each do |val_re|
+ assert_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Phase 2.b - Make sure Puppet is idempotent"
+ apply_manifest_on agent, manifest, get_apply_opts({'FACTER_FACT_PHASE' => '2'}, agent_exit_codes) do
+ (phase1_resources_created + phase2_resources_changed).each do |val_re|
+ assert_no_match(val_re, result.stdout, "Expected output not to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_manage_values.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_manage_values.rb
new file mode 100644
index 000000000..d99faa381
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_manage_values.rb
@@ -0,0 +1,323 @@
+require 'pathname'
+require 'systest/util/registry'
+# Include our utility methods in the singleton class of the test case instance.
+class << self
+ include Systest::Util::Registry
+end
+
+test_name "Registry Value Management"
+
+# Generate a unique key name
+keyname = "PuppetLabsTest_Value_#{randomstring(8)}"
+# This is the keypath we'll use for this entire test. We will actually create this key and delete it.
+vendor_path = "HKLM\\Software\\Vendor"
+keypath = "#{vendor_path}\\#{keyname}"
+
+def getManifest(keypath, vendor_path, phase)
+ manifest = < "fact_phase: #{phase}" }
+ registry_key { '#{vendor_path}': ensure => present }
+ if $architecture == 'x64' {
+ registry_key { '32:#{vendor_path}': ensure => present }
+ }
+ Registry_key { ensure => present }
+ registry_key { '#{keypath}': }
+ registry_key { '#{keypath}\\SubKey1': }
+ registry_key { '#{keypath}\\SubKey2': }
+ if $architecture == 'x64' {
+ registry_key { '32:#{keypath}': }
+ registry_key { '32:#{keypath}\\SubKey1': }
+ registry_key { '32:#{keypath}\\SubKey2': }
+ }
+
+ # The Default Value
+ registry_value { '#{keypath}\\SubKey1\\\\':
+ data => "Default Data phase=#{phase}",
+ }
+ registry_value { '#{keypath}\\SubKey2\\\\':
+ type => array,
+ data => [ "Default Data L1 phase=#{phase}", "Default Data L2 phase=#{phase}" ],
+ }
+
+ # String Values
+ registry_value { '#{keypath}\\SubKey1\\ValueString1':
+ data => "Should be a string phase=#{phase}",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueString2':
+ type => string,
+ data => "Should be a string phase=#{phase}",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueString3':
+ ensure => present,
+ type => string,
+ data => "Should be a string phase=#{phase}",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueString4':
+ data => "Should be a string phase=#{phase}",
+ type => string,
+ ensure => present,
+ }
+
+ if $architecture == 'x64' {
+ # String Values
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString1':
+ data => "Should be a string phase=#{phase}",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString2':
+ type => string,
+ data => "Should be a string phase=#{phase}",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString3':
+ ensure => present,
+ type => string,
+ data => "Should be a string phase=#{phase}",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString4':
+ data => "Should be a string phase=#{phase}",
+ type => string,
+ ensure => present,
+ }
+ }
+
+ # Array Values
+ registry_value { '#{keypath}\\SubKey1\\ValueArray1':
+ type => array,
+ data => "Should be an array L1 phase=#{phase}",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueArray2':
+ type => array,
+ data => [ "Should be an array L1 phase=#{phase}" ],
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueArray3':
+ type => array,
+ data => [ "Should be an array L1 phase=#{phase}",
+ "Should be an array L2 phase=#{phase}" ],
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueArray4':
+ ensure => present,
+ type => array,
+ data => [ "Should be an array L1 phase=#{phase}",
+ "Should be an array L2 phase=#{phase}" ],
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueArray5':
+ data => [ "Should be an array L1 phase=#{phase}",
+ "Should be an array L2 phase=#{phase}" ],
+ type => array,
+ ensure => present,
+ }
+ if $architecture == 'x64' {
+ registry_value { '32:#{keypath}\\SubKey1\\ValueArray1':
+ type => array,
+ data => "Should be an array L1 phase=#{phase}",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueArray2':
+ type => array,
+ data => [ "Should be an array L1 phase=#{phase}" ],
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueArray3':
+ type => array,
+ data => [ "Should be an array L1 phase=#{phase}",
+ "Should be an array L2 phase=#{phase}" ],
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueArray4':
+ ensure => present,
+ type => array,
+ data => [ "Should be an array L1 phase=#{phase}",
+ "Should be an array L2 phase=#{phase}" ],
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueArray5':
+ data => [ "Should be an array L1 phase=#{phase}",
+ "Should be an array L2 phase=#{phase}" ],
+ type => array,
+ ensure => present,
+ }
+ }
+
+ # Expand Values
+ registry_value { '#{keypath}\\SubKey1\\ValueExpand1':
+ type => expand,
+ data => "%SystemRoot% - Should be a REG_EXPAND_SZ phase=#{phase}",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueExpand2':
+ type => expand,
+ data => "%SystemRoot% - Should be a REG_EXPAND_SZ phase=#{phase}",
+ ensure => present,
+ }
+ if $architecture == 'x64' {
+ registry_value { '32:#{keypath}\\SubKey1\\ValueExpand1':
+ type => expand,
+ data => "%SystemRoot% - Should be a REG_EXPAND_SZ phase=#{phase}",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueExpand2':
+ type => expand,
+ data => "%SystemRoot% - Should be a REG_EXPAND_SZ phase=#{phase}",
+ ensure => present,
+ }
+ }
+
+ # DWORD Values
+ registry_value { '#{keypath}\\SubKey1\\ValueDword1':
+ type => dword,
+ data => #{phase},
+ }
+ if $architecture == 'x64' {
+ registry_value { '32:#{keypath}\\SubKey1\\ValueDword1':
+ type => dword,
+ data => #{phase},
+ }
+ }
+
+ # QWORD Values
+ registry_value { '#{keypath}\\SubKey1\\ValueQword1':
+ type => qword,
+ data => #{phase},
+ }
+ if $architecture == 'x64' {
+ registry_value { '32:#{keypath}\\SubKey1\\ValueQword1':
+ type => qword,
+ data => #{phase},
+ }
+ }
+
+ # Binary Values
+ registry_value { '#{keypath}\\SubKey1\\ValueBinary1':
+ type => binary,
+ data => "#{phase}",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueBinary2':
+ type => binary,
+ data => "DE AD BE EF CA F#{phase}"
+ }
+ if $architecture == 'x64' {
+ registry_value { '32:#{keypath}\\SubKey1\\ValueBinary1':
+ type => binary,
+ data => "0#{phase}",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueBinary2':
+ type => binary,
+ data => "DEAD BEEF CAF#{phase}"
+ }
+ }
+P1
+end
+
+step "Start testing should_manage_values" do
+ windows_agents.each do |agent|
+ x64 = x64?(agent)
+
+ # A set of keys we expect Puppet to create
+ phase1_resources_created = [
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest\w+\].ensure: created/,
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\].ensure: created/,
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey2\].ensure: created/,
+ ]
+
+ if x64
+ phase1_resources_created += [
+ /Registry_key\[32:HKLM.Software.Vendor.PuppetLabsTest\w+\].ensure: created/,
+ /Registry_key\[32:HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\].ensure: created/,
+ /Registry_key\[32:HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey2\].ensure: created/,
+ ]
+ end
+
+ # A set of values we expect Puppet to change in Phase 2
+ phase2_resources_changed = Array.new
+
+ prefixes = ['']
+ prefixes << '32:' if x64
+
+ # This is just to save a whole bunch of copy / paste
+ prefixes.each do |prefix|
+ # We should have created 4 REG_SZ values
+ 1.upto(4).each do |idx|
+ phase1_resources_created << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueString#{idx}\].ensure: created/
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueString#{idx}\].data: data changed 'Should be a string phase=1' to 'Should be a string phase=2'/
+ end
+ # We should have created 5 REG_MULTI_SZ values
+ 1.upto(5).each do |idx|
+ phase1_resources_created << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueArray#{idx}\].ensure: created/
+ end
+
+ # The first two array items are an exception
+ 1.upto(2).each do |idx|
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueArray#{idx}\].data: data changed 'Should be an array L1 phase=1' to 'Should be an array L1 phase=2'/
+ end
+
+ # The rest of the array items are OK and have 2 "lines" each.
+ 3.upto(5).each do |idx|
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueArray#{idx}\].data: data changed 'Should be an array L1 phase=1,Should be an array L2 phase=1' to 'Should be an array L1 phase=2,Should be an array L2 phase=2'/
+ end
+
+ # We should have created 2 REG_EXPAND_SZ values
+ 1.upto(2).each do |idx|
+ phase1_resources_created << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueExpand#{idx}\].ensure: created/
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueExpand#{idx}\].data: data changed '%SystemRoot% - Should be a REG_EXPAND_SZ phase=1' to '%SystemRoot% - Should be a REG_EXPAND_SZ phase=2'/
+ end
+ # We should have created 1 qword
+ 1.upto(1).each do |idx|
+ phase1_resources_created << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueQword#{idx}\].ensure: created/
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueQword#{idx}\].data: data changed '1' to '2'/
+ end
+ # We should have created 1 dword
+ 1.upto(1).each do |idx|
+ phase1_resources_created << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueDword#{idx}\].ensure: created/
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueDword#{idx}\].data: data changed '1' to '2'/
+ end
+ # We should have created 2 binary values
+ 1.upto(2).each do |idx|
+ phase1_resources_created << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueBinary#{idx}\].ensure: created/
+ end
+ # We have different data for the binary values
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueBinary1\].data: data changed '01' to '02'/
+ phase2_resources_changed << /Registry_value\[#{prefix}HKLM.Software.Vendor.PuppetLabsTest\w+\\SubKey1\\ValueBinary2\].data: data changed 'de ad be ef ca f1' to 'de ad be ef ca f2'/
+ end
+
+
+ step "Registry Values - Phase 1.a - Create some values"
+ apply_manifest_on agent, getManifest(keypath, vendor_path,'1'), get_apply_opts do
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ phase1_resources_created.each do |val_re|
+ assert_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ end
+
+ step "Registry Values - Phase 1.b - Make sure Puppet is idempotent"
+ apply_manifest_on agent, getManifest(keypath, vendor_path,'1'), get_apply_opts do
+ phase1_resources_created.each do |val_re|
+ assert_no_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Registry Values - Phase 2.a - Change some values"
+ apply_manifest_on agent, getManifest(keypath, vendor_path, '2'), get_apply_opts do
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ phase2_resources_changed.each do |val_re|
+ assert_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ end
+
+ step "Registry Values - Phase 2.b - Make sure Puppet is idempotent"
+ apply_manifest_on agent, getManifest(keypath, vendor_path,'2'), get_apply_opts do
+ phase2_resources_changed.each do |val_re|
+ assert_no_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+
+ step "Registry Values - Phase 3 - Check the default value (#14572)"
+ # (#14572) This test uses the 'native' version of reg.exe to read the
+ # default value of a registry key. It should contain the string shown in
+ # val_re.
+ dir = native_sysdir(agent)
+ if not dir
+ Log.warn("Cannot query 64-bit view of registry from 32-bit process, skipping")
+ else
+ on agent, "#{dir}/reg.exe query '#{keypath}\\Subkey1'" do
+ val_re = /\(Default\) REG_SZ Default Data phase=2/i
+ assert_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ end
+ end
+end
+
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_tolerate_mixed_case.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_tolerate_mixed_case.rb
new file mode 100644
index 000000000..c9f054bae
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/acceptance/tests/resource/registry/should_tolerate_mixed_case.rb
@@ -0,0 +1,158 @@
+require 'pathname'
+require 'systest/util/registry'
+# Include our utility methods in the singleton class of the test case instance.
+class << self
+ include Systest::Util::Registry
+end
+
+test_name "Registry Value Management (Mixed Case)"
+
+# JJM - The whole purpose of this test is to exercise situations where the user
+# specifies registry valies in a case-insensitive but case-preserving way.
+# This has particular "gotchas" with regard to autorequire.
+#
+# Note how SUBKEY1 is capitalized in some resources but downcased in other
+# resources. On windows these refer to the same thing, and case is preserved.
+# In puppet, however, resource namevars are case sensisitve unless otherwise
+# noted.
+
+# Generate a unique key name
+keyname = "PuppetLabsTest_MixedCase_#{randomstring(8)}"
+# This is the keypath we'll use for this entire test. We will actually create this key and delete it.
+vendor_path = "HKLM\\Software\\Vendor"
+keypath = "#{vendor_path}\\#{keyname}"
+
+phase1 = < "fact_phase: $fact_phase" }
+
+ registry_key { '#{vendor_path}': ensure => present }
+ if $architecture == 'x64' {
+ registry_key { '32:#{vendor_path}': ensure => present }
+ }
+
+ Registry_key { ensure => present }
+ registry_key { '#{keypath}': }
+ registry_key { '#{keypath}\\SUBKEY1': }
+ # NOTE THE DIFFERENCE IN CASE IN SubKey1 and SUBKEY1 above
+ registry_key { '#{keypath}\\SubKey1\\SUBKEY2': }
+ if $architecture == 'x64' {
+ registry_key { '32:#{keypath}': }
+ registry_key { '32:#{keypath}\\SUBKEY1': }
+ registry_key { '32:#{keypath}\\SubKey1\\SUBKEY2': }
+ }
+
+ # The Default Value
+ # NOTE THE DIFFERENCE IN CASE IN SubKey1 and SUBKEY1 above
+ registry_value { '#{keypath}\\SubKey1\\\\':
+ data => "Default Data",
+ }
+
+ # String Values
+ registry_value { '#{keypath}\\SubKey1\\ValueString1':
+ data => "Should be a string",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueString2':
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueString3':
+ ensure => present,
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '#{keypath}\\SubKey1\\ValueString4':
+ data => "Should be a string",
+ type => string,
+ ensure => present,
+ }
+ registry_value { '#{keypath}\\SubKey1\\SubKey2\\ValueString1':
+ data => "Should be a string",
+ }
+ registry_value { '#{keypath}\\SubKey1\\SubKey2\\ValueString2':
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '#{keypath}\\SubKey1\\SubKey2\\ValueString3':
+ ensure => present,
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '#{keypath}\\SubKey1\\SubKey2\\ValueString4':
+ data => "Should be a string",
+ type => string,
+ ensure => present,
+ }
+
+ # The Default Value
+ # NOTE THE DIFFERENCE IN CASE IN SubKey1 and SUBKEY1 above
+ if $architecture == 'x64' {
+ registry_value { '32:#{keypath}\\SubKey1\\\\':
+ data => "Default Data",
+ }
+ # String Values
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString1':
+ data => "Should be a string",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString2':
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString3':
+ ensure => present,
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\ValueString4':
+ data => "Should be a string",
+ type => string,
+ ensure => present,
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\SubKey2\\ValueString1':
+ data => "Should be a string",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\SubKey2\\ValueString2':
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\SubKey2\\ValueString3':
+ ensure => present,
+ type => string,
+ data => "Should be a string",
+ }
+ registry_value { '32:#{keypath}\\SubKey1\\SubKey2\\ValueString4':
+ data => "Should be a string",
+ type => string,
+ ensure => present,
+ }
+ }
+HERE
+
+step "Start the master" do
+ # A set of keys we expect Puppet to create
+ phase1_resources_created = [
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\].ensure: created/,
+ /Registry_key\[HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\\SUBKEY1\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\\SubKey1\\\W+.ensure: created.*$/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\\SubKey1\\ValueString1\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\\SubKey1\\ValueString2\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\\SubKey1\\ValueString3\].ensure: created/,
+ /Registry_value\[HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\\SubKey1\\ValueString4\].ensure: created/,
+ ]
+ windows_agents.each do |agent|
+
+ if x64?(agent)
+ phase1_resources_created += [
+ /Registry_key\[32:HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\].ensure: created/,
+ /Registry_key\[32:HKLM.Software.Vendor.PuppetLabsTest_MixedCase_\w+\\SUBKEY1\].ensure: created/,
+ ]
+ end
+
+ step "Registry Tolerate Mixed Case Values - Phase 1.a - Create some values"
+ apply_manifest_on agent, phase1, get_apply_opts({'FACTER_FACT_PHASE' => '1'}) do |result|
+ phase1_resources_created.each do |val_re|
+ assert_match(val_re, result.stdout, "Expected output to contain #{val_re.inspect}.")
+ end
+ assert_no_match(/err:/, result.stdout, "Expected no error messages.")
+ end
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/appveyor.yml b/modules/utilities/windows/registry/puppetlabs_registry_library/appveyor.yml
new file mode 100644
index 000000000..2b56e44bf
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/appveyor.yml
@@ -0,0 +1,42 @@
+version: 1.1.x.{build}
+skip_commits:
+ message: /^\(?doc\)?.*/
+clone_depth: 10
+init:
+- SET
+- 'mkdir C:\ProgramData\PuppetLabs\code && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\facter && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\hiera && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\puppet\var && exit 0'
+environment:
+ matrix:
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 21
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 21-x64
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 23
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 23-x64
+ - PUPPET_GEM_VERSION: 4.2.3
+ RUBY_VER: 21-x64
+ - PUPPET_GEM_VERSION: 4.2.3
+ RUBY_VER: 21-x64
+matrix:
+ fast_finish: true
+install:
+- SET PATH=C:\Ruby%RUBY_VER%\bin;%PATH%
+- bundle install --jobs 4 --retry 2 --without system_tests
+- type Gemfile.lock
+build: off
+test_script:
+- bundle exec puppet -V
+- ruby -v
+- bundle exec rspec spec/unit -fd -b
+notifications:
+- provider: Email
+ to:
+ - nobody@nowhere.com
+ on_build_success: false
+ on_build_failure: false
+ on_build_status_changed: false
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/checksums.json b/modules/utilities/windows/registry/puppetlabs_registry_library/checksums.json
new file mode 100644
index 000000000..0163c7c62
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/checksums.json
@@ -0,0 +1,52 @@
+{
+ "CHANGELOG.md": "05c84205bd442d24aafa3bfbce36bc61",
+ "CONTRIBUTING.md": "77d0440d7cd4206497f99065c60bed46",
+ "Gemfile": "684d107a16e673f1c793f91325229112",
+ "LICENSE": "3b83ef96387f14655fc854ddc3c6bd57",
+ "MAINTAINERS.md": "761ee685b9d6edf562e6b9af2dcec8aa",
+ "NOTICE": "b3b228a5c87f40c082b72477fe6fceca",
+ "README.markdown": "d1dc9c6628a8628a39c5b5da16b9c26c",
+ "Rakefile": "ffc248460ba5c68e8219ac2fc572b940",
+ "acceptance/config/masterless-windows-2008-x86_64.cfg": "2374d99e46fd225ae577449643f0f402",
+ "acceptance/config/masterless-windows-2008r2-x86_64.cfg": "d39479dcf34753cb71f3d737938ac6f5",
+ "acceptance/config/masterless-windows-2012-x86_64.cfg": "6d76b8c217a0e11db888c20ecc025393",
+ "acceptance/config/masterless-windows-2012r2-x86_64.cfg": "e8315390a155f8a1ce6b4ec231f2f0e4",
+ "acceptance/config/windows-2003r2-i386.cfg": "f97a32f5352765601954a423c60ae09e",
+ "acceptance/config/windows-2003r2-x86_64.cfg": "22305912f5b5d397676676ebec03b1d1",
+ "acceptance/config/windows-2008r2-x86_64.cfg": "7663d1d682f54c195f26d17e72a0d900",
+ "acceptance/config/windows-2012-x86_64.cfg": "1c30068e296370d06157f3995e8acd61",
+ "acceptance/lib/systest/util/registry.rb": "3066ce6a31b989638d23dd86a766621d",
+ "acceptance/lib/systest/util.rb": "5fe316915896e133129bec03cfeb6f3c",
+ "acceptance/lib/systest.rb": "08cde457e07db7b82a1ce9ff502dd495",
+ "acceptance/setup/install_puppet.rb": "ca5f676e62a616a095e54891146cfbb7",
+ "acceptance/tests/resource/registry/should_create_key.rb": "277bc3e05e3acc271589df726b598941",
+ "acceptance/tests/resource/registry/should_have_defined_type.rb": "20fcafaf9188994d72d5dc5a56eba19a",
+ "acceptance/tests/resource/registry/should_manage_values.rb": "57fe966179f46188f4c8c0e96ef00af1",
+ "acceptance/tests/resource/registry/should_tolerate_mixed_case.rb": "23ad59a08e7f5b78b6f75dbef66f2011",
+ "appveyor.yml": "179cf97e405a1a2872f349c18922614f",
+ "examples/compliance_example.pp": "adb9ac7990857d5ec4fe046ffe7ee3ad",
+ "examples/purge_example.pp": "09152400a320825852cd9d496041c924",
+ "examples/registry_examples.pp": "28fe1b04385d91f301880920c7f343c0",
+ "examples/service_example.pp": "a19aa627ae1aefd684ff02473459b58a",
+ "lib/puppet/provider/registry_key/registry.rb": "5c26c6cbb1669a01361e69fadbbb408d",
+ "lib/puppet/provider/registry_value/registry.rb": "92b54f65f5be3c130681cbb04b216e09",
+ "lib/puppet/type/registry_key.rb": "bcf74b3a991cafdae54514b3c3c4a38c",
+ "lib/puppet/type/registry_value.rb": "140295468b773a7ad709a532e496005c",
+ "lib/puppet_x/puppetlabs/registry/provider_base.rb": "76fb5dc01fdbf0358719dfa64ce778f9",
+ "lib/puppet_x/puppetlabs/registry.rb": "8fc2b92a8362c6e5a95951cb36b02103",
+ "manifests/service.pp": "9028f47d1fa41da7f6bca05761f7303f",
+ "manifests/value.pp": "230398fc31dacc5aa5ffc0fb1696a888",
+ "metadata.json": "113bc4c833d15867250364ba63d13ba5",
+ "spec/acceptance/nodesets/centos-7-x64.yml": "a713f3abd3657f0ae2878829badd23cd",
+ "spec/acceptance/nodesets/debian-8-x64.yml": "d2d2977900989f30086ad251a14a1f39",
+ "spec/acceptance/nodesets/default.yml": "b42da5a1ea0c964567ba7495574b8808",
+ "spec/acceptance/nodesets/docker/centos-7.yml": "8a3892807bdd62306ae4774f41ba11ae",
+ "spec/acceptance/nodesets/docker/debian-8.yml": "ac8e871d1068c96de5e85a89daaec6df",
+ "spec/acceptance/nodesets/docker/ubuntu-14.04.yml": "dc42ee922a96908d85b8f0f08203ce58",
+ "spec/spec_helper.rb": "1786dac4fb79434afde64a0da40d415e",
+ "spec/unit/puppet/provider/registry_key_spec.rb": "3360361b6d5599933f9ca8d06524fa52",
+ "spec/unit/puppet/provider/registry_value_spec.rb": "86f6c1e160ae09682cf9cdc27d33b046",
+ "spec/unit/puppet/type/registry_key_spec.rb": "6b6c30fa62ac774407eb1c274e5c86a4",
+ "spec/unit/puppet/type/registry_value_spec.rb": "acca25c1598cb08bcfdd6e1d00f420b8",
+ "spec/watchr.rb": "0d23eac3b37babe4229307850cfc4240"
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/examples/compliance_example.pp b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/compliance_example.pp
new file mode 100644
index 000000000..a03c37c56
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/compliance_example.pp
@@ -0,0 +1,111 @@
+# = Class: registry::compliance_example
+#
+# This class provides an example of how to use the audit metaparameter to
+# inspect registry_key and registry_value resources with the Compliance
+# feature of Puppet Enterprise.
+#
+# = Parameters
+#
+# = Actions
+#
+# = Requires
+#
+# = Sample Usage
+#
+# include registry::compliance_example
+#
+# (MARKUP: http://links.puppetlabs.com/puppet_manifest_documentation)
+class registry::compliance_example {
+ $key_path = 'HKLM\Software\Vendor\Puppet Labs\Examples\Compliance'
+
+ case $::registry_compliance_example_mode {
+ audit: {
+ $mode = 'audit'
+ }
+ default: {
+ $mode = 'setup'
+ notify { 'compliance_example_mode_info':
+ message => 'Switch to audit mode using
+ \$env:FACTER_REGISTRY_COMPLIANCE_EXAMPLE_MODE = \'audit\'',
+ before => Notify['compliance_example_mode']
+ }
+ }
+ }
+
+ notify { 'compliance_example_mode':
+ message => "Registry compliance example mode: ${mode}",
+ }
+
+ # Resource Defaults
+ Registry_key {
+ ensure => $mode ? {
+ setup => present,
+ default => undef
+ },
+ purge_values => $mode ? {
+ setup => true,
+ default => false,
+ },
+ }
+ Registry_value {
+ ensure => $mode ? {
+ setup => present,
+ default => undef,
+ },
+ type => $mode ? {
+ setup => string,
+ default => undef,
+ },
+ data => $mode ? {
+ setup => 'Puppet Default Data',
+ default => undef,
+ },
+ audit => $mode ? {
+ setup => undef,
+ default => all,
+ },
+ }
+
+ # Create the nested key structure we want to audit. The resource defaults
+ # will determine the properties managed or audited.
+ registry_key { $key_path: }
+ registry_key { "${key_path}\\SubKeyA": }
+ registry_key { "${key_path}\\SubKeyA\\SubKeyA1": }
+ registry_key { "${key_path}\\SubKeyA\\SubKeyA2": }
+ registry_key { "${key_path}\\SubKeyB": }
+ registry_key { "${key_path}\\SubKeyB\\SubKeyB1": }
+ registry_key { "${key_path}\\SubKeyB\\SubKeyB2": }
+ registry_key { "${key_path}\\SubKeyC": }
+ registry_key { "${key_path}\\SubKeyC\\SubKeyC1": }
+ registry_key { "${key_path}\\SubKeyC\\SubKeyC2": }
+ registry_value { "${key_path}\\Value1": }
+ registry_value { "${key_path}\\Value2": }
+ registry_value { "${key_path}\\Value3": }
+ registry_value { "${key_path}\\SubKeyA\\ValueA1": }
+ registry_value { "${key_path}\\SubKeyA\\ValueA2": }
+ registry_value { "${key_path}\\SubKeyA\\ValueA3": }
+ registry_value { "${key_path}\\SubKeyB\\ValueB1": }
+ registry_value { "${key_path}\\SubKeyB\\ValueB2": }
+ registry_value { "${key_path}\\SubKeyB\\ValueB3": }
+ registry_value { "${key_path}\\SubKeyC\\ValueC1": }
+ registry_value { "${key_path}\\SubKeyC\\ValueC2": }
+ registry_value { "${key_path}\\SubKeyC\\ValueC3": }
+ registry_value { "${key_path}\\SubKeyA\\SubKeyA1\\ValueA1X": }
+ registry_value { "${key_path}\\SubKeyA\\SubKeyA1\\ValueA1Y": }
+ registry_value { "${key_path}\\SubKeyA\\SubKeyA1\\ValueA1Z": }
+ registry_value { "${key_path}\\SubKeyA\\SubKeyA2\\ValueA2X": }
+ registry_value { "${key_path}\\SubKeyA\\SubKeyA2\\ValueA2Y": }
+ registry_value { "${key_path}\\SubKeyA\\SubKeyA2\\ValueA2Z": }
+ registry_value { "${key_path}\\SubKeyB\\SubKeyB1\\ValueB1X": }
+ registry_value { "${key_path}\\SubKeyB\\SubKeyB1\\ValueB1Y": }
+ registry_value { "${key_path}\\SubKeyB\\SubKeyB1\\ValueB1Z": }
+ registry_value { "${key_path}\\SubKeyB\\SubKeyB2\\ValueB2X": }
+ registry_value { "${key_path}\\SubKeyB\\SubKeyB2\\ValueB2Y": }
+ registry_value { "${key_path}\\SubKeyB\\SubKeyB2\\ValueB2Z": }
+ registry_value { "${key_path}\\SubKeyC\\SubKeyC1\\ValueC1X": }
+ registry_value { "${key_path}\\SubKeyC\\SubKeyC1\\ValueC1Y": }
+ registry_value { "${key_path}\\SubKeyC\\SubKeyC1\\ValueC1Z": }
+ registry_value { "${key_path}\\SubKeyC\\SubKeyC2\\ValueC2X": }
+ registry_value { "${key_path}\\SubKeyC\\SubKeyC2\\ValueC2Y": }
+ registry_value { "${key_path}\\SubKeyC\\SubKeyC2\\ValueC2Z": }
+}
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/examples/purge_example.pp b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/purge_example.pp
new file mode 100644
index 000000000..48e4783f7
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/purge_example.pp
@@ -0,0 +1,119 @@
+# = Class: registry::purge_example
+#
+# This class provides an example of how to purge registry values associated
+# with a specific key.
+#
+# This class has two modes of operation determined by the Facter fact
+# PURGE_EXAMPLE_MODE The value of this fact can be either 'setup' or 'purge'
+#
+# The easiest way to set this mode is to set an
+# environment variable in Power Shell:
+#
+# The setup mode creates a registry key and 6 values.
+#
+# `$env:FACTER_PURGE_EXAMPLE_MODE = "setup"`
+# `puppet agent --test`
+#
+# The purge mode manages the key with purge_values => true and manages only 3
+# of the 6 values. The other 3 values will be automatically purged.
+#
+# `$env:FACTER_PURGE_EXAMPLE_MODE = "purge"`
+# `puppet agent --test`
+#
+# = Parameters
+#
+# = Actions
+#
+# = Requires
+#
+# = Sample Usage
+#
+# include registry::purge_example
+#
+# (MARKUP: http://links.puppetlabs.com/puppet_manifest_documentation)
+class registry::purge_example {
+
+ $key_path = 'HKLM\Software\Vendor\Puppet Labs\Examples\KeyPurge'
+
+ case $::purge_example_mode {
+ setup: {
+ registry_key { $key_path:
+ ensure => present,
+ purge_values => false,
+ }
+ registry_key { "${key_path}\\SubKey":
+ ensure => present,
+ purge_values => false,
+ }
+ registry_value { "${key_path}\\SubKey\\Value1":
+ ensure => present,
+ type => dword,
+ data => 1,
+ }
+ registry_value { "${key_path}\\SubKey\\Value2":
+ ensure => present,
+ type => dword,
+ data => 1,
+ }
+ registry_value { "${key_path}\\Value1":
+ ensure => present,
+ type => dword,
+ data => 1,
+ }
+ registry_value { "${key_path}\\Value2":
+ ensure => present,
+ type => dword,
+ data => 2,
+ }
+ registry_value { "${key_path}\\Value3":
+ ensure => present,
+ type => string,
+ data => 'key3',
+ }
+ registry_value { "${key_path}\\Value4":
+ ensure => present,
+ type => array,
+ data => [ 'one', 'two', 'three' ],
+ }
+ registry_value { "${key_path}\\Value5":
+ ensure => present,
+ type => expand,
+ data => '%SystemRoot%\system32',
+ }
+ registry_value { "${key_path}\\Value6":
+ ensure => present,
+ type => binary,
+ data => '01AB CDEF',
+ }
+ }
+ purge: {
+ registry_key { $key_path:
+ ensure => present,
+ purge_values => true,
+ }
+ registry_value { "${key_path}\\Value1":
+ ensure => present,
+ type => dword,
+ data => 0,
+ }
+ registry_value { "${key_path}\\Value2":
+ ensure => present,
+ type => dword,
+ data => 0,
+ }
+ registry_value { "${key_path}\\Value3":
+ ensure => present,
+ type => string,
+ data => 'should not be purged',
+ }
+ }
+ default: {
+ notify { 'purge_example_notice':
+ message => 'The purge_example_mode fact is not set. To try this
+ example class first set \$env:FACTER_PURGE_EXAMPLE_MODE = \'setup\' then
+ run puppet agent, then set \$env:FACTER_PURGE_EXAMPLE_MODE = \'purge\'
+ and run puppet agent again to see the values purged.',
+ }
+ }
+ }
+}
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/examples/registry_examples.pp b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/registry_examples.pp
new file mode 100644
index 000000000..3f4496854
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/registry_examples.pp
@@ -0,0 +1,87 @@
+# = Class: registry_example
+#
+# This is an example of how to manage registry keys and values.
+#
+# = Parameters
+#
+# = Actions
+#
+# = Requires
+#
+# = Sample Usage
+#
+# include registry_example
+#
+# (MARKUP: http://links.puppetlabs.com/puppet_manifest_documentation)
+class registry_example {
+ registry_key { 'HKLM\Software\Vendor':
+ ensure => present,
+ }
+
+ # This should trigger a duplicate resource with HKLM
+ # registry_key { 'HKEY_LOCAL_MACHINE\Software\Vendor':
+ # ensure => present,
+ # }
+
+ registry_key { 'HKLM\Software\Vendor\Bar':
+ ensure => present,
+ }
+
+ registry_value { 'HKLM\Software\Vendor\Bar\valuedword2':
+ ensure => present,
+ type => dword,
+ data => 0xFFFFFFFF,
+ }
+
+ registry_value { 'HKLM\Software\Vendor\Bar\valueqword1':
+ ensure => present,
+ type => qword,
+ data => 100,
+ }
+
+ registry_value { 'HKLM\Software\Vendor\Bar\valuedstring1':
+ ensure => present,
+ type => string,
+ data => 'this is a string',
+ }
+
+ registry_value { 'HKLM\Software\Vendor\Bar\valuedexpand1':
+ ensure => present,
+ type => expand,
+ data => '%windir%\system32',
+ }
+
+ registry_value { 'HKLM\Software\Vendor\Bar\valuedbinary1':
+ ensure => present,
+ type => binary,
+ data => 'DE AD BE EF',
+ }
+
+ registry_value { 'HKLM\Software\Vendor\Bar\valuedbinary2':
+ ensure => present,
+ type => binary,
+ data => 'CAFEBEEF',
+ }
+
+ registry_value { 'HKLM\Software\Vendor\Bar\valuearray1':
+ ensure => present,
+ type => array,
+ data => [ 'one', 'two', 'three' ],
+ }
+
+ $some_string = "somestring"
+ registry_value { 'HKLM\Software\Vendor\Bar\valuearray2':
+ ensure => present,
+ type => array,
+ data => [ 0, 'zero', '0', 123456, 'two', $some_string ],
+ }
+
+ $some_array = [ "array1", "array2", "array3" ]
+ registry_value { 'HKLM\Software\Vendor\Bar\valuearray3':
+ ensure => present,
+ type => array,
+ data => $some_array,
+ }
+}
+
+include registry_example
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/examples/service_example.pp b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/service_example.pp
new file mode 100644
index 000000000..60a70e188
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/examples/service_example.pp
@@ -0,0 +1,36 @@
+# = Class: registry::service_example
+#
+# This is an example of how to use the registry::service defined resource
+# type included in this module.
+#
+# = Parameters
+#
+# = Actions
+#
+# = Requires
+#
+# = Sample Usage
+#
+# include registry::service_example
+#
+#
+# (MARKUP: http://links.puppetlabs.com/puppet_manifest_documentation)
+class registry::service_example {
+ # Define a new service named "Puppet Test" that is disabled.
+ registry::service { 'PuppetExample1':
+ display_name => 'Puppet Example 1',
+ description =>
+ 'This is a simple example managing the
+ registry entries for a Windows Service',
+ command => 'C:\PuppetExample1.bat',
+ start => 'disabled',
+ }
+ registry::service { 'PuppetExample2':
+ display_name => 'Puppet Example 2',
+ description =>
+ 'This is a simple example
+ managing the registry entries for a Windows Service',
+ command => 'C:\PuppetExample2.bat',
+ start => 'disabled',
+ }
+}
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/provider/registry_key/registry.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/provider/registry_key/registry.rb
new file mode 100644
index 000000000..ea49a02e3
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/provider/registry_key/registry.rb
@@ -0,0 +1,65 @@
+# REMIND: need to support recursive delete of subkeys & values
+begin
+ # We expect this to work once Puppet supports Rubygems in #7788
+ require "puppet_x/puppetlabs/registry"
+ require "puppet_x/puppetlabs/registry/provider_base"
+rescue LoadError => detail
+ # Work around #7788 (Rubygems support for modules)
+ require 'pathname' # JJM WORK_AROUND #14073
+ module_base = Pathname.new(__FILE__).dirname
+ require module_base + "../../../" + "puppet_x/puppetlabs/registry"
+ require module_base + "../../../" + "puppet_x/puppetlabs/registry/provider_base"
+end
+
+Puppet::Type.type(:registry_key).provide(:registry) do
+ include PuppetX::Puppetlabs::Registry::ProviderBase
+
+ defaultfor :operatingsystem => :windows
+ confine :operatingsystem => :windows
+
+ def self.instances
+ hkeys.keys.collect do |hkey|
+ new(:provider => :registry, :name => "#{hkey.to_s}")
+ end
+ end
+
+ def create
+ Puppet.debug("Creating registry key #{self}")
+ hive.create(subkey, Win32::Registry::KEY_ALL_ACCESS | access) {|reg| true }
+ end
+
+ def exists?
+ Puppet.debug("Checking existence of registry key #{self}")
+ !!hive.open(subkey, Win32::Registry::KEY_READ | access) {|reg| true } rescue false
+ end
+
+ def destroy
+ Puppet.debug("Destroying registry key #{self}")
+
+ raise ArgumentError, "Cannot delete root key: #{path}" unless subkey
+
+ from_string_to_wide_string(subkey) do |subkey_ptr|
+ # hive.hkey returns an integer value that's like a FD
+ if RegDeleteKeyExW(hive.hkey, subkey_ptr, access, 0) != 0
+ raise "Failed to delete registry key: #{self}"
+ end
+ end
+ end
+
+ def values
+ names = []
+ # Only try and get the values for this key if the key itself exists.
+ if exists? then
+ hive.open(subkey, Win32::Registry::KEY_READ | access) do |reg|
+ each_value(reg) do |name, type, data| names << name end
+ end
+ end
+ names
+ end
+
+ private
+
+ def path
+ @path ||= PuppetX::Puppetlabs::Registry::RegistryKeyPath.new(resource.parameter(:path).value)
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/provider/registry_value/registry.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/provider/registry_value/registry.rb
new file mode 100644
index 000000000..0b6897cb6
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/provider/registry_value/registry.rb
@@ -0,0 +1,219 @@
+require 'puppet/type'
+begin
+ require "puppet_x/puppetlabs/registry"
+ require "puppet_x/puppetlabs/registry/provider_base"
+rescue LoadError => detail
+ require "pathname" # JJM WORK_AROUND #14073 and #7788
+ module_base = Pathname.new(__FILE__).dirname + "../../../"
+ require module_base + "puppet_x/puppetlabs/registry"
+ require module_base + "puppet_x/puppetlabs/registry/provider_base"
+end
+
+Puppet::Type.type(:registry_value).provide(:registry) do
+ include PuppetX::Puppetlabs::Registry::ProviderBase
+
+ defaultfor :operatingsystem => :windows
+ confine :operatingsystem => :windows
+
+ def self.instances
+ []
+ end
+
+ def exists?
+ Puppet.debug("Checking the existence of registry value: #{self}")
+ found = false
+ begin
+ hive.open(subkey, Win32::Registry::KEY_READ | access) do |reg|
+ from_string_to_wide_string(valuename) do |valuename_ptr|
+ status = RegQueryValueExW(reg.hkey, valuename_ptr,
+ FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL,
+ FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL)
+
+ found = status == 0
+ raise Win32::Registry::Error.new(status) if !found
+ end
+ end
+ rescue Win32::Registry::Error => detail
+ case detail.code
+ when 2
+ # Code 2 is the error message for "The system cannot find the file specified."
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx
+ found = false
+ else
+ error = Puppet::Error.new("Unexpected exception from Win32 API. detail: (#{detail.message}) ERROR CODE: #{detail.code}. Puppet Error ID: D4B679E4-0E22-48D5-80EF-96AAEC0282B9")
+ error.set_backtrace detail.backtrace
+ raise error
+ end
+ end
+ found
+ end
+
+ def create
+ Puppet.debug("Creating registry value: #{self}")
+ write_value
+ end
+
+ def flush
+ # REVISIT - This concept of flush seems different than package provider's
+ # concept of flush.
+ Puppet.debug("Flushing registry value: #{self}")
+ return if resource[:ensure] == :absent
+ write_value
+ end
+
+ def destroy
+ Puppet.debug("Destroying registry value: #{self}")
+ # On Ruby 2.1.x, due to https://bugs.ruby-lang.org/issues/10820, we see
+ # a FileNotFound error - hence an FFI re-implementation inside destroy
+ hive.open(subkey, Win32::Registry::KEY_ALL_ACCESS | access) do |reg|
+ from_string_to_wide_string(valuename) do |valuename_ptr|
+ if RegDeleteValueW(reg.hkey, valuename_ptr) != 0
+ msg = "Failed to delete registry value #{valuename} at #{reg.keyname}"
+ raise Puppet::Util::Windows::Error.new(msg)
+ end
+ end
+ end
+ end
+
+ def type
+ regvalue[:type] || :absent
+ end
+
+ def type=(value)
+ regvalue[:type] = value
+ end
+
+ def data
+ regvalue[:data] || :absent
+ end
+
+ def data=(value)
+ regvalue[:data] = value
+ end
+
+ def regvalue
+ unless @regvalue
+ @regvalue = {}
+ hive.open(subkey, Win32::Registry::KEY_READ | access) do |reg|
+ from_string_to_wide_string(valuename) do |valuename_ptr|
+ if RegQueryValueExW(reg.hkey, valuename_ptr,
+ FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL,
+ FFI::MemoryPointer::NULL, FFI::MemoryPointer::NULL) == 0
+ @regvalue[:type], @regvalue[:data] = from_native(reg.read(valuename))
+ end
+ end
+ end
+ end
+ @regvalue
+ end
+
+ # convert puppet type and data to native
+ def to_native(ptype, pdata)
+ # JJM Because the data property is set to :array_matching => :all we
+ # should always get an array from Puppet. We need to convert this
+ # array to something usable by the Win API.
+ raise Puppet::Error, "Data should be an Array (ErrorID 37D9BBAB-52E8-4A7C-9F2E-D7BF16A59050)" unless pdata.kind_of?(Array)
+ ndata =
+ case ptype
+ when :binary
+ pdata.first.scan(/[a-f\d]{2}/i).map{ |byte| [byte].pack('H2') }.join('')
+ when :array
+ # We already have an array, and the native API write method takes an
+ # array, so send it thru.
+ pdata
+ else
+ # Since we have an array, take the first element and send it to the
+ # native API which is expecting a scalar.
+ pdata.first
+ end
+
+ return [name2type(ptype), ndata]
+ end
+
+ # convert from native type and data to puppet
+ def from_native(ary)
+ ntype, ndata = ary
+
+ pdata =
+ case type2name(ntype)
+ when :binary
+ ndata.bytes.map{ |byte| "%02x" % byte }.join(' ')
+ when :array
+ # We get the data from the registry in Array form.
+ ndata
+ else
+ ndata
+ end
+
+ # JJM Since the data property is set to :array_matching => all we should
+ # always give an array to Puppet. This is why we have the ternary operator
+ # I'm not calling .to_a because Ruby issues a warning about the default
+ # implementation of to_a going away in the future.
+ return [type2name(ntype), pdata.kind_of?(Array) ? pdata : [pdata]]
+ end
+
+ private
+
+ def write_value
+ begin
+ hive.open(subkey, Win32::Registry::KEY_ALL_ACCESS | access) do |reg|
+ ary = to_native(resource[:type], resource[:data])
+ write(reg, valuename, ary[0], ary[1])
+ end
+ rescue Win32::Registry::Error => detail
+ error = case detail.code
+ when 2
+ # Code 2 is the error message for "The system cannot find the file specified."
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx
+ Puppet::Error.new("Cannot write to the registry. The parent key does not exist. detail: (#{detail.message}) Puppet Error ID: AC99C7C6-98D6-4E91-A75E-970F4064BF95")
+ else
+ Puppet::Error.new("Unexpected exception from Win32 API. detail: (#{detail.message}). ERROR CODE: #{detail.code}. Puppet Error ID: F46C6AE2-C711-48F9-86D6-5D50E1988E48")
+ end
+ error.set_backtrace detail.backtrace
+ raise error
+ end
+ end
+
+ def data_to_bytes(type, data)
+ bytes = []
+
+ case type
+ when Win32::Registry::REG_SZ, Win32::Registry::REG_EXPAND_SZ
+ bytes = wide_string(data).bytes.to_a
+ when Win32::Registry::REG_MULTI_SZ
+ # each wide string is already NULL terminated
+ bytes = data.map { |s| wide_string(s).bytes.to_a }.flat_map { |a| a }
+ # requires an additional NULL terminator to terminate properly
+ bytes << 0 << 0
+ when Win32::Registry::REG_BINARY
+ bytes = data.bytes.to_a
+ when Win32::Registry::REG_DWORD
+ # L is 32-bit unsigned native (little) endian order
+ bytes = [data].pack('L').unpack('C*')
+ when Win32::Registry::REG_QWORD
+ # Q is 64-bit unsigned native (little) endian order
+ bytes = [data].pack('Q').unpack('C*')
+ else
+ raise TypeError, "Unsupported type #{type}"
+ end
+
+ bytes
+ end
+
+ def write(reg, name, type, data)
+ from_string_to_wide_string(valuename) do |name_ptr|
+ bytes = data_to_bytes(type, data)
+ FFI::MemoryPointer.new(:uchar, bytes.length) do |data_ptr|
+ data_ptr.write_array_of_uchar(bytes)
+ if RegSetValueExW(reg.hkey, name_ptr, 0,
+ type, data_ptr, data_ptr.size) != 0
+ raise Puppet::Util::Windows::Error.new("Failed to write registry value")
+ end
+ end
+ end
+ end
+
+ def path
+ @path ||= PuppetX::Puppetlabs::Registry::RegistryValuePath.new(resource.parameter(:path).value)
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/type/registry_key.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/type/registry_key.rb
new file mode 100644
index 000000000..8ce7f9e5f
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/type/registry_key.rb
@@ -0,0 +1,119 @@
+require 'puppet/type'
+begin
+ require "puppet_x/puppetlabs/registry"
+rescue LoadError => detail
+ require 'pathname' # JJM WORK_AROUND #14073 and #7788
+ require Pathname.new(__FILE__).dirname + "../../" + "puppet_x/puppetlabs/registry"
+end
+
+Puppet::Type.newtype(:registry_key) do
+ @doc = <<-EOT
+ Manages registry keys on Windows systems.
+
+ Keys within HKEY_LOCAL_MACHINE (hklm) or HKEY_CLASSES_ROOT (hkcr) are
+ supported. Other predefined root keys, e.g. HKEY_USERS, are not
+ currently supported.
+
+ If Puppet creates a registry key, Windows will automatically create any
+ necessary parent registry keys that do not exist.
+
+ Puppet will not recursively delete registry keys.
+
+ **Autorequires:** Any parent registry key managed by Puppet will be
+ autorequired.
+EOT
+
+ def self.title_patterns
+ [ [ /^(.*?)\Z/m, [ [ :path, lambda{|x| x} ] ] ] ]
+ end
+
+ ensurable
+
+ newparam(:path, :namevar => true) do
+ desc "The path to the registry key to manage. For example; 'HKLM\Software',
+ 'HKEY_LOCAL_MACHINE\Software\Vendor'. If Puppet is running on a 64-bit
+ system, the 32-bit registry key can be explicitly managed using a
+ prefix. For example: '32:HKLM\Software'"
+
+ validate do |path|
+ PuppetX::Puppetlabs::Registry::RegistryKeyPath.new(path).valid?
+ end
+ munge do |path|
+ reg_path = PuppetX::Puppetlabs::Registry::RegistryKeyPath.new(path)
+ # Windows is case insensitive and case preserving. We deal with this by
+ # aliasing resources to their downcase values. This is inspired by the
+ # munge block in the alias metaparameter.
+ if @resource.catalog
+ reg_path.aliases.each do |alt_name|
+ @resource.catalog.alias(@resource, alt_name)
+ end
+ else
+ Puppet.debug "Resource has no associated catalog. Aliases are not being set for #{@resource.to_s}"
+ end
+ reg_path.canonical
+ end
+ end
+
+ # REVISIT - Make a common parameter for boolean munging and validation. This will be used
+ # By both registry_key and registry_value types.
+ newparam(:purge_values, :boolean => true) do
+ desc "Whether to delete any registry value associated with this key that is
+ not being managed by puppet."
+
+ newvalues(:true, :false)
+ defaultto false
+
+ validate do |value|
+ case value
+ when true, /^true$/i, :true, false, /^false$/i, :false, :undef, nil
+ true
+ else
+ # We raise an ArgumentError and not a Puppet::Error so we get manifest
+ # and line numbers in the error message displayed to the user.
+ raise ArgumentError.new("Validation Error: purge_values must be true or false, not #{value}")
+ end
+ end
+
+ munge do |value|
+ case value
+ when true, /^true$/i, :true
+ true
+ else
+ false
+ end
+ end
+ end
+
+ # Autorequire the nearest ancestor registry_key found in the catalog.
+ autorequire(:registry_key) do
+ req = []
+ path = PuppetX::Puppetlabs::Registry::RegistryKeyPath.new(value(:path))
+ # It is important to match against the downcase value of the path because
+ # other resources are expected to alias themselves to the downcase value so
+ # that we respect the case insensitive and preserving nature of Windows.
+ if found = path.enum_for(:ascend).find { |p| catalog.resource(:registry_key, p.to_s.downcase) }
+ req << found.to_s.downcase
+ end
+ req
+ end
+
+ def eval_generate
+ # This value will be given post-munge so we can assume it will be a ruby true or false object
+ return [] unless value(:purge_values)
+
+ # get the "should" names of registry values associated with this key
+ should_values = catalog.relationship_graph.direct_dependents_of(self).select {|dep| dep.type == :registry_value }.map do |reg|
+ PuppetX::Puppetlabs::Registry::RegistryValuePath.new(reg.parameter(:path).value).valuename
+ end
+
+ # get the "is" names of registry values associated with this key
+ is_values = provider.values
+
+ # create absent registry_value resources for the complement
+ resources = []
+ (is_values - should_values).each do |name|
+ resources << Puppet::Type.type(:registry_value).new(:path => "#{self[:path]}\\#{name}", :ensure => :absent, :catalog => catalog)
+ end
+ resources
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/type/registry_value.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/type/registry_value.rb
new file mode 100644
index 000000000..556cba3dc
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet/type/registry_value.rb
@@ -0,0 +1,138 @@
+require 'puppet/type'
+begin
+ require "puppet_x/puppetlabs/registry"
+rescue LoadError => detail
+ require 'pathname' # JJM WORK_AROUND #14073 and #7788
+ require Pathname.new(__FILE__).dirname + "../../" + "puppet_x/puppetlabs/registry"
+end
+
+Puppet::Type.newtype(:registry_value) do
+ @doc = <<-EOT
+ Manages registry values on Windows systems.
+
+ The `registry_value` type can manage registry values. See the
+ `type` and `data` attributes for information about supported
+ registry types, e.g. REG_SZ, and how the data should be specified.
+
+ **Autorequires:** Any parent registry key managed by Puppet will be
+ autorequired.
+ EOT
+
+ def self.title_patterns
+ [[/^(.*?)\Z/m, [[:path, lambda { |x| x }]]]]
+ end
+
+ ensurable
+
+ newparam(:path, :namevar => true) do
+ desc "The path to the registry value to manage. For example:
+ 'HKLM\Software\Value1', 'HKEY_LOCAL_MACHINE\Software\Vendor\Value2'.
+ If Puppet is running on a 64-bit system, the 32-bit registry key can
+ be explicitly manage using a prefix. For example:
+ '32:HKLM\Software\Value3'"
+
+ validate do |path|
+ PuppetX::Puppetlabs::Registry::RegistryValuePath.new(path).valid?
+ end
+ munge do |path|
+ reg_path = PuppetX::Puppetlabs::Registry::RegistryValuePath.new(path)
+ # Windows is case insensitive and case preserving. We deal with this by
+ # aliasing resources to their downcase values. This is inspired by the
+ # munge block in the alias metaparameter.
+ if @resource.catalog
+ reg_path.aliases.each do |alt_name|
+ @resource.catalog.alias(@resource, alt_name)
+ end
+ else
+ Puppet.debug "Resource has no associated catalog. Aliases are not being set for #{@resource.to_s}"
+ end
+ reg_path.canonical
+ end
+ end
+
+ newproperty(:type) do
+ desc "The Windows data type of the registry value. Puppet provides
+ helpful names for these types as follows:
+
+ * string => REG_SZ
+ * array => REG_MULTI_SZ
+ * expand => REG_EXPAND_SZ
+ * dword => REG_DWORD
+ * qword => REG_QWORD
+ * binary => REG_BINARY
+
+ "
+ newvalues(:string, :array, :dword, :qword, :binary, :expand)
+ defaultto :string
+ end
+
+ newproperty(:data, :array_matching => :all) do
+ desc "The data stored in the registry value. Data should be specified
+ as a string value but may be specified as a Puppet array when the
+ type is set to `array`."
+
+ defaultto ''
+
+ munge do |value|
+ case resource[:type]
+ when :dword
+ val = Integer(value) rescue nil
+ fail("The data must be a valid DWORD: #{value}") unless val and (val.abs >> 32) <= 0
+ val
+ when :qword
+ val = Integer(value) rescue nil
+ fail("The data must be a valid QWORD: #{value}") unless val and (val.abs >> 64) <= 0
+ val
+ when :binary
+ if (value.respond_to?(:length) && value.length == 1) || (value.kind_of?(Integer) && value <= 9)
+ value = "0#{value}"
+ end
+ unless value.match(/^([a-f\d]{2} ?)*$/i)
+ fail("The data must be a hex encoded string of the form: '00 01 02 ...'")
+ end
+ # First, strip out all spaces from the string in the manfest. Next,
+ # put a space after each pair of hex digits. Strip off the rightmost
+ # space if it's present. Finally, downcase the whole thing. The final
+ # result should be: "CaFE BEEF" => "ca fe be ef"
+ value.gsub(/\s+/, '').gsub(/([0-9a-f]{2})/i) { "#{$1} " }.rstrip.downcase
+ else #:string, :expand, :array
+ value
+ end
+ end
+
+ def property_matches?(current, desired)
+ case resource[:type]
+ when :binary
+ return false unless current
+ current.casecmp(desired) == 0
+ else
+ super(current, desired)
+ end
+ end
+
+ def change_to_s(currentvalue, newvalue)
+ if currentvalue.respond_to? :join
+ currentvalue = currentvalue.join(",")
+ end
+ if newvalue.respond_to? :join
+ newvalue = newvalue.join(",")
+ end
+ super(currentvalue, newvalue)
+ end
+ end
+
+ # Autorequire the nearest ancestor registry_key found in the catalog.
+ autorequire(:registry_key) do
+ req = []
+ # This is a value path and not a key path because it's based on the path of
+ # the value resource.
+ path = PuppetX::Puppetlabs::Registry::RegistryValuePath.new(value(:path))
+ # It is important to match against the downcase value of the path because
+ # other resources are expected to alias themselves to the downcase value so
+ # that we respect the case insensitive and preserving nature of Windows.
+ if found = path.enum_for(:ascend).find { |p| catalog.resource(:registry_key, p.to_s.downcase) }
+ req << found.to_s.downcase
+ end
+ req
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet_x/puppetlabs/registry.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet_x/puppetlabs/registry.rb
new file mode 100644
index 000000000..e13ef24ee
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet_x/puppetlabs/registry.rb
@@ -0,0 +1,162 @@
+module PuppetX
+module Puppetlabs
+module Registry
+ # For 64-bit OS, use 64-bit view. Ignored on 32-bit OS
+ KEY_WOW64_64KEY = 0x100
+ # For 64-bit OS, use 32-bit view. Ignored on 32-bit OS
+ KEY_WOW64_32KEY = 0x200 unless defined? KEY_WOW64_32KEY
+
+ # This is the base class for Path manipulation. This class is meant to be
+ # abstract, RegistryKeyPath and RegistryValuePath will customize and override
+ # this class.
+ class RegistryPathBase < String
+ attr_reader :path
+ def initialize(path)
+ @filter_path_memo = nil
+ @path ||= path
+ super(path)
+ end
+
+ # The path is valid if we're able to parse it without exceptions.
+ def valid?
+ (filter_path and true) rescue false
+ end
+
+ def canonical
+ filter_path[:canonical]
+ end
+
+ # This method is meant to help setup aliases so autorequire can sort itself
+ # out in a case insensitive but preserving manner. It returns an array of
+ # resource identifiers.
+ def aliases
+ [canonical.downcase]
+ end
+
+ def access
+ filter_path[:access]
+ end
+
+ def root
+ filter_path[:root]
+ end
+
+ def ascend(&block)
+ p = canonical
+ while idx = p.rindex('\\')
+ p = p[0, idx]
+ yield p
+ end
+ end
+
+ private
+
+ def filter_path
+ if @filter_path_memo
+ return @filter_path_memo
+ end
+ result = {}
+
+ path = @path
+
+ result[:valuename] = case path[-1, 1]
+ when '\\'
+ result[:is_default] = true
+ ''
+ else
+ result[:is_default] = false
+ idx = path.rindex('\\') || 0
+ if idx > 0
+ path[idx+1..-1]
+ else
+ ''
+ end
+ end
+
+ # Strip off any trailing slash.
+ path = path.gsub(/\\*$/, '')
+
+ unless captures = /^(32:)?([h|H][^\\]*)((?:\\[^\\]{1,255})*)$/.match(path)
+ raise ArgumentError, "Invalid registry key: #{path}"
+ end
+
+ case captures[1]
+ when '32:'
+ result[:access] = PuppetX::Puppetlabs::Registry::KEY_WOW64_32KEY
+ result[:prefix] = '32:'
+ else
+ result[:access] = PuppetX::Puppetlabs::Registry::KEY_WOW64_64KEY
+ result[:prefix] = ''
+ end
+
+ # canonical root key symbol
+ result[:root] = case captures[2].to_s.downcase
+ when /hkey_local_machine/, /hklm/
+ :hklm
+ when /hkey_classes_root/, /hkcr/
+ :hkcr
+ when /hkey_users/, /hku/
+ :hku
+ when /hkey_current_user/, /hkcu/,
+ /hkey_current_config/, /hkcc/,
+ /hkey_performance_data/,
+ /hkey_performance_text/,
+ /hkey_performance_nlstext/,
+ /hkey_dyn_data/
+ raise ArgumentError, "Unsupported predefined key: #{path}"
+ else
+ raise ArgumentError, "Invalid registry key: #{path}"
+ end
+
+ result[:trailing_path] = captures[3]
+
+ result[:trailing_path].gsub!(/^\\/, '')
+
+ if result[:trailing_path].empty?
+ result[:canonical] = "#{result[:prefix]}#{result[:root].to_s}"
+ else
+ # Leading backslash is not part of the subkey name
+ result[:canonical] = "#{result[:prefix]}#{result[:root].to_s}\\#{result[:trailing_path]}"
+ end
+
+ @filter_path_memo = result
+ end
+ end
+
+ class RegistryKeyPath < RegistryPathBase
+ def subkey
+ filter_path[:trailing_path]
+ end
+ end
+
+ class RegistryValuePath < RegistryPathBase
+ def canonical
+ # This method gets called in the type and the provider. We need to
+ # preserve the trailing backslash for the provider, otherwise it won't
+ # think this is a default value.
+ if default?
+ filter_path[:canonical] << "\\"
+ else
+ filter_path[:canonical]
+ end
+ end
+
+ def subkey
+ if default?
+ filter_path[:trailing_path]
+ else
+ filter_path[:trailing_path].gsub(/^(.*)\\.*$/, '\1')
+ end
+ end
+
+ def valuename
+ filter_path[:valuename]
+ end
+
+ def default?
+ !!filter_path[:is_default]
+ end
+ end
+end
+end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet_x/puppetlabs/registry/provider_base.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet_x/puppetlabs/registry/provider_base.rb
new file mode 100644
index 000000000..c7a120dfa
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/lib/puppet_x/puppetlabs/registry/provider_base.rb
@@ -0,0 +1,229 @@
+# This module is meant to be mixed into the registry_key AND registry_value providers.
+module PuppetX
+module Puppetlabs
+module Registry
+module ProviderBase
+ def self.define_ffi(base)
+ extend FFI::Library
+
+ ffi_convention :stdcall
+
+ # uintptr_t is defined in an FFI conf as platform specific, either
+ # ulong_long on x64 or just ulong on x86
+ typedef :uintptr_t, :handle
+ # any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width
+ # which is what FFI uses by default
+ # instead create new aliases for these very special cases
+ typedef :int32, :win32_long
+ typedef :uint32, :win32_ulong
+ typedef :uint32, :dword
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724911(v=vs.85).aspx
+ # LONG WINAPI RegQueryValueEx(
+ # _In_ HKEY hKey,
+ # _In_opt_ LPCTSTR lpValueName,
+ # _Reserved_ LPDWORD lpReserved,
+ # _Out_opt_ LPDWORD lpType,
+ # _Out_opt_ LPBYTE lpData,
+ # _Inout_opt_ LPDWORD lpcbData
+ # );
+ ffi_lib :advapi32
+ attach_function :RegQueryValueExW,
+ [:handle, :pointer, :pointer, :pointer, :pointer, :pointer], :win32_long
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724847(v=vs.85).aspx
+ # LONG WINAPI RegDeleteKeyEx(
+ # _In_ HKEY hKey,
+ # _In_ LPCTSTR lpSubKey,
+ # _In_ REGSAM samDesired,
+ # _Reserved_ DWORD Reserved
+ # );
+ ffi_lib :advapi32
+ attach_function :RegDeleteKeyExW,
+ [:handle, :pointer, :win32_ulong, :dword], :win32_long
+
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724851(v=vs.85).aspx
+ # LONG WINAPI RegDeleteValue(
+ # _In_ HKEY hKey,
+ # _In_opt_ LPCTSTR lpValueName
+ # );
+ ffi_lib :advapi32
+ attach_function :RegDeleteValueW,
+ [:handle, :pointer], :win32_long
+
+ # https://msdn.microsoft.com/en-us/library/windows/desktop/ms724923(v=vs.85).aspx
+ # LONG WINAPI RegSetValueEx(
+ # _In_ HKEY hKey,
+ # _In_opt_ LPCTSTR lpValueName,
+ # _Reserved_ DWORD Reserved,
+ # _In_ DWORD dwType,
+ # _In_ const BYTE *lpData,
+ # _In_ DWORD cbData
+ # );
+ ffi_lib :advapi32
+ attach_function :RegSetValueExW,
+ [:handle, :pointer, :dword, :dword, :pointer, :dword], :win32_long
+
+ # this duplicates code found in puppet, but necessary for backwards compat
+ class << base
+ # note that :uchar is aliased in Puppet to :byte
+ def from_string_to_wide_string(str, &block)
+ str = wide_string(str)
+ FFI::MemoryPointer.new(:uchar, str.bytesize) do |ptr|
+ ptr.put_array_of_uchar(0, str.bytes.to_a)
+
+ yield ptr
+ end
+
+ # ptr has already had free called, so nothing to return
+ nil
+ end
+
+ def wide_string(str)
+ # if given a nil string, assume caller wants to pass a nil pointer to win32
+ return nil if str.nil?
+ # ruby (< 2.1) does not respect multibyte terminators, so it is possible
+ # for a string to contain a single trailing null byte, followed by garbage
+ # causing buffer overruns.
+ #
+ # See http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=41920&view=revision
+ newstr = str + "\0".encode(str.encoding)
+ newstr.encode!('UTF-16LE')
+ end
+ end
+ end
+
+ # This is a class method in order to be easily mocked in the spec tests.
+ def self.initialize_system_api(base)
+ if Puppet.features.microsoft_windows?
+ begin
+ require 'win32/registry'
+ rescue LoadError => exc
+ msg = "Could not load the required win32/registry library (ErrorID 1EAD86E3-D533-4286-BFCB-CCE8B818DDEA) [#{exc.message}]"
+ Puppet.err msg
+ error = Puppet::Error.new(msg)
+ error.set_backtrace exc.backtrace
+ raise error
+ end
+
+ begin
+ require 'ffi'
+ define_ffi(base)
+ rescue LoadError => exc
+ msg = "Could not load the required ffi library [#{exc.message}]"
+ Puppet.err msg
+ error = Puppet::Error.new(msg)
+ error.set_backtrace exc.backtrace
+ raise error
+ end
+
+ class << base
+ # create instance to access mix-in methods since it doesn't use module_function
+ require 'puppet/util/windows/registry'
+ def RegistryHelpers
+ @registry_helpers ||= Class.new.extend(Puppet::Util::Windows::Registry)
+ end
+ end
+ end
+ end
+
+ def self.included(base)
+ # Initialize the Win32 API. This is a method call so the spec tests can
+ # easily mock the initialization of the Win32 libraries on non-win32
+ # systems.
+ initialize_system_api(base)
+
+ # Define an hkeys class method in the eigenclass we're being mixed into.
+ # This is the one true place to define the root hives we support.
+ class << base
+ def hkeys
+ # REVISIT: I'd like to make this easier to mock and stub.
+ {
+ :hkcr => Win32::Registry::HKEY_CLASSES_ROOT,
+ :hklm => Win32::Registry::HKEY_LOCAL_MACHINE,
+ :hku => Win32::Registry::HKEY_USERS,
+ }
+ end
+ end
+ end
+
+ # The rest of these methods will be mixed in as instance methods into the
+ # provider class. The path method is expected to be mixed in by the provider
+ # specific module, ProviderKeyBase or ProviderValueBase
+ def from_string_to_wide_string(str, &block)
+ self.class.from_string_to_wide_string(str, &block)
+ end
+
+ def wide_string(str)
+ self.class.wide_string(str)
+ end
+
+ def hkeys
+ self.class.hkeys
+ end
+
+ def registry_helpers
+ self.class.RegistryHelpers
+ end
+
+ def hive
+ hkeys[path.root]
+ end
+
+ def access
+ path.access
+ end
+
+ def root
+ path.root
+ end
+
+ def subkey
+ path.subkey
+ end
+
+ def valuename
+ path.valuename
+ end
+
+ def type2name_map
+ {
+ Win32::Registry::REG_NONE => :none,
+ Win32::Registry::REG_SZ => :string,
+ Win32::Registry::REG_EXPAND_SZ => :expand,
+ Win32::Registry::REG_BINARY => :binary,
+ Win32::Registry::REG_DWORD => :dword,
+ Win32::Registry::REG_QWORD => :qword,
+ Win32::Registry::REG_MULTI_SZ => :array
+ }
+ end
+
+ def type2name(type)
+ type2name_map[type]
+ end
+
+ def name2type(name)
+ name2type = {}
+ type2name_map.each_pair {|k,v| name2type[v] = k}
+ name2type[name]
+ end
+
+ def each_value(key, &block)
+ # This problem affects Ruby 2.1 and higher by introducing locale conversion
+ # unnecessary. Puppet 4 introduces it's own each_value patches to the
+ # Registry abstraction to work around these problems
+ # https://github.com/puppetlabs/puppet/commit/b46ede74f640a809b68a473ac8720b93b13d2ac3
+ if registry_helpers.respond_to?(:each_value)
+ registry_helpers.each_value(key) do |name, type, data|
+ yield name, type, data
+ end
+ else
+ key.each_value do |name, type, data|
+ yield name, type, data
+ end
+ end
+ end
+end
+end
+end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/manifests/service.pp b/modules/utilities/windows/registry/puppetlabs_registry_library/manifests/service.pp
new file mode 100644
index 000000000..eb0ea1a40
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/manifests/service.pp
@@ -0,0 +1,135 @@
+# Define: registry::service
+#
+# This defined resource type manages service entries in the Microsoft service
+# control framework by managing the appropriate registry keys and values.
+#
+# This is an alternative approach to using INSTSRV.EXE [1].
+#
+# [1] http://support.microsoft.com/kb/137890
+#
+# Parameters:
+#
+# ensure: [ present, absent ]
+#
+# display_name: The Display Name of the service. Defaults to the title of
+# the resource.
+#
+# description: A description of the service
+#
+# command: The command to execute
+#
+# start: The starting mode of the service. (Note, the native service
+# resource can also be used to manage this setting.)
+# [ automatic, manual, disabled ]
+#
+# Actions:
+#
+# Manages the values in the key HKLM\System\CurrentControlSet\Services\$name\
+#
+# Requires:
+#
+# Module puppetlabs-registry
+#
+# Sample Usage:
+#
+# registry::service { puppet:
+# ensure => present,
+# display_name => 'Puppet Agent',
+# description => 'Periodically fetches and applies
+# configurations from a Puppet master server.',
+# command => 'C:\PuppetLabs\Puppet\service\daemon.bat',
+# }
+#
+define registry::service(
+ $ensure = 'UNSET',
+ $display_name = 'UNSET',
+ $description = 'UNSET',
+ $command = 'UNSET',
+ $start = 'UNSET'
+) {
+
+ $ensure_real = $ensure ? {
+ 'UNSET' => present,
+ undef => present,
+ present => present,
+ absent => absent,
+ }
+
+ $display_name_real = $display_name ? {
+ 'UNSET' => $name,
+ default => $display_name,
+ }
+
+ $description_real = $description ? {
+ 'UNSET' => $display_name_real,
+ default => $description,
+ }
+
+ # FIXME Better validation of the command parameter.
+ # (Fully qualified path? Though, it will be a REG_EXPAND_SZ.)
+ $command_real = $command ? {
+ default => $command,
+ }
+
+ # Map descriptive names to flags.
+ $start_real = $start ? {
+ automatic => 2,
+ manual => 3,
+ disabled => 4,
+ }
+
+ # Variable to hold the base key path.
+ $service_key = "HKLM\\System\\CurrentControlSet\\Services\\${name}"
+
+ # Manage the key
+ if $ensure_real == present {
+ registry_key { $service_key:
+ ensure => present,
+ }
+ } else {
+ registry_key { $service_key:
+ ensure => absent,
+ # REVISIT: purge_values => true,
+ }
+ }
+
+ # Manage the values
+ if $ensure_real == present {
+ registry_value { "${service_key}\\Description":
+ ensure => present,
+ type => string,
+ data => $description_real,
+ }
+ registry_value { "${service_key}\\DisplayName":
+ ensure => present,
+ type => string,
+ data => $display_name_real,
+ }
+ registry_value { "${service_key}\\ErrorControl":
+ ensure => present,
+ type => dword,
+ data => 0x00000001,
+ }
+ registry_value { "${service_key}\\ImagePath":
+ ensure => present,
+ type => expand,
+ data => $command_real,
+ }
+ registry_value { "${service_key}\\ObjectName":
+ ensure => present,
+ type => string,
+ data => 'LocalSystem',
+ }
+ registry_value { "${service_key}\\Start":
+ ensure => present,
+ type => dword,
+ data => $start_real,
+ }
+ registry_value { "${service_key}\\Type":
+ ensure => present,
+ type => dword,
+ data => 0x00000010, # (16)
+ }
+ }
+}
+# EOF
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/manifests/value.pp b/modules/utilities/windows/registry/puppetlabs_registry_library/manifests/value.pp
new file mode 100644
index 000000000..4c30cc557
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/manifests/value.pp
@@ -0,0 +1,83 @@
+# = Define: registry::value
+#
+# This defined resource type provides a higher level of abstraction on top of
+# the registry_key and registry_value resources. Using this defined resource
+# type, you do not need to explicitly manage the parent key for a particular
+# value. Puppet will automatically manage the parent key for you.
+#
+# == Parameters:
+#
+# key:: The path of key the value will placed inside.
+#
+# value:: The name of the registry value to manage. This will be copied from
+# the resource title if not specified. The special value of
+# '(default)' may be used to manage the default value of the key.
+#
+# type:: The type the registry value. Defaults to 'string'. See the output of
+# `puppet describe registry_value` for a list of supported types in the
+# "type" parameter.
+#
+# data:: The data to place inside the registry value.
+#
+# == Actions:
+# - Manage the parent key if not already managed.
+# - Manage the value
+#
+# == Requires:
+# - Registry Module
+# - Stdlib Module
+#
+# == Sample Usage:
+#
+# This example will automatically manage the key. It will also create a value
+# named 'puppetmaster' inside this key.
+#
+# class myapp {
+# registry::value { 'puppetmaster':
+# key => 'HKLM\Software\Vendor\PuppetLabs',
+# data => 'puppet.puppetlabs.com',
+# }
+# }
+#
+define registry::value (
+ $key,
+ $value = undef,
+ $type = 'string',
+ $data = undef,
+) {
+
+ # ensure windows os
+ if $::operatingsystem != 'windows'{
+ fail("Unsupported OS ${::operatingsystem}")
+ }
+
+ # validate our inputs.
+ validate_re($key, '^\w+',
+ "key parameter must not be empty but it is key => '${key}'")
+ validate_re($type, '^\w+',
+ "type parameter must not be empty but it is type => '${type}'")
+
+
+
+ $value_real = $value ? {
+ undef => $name,
+ '(default)' => '',
+ default => $value,
+ }
+
+ # Resource defaults.
+ Registry_key { ensure => present }
+ Registry_value { ensure => present }
+
+ if !defined(Registry_key[$key]) {
+ registry_key { $key: }
+ }
+
+ # If value_real is an empty string then the default value of the key will be
+ # managed.
+ registry_value { "${key}\\${value_real}":
+ type => $type,
+ data => $data,
+ }
+}
+
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/metadata.json b/modules/utilities/windows/registry/puppetlabs_registry_library/metadata.json
new file mode 100644
index 000000000..598cac308
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/metadata.json
@@ -0,0 +1,38 @@
+{
+ "name": "puppetlabs-registry",
+ "version": "1.1.4",
+ "author": "Puppet Inc",
+ "summary": "This module provides a native type and provider to manage keys and values in the Windows Registry",
+ "license": "Apache-2.0",
+ "source": "git://github.com/puppetlabs/puppetlabs-registry.git",
+ "project_page": "http://links.puppet.com/registry-module",
+ "issues_url": "https://github.com/puppetlabs/puppetlabs-registry/issues",
+ "dependencies": [
+ {"name":"puppetlabs/stdlib","version_requirement":">= 2.3.0"}
+ ],
+ "data_provider": null,
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "Windows",
+ "operatingsystemrelease": [
+ "Server 2008",
+ "Server 2008 R2",
+ "Server 2012",
+ "Server 2012 R2",
+ "7",
+ "8"
+ ]
+ }
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": ">= 3.3.0 < 2015.4.0"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": ">= 3.3.0 < 5.0.0"
+ }
+ ],
+ "description": "This module provides a native type and provider to manage keys and values in the Windows Registry"
+}
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/puppetlabs_registry_library.pp b/modules/utilities/windows/registry/puppetlabs_registry_library/puppetlabs_registry_library.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/secgen_metadata.xml b/modules/utilities/windows/registry/puppetlabs_registry_library/secgen_metadata.xml
new file mode 100644
index 000000000..f75cb829d
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/secgen_metadata.xml
@@ -0,0 +1,18 @@
+
+
+
+ Registry library
+ Jason Keighley
+ Puppetlabs
+ Apache v2
+ A local version of the puppet registry library
+
+ registry
+ windows
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/centos-7-x64.yml b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/centos-7-x64.yml
new file mode 100644
index 000000000..5eebdefbf
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/centos-7-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ centos-7-x64:
+ roles:
+ - agent
+ - default
+ platform: el-7-x86_64
+ hypervisor: vagrant
+ box: puppetlabs/centos-7.2-64-nocm
+CONFIG:
+ type: foss
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/debian-8-x64.yml b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/debian-8-x64.yml
new file mode 100644
index 000000000..fef6e63ca
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/debian-8-x64.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ debian-8-x64:
+ roles:
+ - agent
+ - default
+ platform: debian-8-amd64
+ hypervisor: vagrant
+ box: puppetlabs/debian-8.2-64-nocm
+CONFIG:
+ type: foss
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/default.yml b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/default.yml
new file mode 100644
index 000000000..dba339c46
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/default.yml
@@ -0,0 +1,10 @@
+HOSTS:
+ ubuntu-1404-x64:
+ roles:
+ - agent
+ - default
+ platform: ubuntu-14.04-amd64
+ hypervisor: vagrant
+ box: puppetlabs/ubuntu-14.04-64-nocm
+CONFIG:
+ type: foss
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/centos-7.yml b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/centos-7.yml
new file mode 100644
index 000000000..a3333aac5
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/centos-7.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ centos-7-x64:
+ platform: el-7-x86_64
+ hypervisor: docker
+ image: centos:7
+ docker_preserve_image: true
+ docker_cmd: '["/usr/sbin/init"]'
+ # install various tools required to get the image up to usable levels
+ docker_image_commands:
+ - 'yum install -y crontabs tar wget openssl sysvinit-tools iproute which initscripts'
+CONFIG:
+ trace_limit: 200
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/debian-8.yml b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/debian-8.yml
new file mode 100644
index 000000000..df5c31944
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/debian-8.yml
@@ -0,0 +1,11 @@
+HOSTS:
+ debian-8-x64:
+ platform: debian-8-amd64
+ hypervisor: docker
+ image: debian:8
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ - 'apt-get update && apt-get install -y net-tools wget locales strace lsof && echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen'
+CONFIG:
+ trace_limit: 200
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/ubuntu-14.04.yml b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/ubuntu-14.04.yml
new file mode 100644
index 000000000..b1efa5839
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/acceptance/nodesets/docker/ubuntu-14.04.yml
@@ -0,0 +1,12 @@
+HOSTS:
+ ubuntu-1404-x64:
+ platform: ubuntu-14.04-amd64
+ hypervisor: docker
+ image: ubuntu:14.04
+ docker_preserve_image: true
+ docker_cmd: '["/sbin/init"]'
+ docker_image_commands:
+ # ensure that upstart is booting correctly in the container
+ - 'rm /usr/sbin/policy-rc.d && rm /sbin/initctl && dpkg-divert --rename --remove /sbin/initctl && apt-get update && apt-get install -y net-tools wget && locale-gen en_US.UTF-8'
+CONFIG:
+ trace_limit: 200
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/spec_helper.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/spec_helper.rb
new file mode 100644
index 000000000..f6395ee39
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/spec_helper.rb
@@ -0,0 +1,30 @@
+
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+
+require 'mocha'
+require 'puppet'
+require 'rspec'
+require 'rspec-puppet'
+require 'puppetlabs_spec_helper/module_spec_helper'
+
+RSpec.configure do |c|
+ c.mock_with :mocha
+
+ if File::ALT_SEPARATOR && RUBY_VERSION =~ /^1\./
+ require 'win32console'
+ c.output_stream = $stdout
+ c.error_stream = $stderr
+ c.formatters.each { |f| f.instance_variable_set(:@output, $stdout) }
+ end
+
+ c.expect_with :rspec do |e|
+ e.syntax = [:should, :expect]
+ end
+end
+
+# We need this because the RAL uses 'should' as a method. This
+# allows us the same behaviour but with a different method name.
+class Object
+ alias :must :should
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/provider/registry_key_spec.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/provider/registry_key_spec.rb
new file mode 100644
index 000000000..1d7a2a1cd
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/provider/registry_key_spec.rb
@@ -0,0 +1,116 @@
+#! /usr/bin/env ruby
+
+require 'spec_helper'
+require 'puppet/type/registry_key'
+
+describe Puppet::Type.type(:registry_key).provider(:registry), :if => Puppet.features.microsoft_windows? do
+ let (:catalog) do Puppet::Resource::Catalog.new end
+ let (:type) { Puppet::Type.type(:registry_key) }
+
+ puppet_key = "SOFTWARE\\Puppet Labs"
+ subkey_name ="PuppetRegProviderTest"
+
+ before(:each) do
+ # problematic Ruby codepath triggers a conversion of UTF-16LE to
+ # a local codepage which can totally break when that codepage has no
+ # conversion from the given UTF-16LE characters to local codepage
+ # a prime example is that IBM437 has no conversion from a Unicode en-dash
+ Win32::Registry.any_instance.expects(:export_string).never
+
+ Win32::Registry.any_instance.expects(:delete_value).never
+ Win32::Registry.any_instance.expects(:delete_key).never
+
+ if RUBY_VERSION >= '2.1'
+ # also, expect that we're not using Rubys each_key / each_value which exhibit bad behavior
+ Win32::Registry.any_instance.expects(:each_key).never
+ Win32::Registry.any_instance.expects(:each_value).never
+ end
+ end
+
+ describe "#destroy" do
+ it "can destroy a randomly created key" do
+
+ guid = SecureRandom.uuid
+ reg_key = type.new(:path => "hklm\\#{puppet_key}\\#{subkey_name}\\#{guid}", :provider => described_class.name)
+ already_exists = reg_key.provider.exists?
+ already_exists.should be_falsey
+
+ # something has gone terribly wrong here, pull the ripcord
+ break if already_exists
+
+ reg_key.provider.create
+ reg_key.provider.exists?.should be true
+
+ # test FFI code
+ reg_key.provider.destroy
+ reg_key.provider.exists?.should be false
+ end
+ end
+
+ describe "#purge_values", :if => Puppet.features.microsoft_windows? do
+ let (:guid) { SecureRandom.uuid }
+ let (:reg_path) { "#{puppet_key}\\#{subkey_name}\\Unicode-#{guid}" }
+
+ def bytes_to_utf8(bytes)
+ bytes.pack('c*').force_encoding(Encoding::UTF_8)
+ end
+
+ after(:each) do
+ reg_key = type.new(:path => "hklm\\#{reg_path}", :provider => described_class.name)
+ reg_key.provider.destroy
+
+ reg_key.provider.exists?.should be_falsey
+ end
+
+ context "with ANSI strings on all Ruby platforms" do
+ before(:each) do
+ Win32::Registry::HKEY_LOCAL_MACHINE.create(reg_path,
+ Win32::Registry::KEY_ALL_ACCESS |
+ PuppetX::Puppetlabs::Registry::KEY_WOW64_64KEY) do |reg_key|
+ reg_key.write('hi', Win32::Registry::REG_SZ, 'yes')
+ end
+ end
+
+ it "does not raise an error" do
+ reg_key = type.new(:catalog => catalog,
+ :ensure => :absent,
+ :name => "hklm\\#{reg_path}",
+ :purge_values => true,
+ :provider => described_class.name)
+
+ catalog.add_resource(reg_key)
+
+ expect { reg_key.eval_generate }.to_not raise_error
+ end
+ end
+
+ context "with unicode", :if => Puppet.features.microsoft_windows? && RUBY_VERSION =~ /^2\./ do
+ before(:each) do
+ # create temp registry key with Unicode values
+ Win32::Registry::HKEY_LOCAL_MACHINE.create(reg_path,
+ Win32::Registry::KEY_ALL_ACCESS |
+ PuppetX::Puppetlabs::Registry::KEY_WOW64_64KEY) do |reg_key|
+ endash = bytes_to_utf8([0xE2, 0x80, 0x93])
+ tm = bytes_to_utf8([0xE2, 0x84, 0xA2])
+
+ reg_key.write(endash, Win32::Registry::REG_SZ, tm)
+ end
+ end
+
+ it "does not use Rubys each_value, which unnecessarily string encodes" do
+ # endash and tm undergo LOCALE conversion during Rubys each_value
+ # which will generally lead to a conversion exception
+ reg_key = type.new(:catalog => catalog,
+ :ensure => :absent,
+ :name => "hklm\\#{reg_path}",
+ :purge_values => true,
+ :provider => described_class.name)
+
+ catalog.add_resource(reg_key)
+
+ # this will trigger
+ expect { reg_key.eval_generate }.to_not raise_error
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/provider/registry_value_spec.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/provider/registry_value_spec.rb
new file mode 100644
index 000000000..3b6f612fe
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/provider/registry_value_spec.rb
@@ -0,0 +1,223 @@
+#! /usr/bin/env ruby
+
+require 'spec_helper'
+require 'puppet/type/registry_value'
+
+describe Puppet::Type.type(:registry_value).provider(:registry), :if => Puppet.features.microsoft_windows? do
+ let (:catalog) do Puppet::Resource::Catalog.new end
+ let (:type) { Puppet::Type.type(:registry_value) }
+
+ puppet_key = "SOFTWARE\\Puppet Labs"
+ subkey_name ="PuppetRegProviderTest"
+
+ before(:all) do
+ Win32::Registry::HKEY_LOCAL_MACHINE.create("#{puppet_key}\\#{subkey_name}",
+ Win32::Registry::KEY_ALL_ACCESS |
+ PuppetX::Puppetlabs::Registry::KEY_WOW64_64KEY)
+ end
+
+ before(:each) do
+ # problematic Ruby codepath triggers a conversion of UTF-16LE to
+ # a local codepage which can totally break when that codepage has no
+ # conversion from the given UTF-16LE characters to local codepage
+ # a prime example is that IBM437 has no conversion from a Unicode en-dash
+ Win32::Registry.any_instance.expects(:export_string).never
+
+ Win32::Registry.any_instance.expects(:delete_value).never
+ Win32::Registry.any_instance.expects(:delete_key).never
+
+ if RUBY_VERSION >= '2.1'
+ # also, expect that we're not using Rubys each_key / each_value which exhibit bad behavior
+ Win32::Registry.any_instance.expects(:each_key).never
+ Win32::Registry.any_instance.expects(:each_value).never
+
+ # this covers []= write_s write_i and write_bin
+ Win32::Registry.any_instance.expects(:write).never
+ end
+ end
+
+ after(:all) do
+ # Ruby 2.1.5 has bugs with deleting registry keys due to using ANSI
+ # character APIs, but passing wide strings to them (facepalm)
+ # https://github.com/ruby/ruby/blob/v2_1_5/ext/win32/lib/win32/registry.rb#L323-L329
+ # therefore, use our own code instead of hklm.delete_value
+
+ # NOTE: registry_value tests unfortunately depend on registry_key type
+ # otherwise, there would be a bit of Win32 API code here
+ reg_key = Puppet::Type.type(:registry_key).new(:path => "hklm\\#{puppet_key}\\#{subkey_name}",
+ :provider => :registry)
+ reg_key.provider.destroy
+ end
+
+ describe "#exists?" do
+ it "should return true for a well known hive" do
+ reg_value = type.new(:path => 'hklm\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareType', :provider => described_class.name)
+ reg_value.provider.exists?.should be true
+ end
+
+ it "should return false for a bogus hive/path" do
+ reg_value = type.new(:path => 'hklm\foobar5000', :catalog => catalog, :provider => described_class.name)
+ reg_value.provider.exists?.should be false
+ end
+ end
+
+ describe "#regvalue" do
+ it "should return a valid string for a well known key" do
+ reg_value = type.new(:path => 'hklm\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRoot', :provider => described_class.name)
+ reg_value.provider.data.should eq [ENV['SystemRoot']]
+ reg_value.provider.type.should eq :string
+ end
+
+ it "returns a string of lowercased hex encoded bytes" do
+ reg = described_class.new
+ type, data = reg.from_native([3, "\u07AD"])
+ data.should eq ['de ad']
+ end
+
+ it "left pads binary strings" do
+ reg = described_class.new
+ type, data = reg.from_native([3, "\x1"])
+ data.should eq ['01']
+ end
+ end
+
+ describe "#destroy" do
+ let (:path) { path = "hklm\\#{puppet_key}\\#{subkey_name}\\#{SecureRandom.uuid}" }
+ def create_and_destroy(path, reg_type, data)
+ reg_value = type.new(:path => path,
+ :type => reg_type,
+ :data => data,
+ :provider => described_class.name)
+ already_exists = reg_value.provider.exists?
+ already_exists.should be_falsey
+
+ # something has gone terribly wrong here, pull the ripcord
+ fail if already_exists
+
+ reg_value.provider.create
+ reg_value.provider.exists?.should be true
+ expect(reg_value.provider.data).to eq([data].flatten)
+
+ reg_value.provider.destroy
+ reg_value.provider.exists?.should be false
+ end
+
+ it "can destroy a randomly created REG_SZ value" do
+ create_and_destroy(path, :string, SecureRandom.uuid)
+ end
+
+ it "can destroy a randomly created REG_EXPAND_SZ value" do
+ create_and_destroy(path, :expand, "#{SecureRandom.uuid} system root is %SystemRoot%")
+ end
+
+ it "can destroy a randomly created REG_BINARY value" do
+ create_and_destroy(path, :binary, '01 01 10 10')
+ end
+
+ it "can destroy a randomly created REG_DWORD value" do
+ create_and_destroy(path, :dword, rand(2 ** 32 - 1))
+ end
+
+ it "can destroy a randomly created REG_QWORD value" do
+ create_and_destroy(path, :qword, rand(2 ** 64 - 1))
+ end
+
+ it "can destroy a randomly created REG_MULTI_SZ value" do
+ create_and_destroy(path, :array, [SecureRandom.uuid, SecureRandom.uuid])
+ end
+ end
+
+ context "when writing numeric values" do
+ let (:path) { path = "hklm\\#{puppet_key}\\#{subkey_name}\\#{SecureRandom.uuid}" }
+
+ after(:each) do
+ reg_value = type.new(:path => path, :provider => described_class.name)
+
+ reg_value.provider.destroy
+ expect(reg_value.provider).to_not be_exists
+ end
+
+ def write_and_read_value(path, reg_type, value)
+ reg_value = type.new(:path => path,
+ :type => reg_type,
+ :data => value,
+ :provider => described_class.name)
+
+ reg_value.provider.create
+ expect(reg_value.provider).to be_exists
+ expect(reg_value.provider.type).to eq(reg_type)
+
+ written = reg_value.provider.data.first
+ expect(written).to eq(value)
+ end
+
+
+ # values chosen at 1 bit past previous byte boundary
+ [0xFF + 1, 0xFFFF + 1, 0xFFFFFF + 1, 0xFFFFFFFF].each do |value|
+ it "properly round-trips written values by converting endianness properly" do
+ write_and_read_value(path, :dword, value)
+ write_and_read_value(path, :qword, value)
+ end
+ end
+
+ [0xFFFFFFFFFF + 1, 0xFFFFFFFFFFFF + 1, 0xFFFFFFFFFFFFFF + 1, 0xFFFFFFFFFFFFFFFF].each do |value|
+ it "properly round-trips written values by converting endianness properly" do
+ write_and_read_value(path, :qword, value)
+ end
+ end
+ end
+
+ context "when reading non-ASCII values" do
+ ENDASH_UTF_8 = [0xE2, 0x80, 0x93]
+ ENDASH_UTF_16 = [0x2013]
+ TM_UTF_8 = [0xE2, 0x84, 0xA2]
+ TM_UTF_16 = [0x2122]
+
+ let (:guid) { SecureRandom.uuid }
+
+ after(:each) do
+ # Ruby 2.1.5 has bugs with deleting registry keys due to using ANSI
+ # character APIs, but passing wide strings to them (facepalm)
+ # https://github.com/ruby/ruby/blob/v2_1_5/ext/win32/lib/win32/registry.rb#L323-L329
+ # therefore, use our own code instead of hklm.delete_value
+
+ reg_value = type.new(:path => "hklm\\#{puppet_key}\\#{subkey_name}\\#{guid}",
+ :provider => described_class.name)
+
+ reg_value.provider.destroy
+ reg_value.provider.exists?.should be_falsey
+ end
+
+ # proof that there is no conversion to local encodings like IBM437
+ it "will return a UTF-8 string from a REG_SZ registry value (written as UTF-16LE)",
+ :if => Puppet.features.microsoft_windows? && RUBY_VERSION >= '2.1' do
+
+ # create a UTF-16LE byte array representing "–™"
+ utf_16_bytes = ENDASH_UTF_16 + TM_UTF_16
+ utf_16_str = utf_16_bytes.pack('s*').force_encoding(Encoding::UTF_16LE)
+
+ # and it's UTF-8 equivalent bytes
+ utf_8_bytes = ENDASH_UTF_8 + TM_UTF_8
+ utf_8_str = utf_8_bytes.pack('c*').force_encoding(Encoding::UTF_8)
+
+ reg_value = type.new(:path => "hklm\\#{puppet_key}\\#{subkey_name}\\#{guid}",
+ :type => :string,
+ :data => utf_16_str,
+ :provider => described_class.name)
+
+ already_exists = reg_value.provider.exists?
+ already_exists.should be_falsey
+
+ reg_value.provider.create
+ reg_value.provider.exists?.should be true
+
+ reg_value.provider.data.length.should eq 1
+ reg_value.provider.type.should eq :string
+
+ # The UTF-16LE string written should come back as the equivalent UTF-8
+ written = reg_value.provider.data.first
+ written.should eq(utf_8_str)
+ written.encoding.should eq(Encoding::UTF_8)
+ end
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/type/registry_key_spec.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/type/registry_key_spec.rb
new file mode 100644
index 000000000..dd90ca901
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/type/registry_key_spec.rb
@@ -0,0 +1,151 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'puppet/resource'
+require 'puppet/resource/catalog'
+require 'puppet/type/registry_key'
+
+describe Puppet::Type.type(:registry_key) do
+ let (:catalog) do Puppet::Resource::Catalog.new end
+
+
+ # This is overridden here so we get a consistent association with the key
+ # and a catalog using memoized let methods.
+ let (:key) do
+ Puppet::Type.type(:registry_key).new(:name => 'HKLM\Software', :catalog => catalog)
+ end
+ let(:provider) { Puppet::Provider.new(key) }
+
+ before :each do
+ key.provider = provider
+ end
+
+ [:ensure].each do |property|
+ it "should have a #{property} property" do
+ described_class.attrclass(property).ancestors.should be_include(Puppet::Property)
+ end
+
+ it "should have documentation for its #{property} property" do
+ described_class.attrclass(property).doc.should be_instance_of(String)
+ end
+ end
+
+ describe "path parameter" do
+ it "should have a path parameter" do
+ Puppet::Type.type(:registry_key).attrtype(:path).must == :param
+ end
+
+ %w[hklm hklm\software hklm\software\vendor].each do |path|
+ it "should accept #{path}" do
+ key[:path] = path
+ end
+ end
+
+ %w[hku hku\.DEFAULT hku\.DEFAULT\software hku\.DEFAULT\software\vendor].each do |path|
+ it "should accept #{path}" do
+ key[:path] = path
+ end
+ end
+
+ %w[HKEY_DYN_DATA HKEY_PERFORMANCE_DATA].each do |path|
+ it "should reject #{path} as unsupported case insensitively" do
+ expect { key[:path] = path }.to raise_error(Puppet::Error, /Unsupported/)
+ end
+ end
+
+ %w[hklm\\ hklm\foo\\ unknown unknown\subkey \:hkey].each do |path|
+ it "should reject #{path} as invalid" do
+ path = "hklm\\" + 'a' * 256
+ expect { key[:path] = path }.to raise_error(Puppet::Error, /Invalid registry key/)
+ end
+ end
+
+ %w[HKLM HKEY_LOCAL_MACHINE hklm].each do |root|
+ it "should canonicalize the root key #{root}" do
+ key[:path] = root
+ key[:path].must == 'hklm'
+ end
+ end
+
+ it 'should be case-preserving'
+ it 'should be case-insensitive'
+ it 'should autorequire ancestor keys'
+ it 'should support 32-bit keys' do
+ key[:path] = '32:hklm\software'
+ end
+ end
+
+ describe "#eval_generate" do
+ context "not purging" do
+ it "should return an empty array" do
+ key.eval_generate.must be_empty
+ end
+ end
+
+ context "purging" do
+ let (:catalog) do Puppet::Resource::Catalog.new end
+
+ # This is overridden here so we get a consistent association with the key
+ # and a catalog using memoized let methods.
+ let (:key) do
+ Puppet::Type.type(:registry_key).new(:name => 'HKLM\Software', :catalog => catalog)
+ end
+
+ before :each do
+ key[:purge_values] = true
+ catalog.add_resource(key)
+ catalog.add_resource(Puppet::Type.type(:registry_value).new(:path => "#{key[:path]}\\val1", :catalog => catalog))
+ catalog.add_resource(Puppet::Type.type(:registry_value).new(:path => "#{key[:path]}\\val2", :catalog => catalog))
+ end
+
+ it "should return an empty array if the key doesn't have any values" do
+ key.provider.expects(:values).returns([])
+ key.eval_generate.must be_empty
+ end
+
+ it "should purge existing values that are not being managed" do
+ key.provider.expects(:values).returns(['val1', 'val3'])
+ res = key.eval_generate.first
+
+ res[:ensure].must == :absent
+ res[:path].must == "#{key[:path]}\\val3"
+ end
+
+ it "should return an empty array if all existing values are being managed" do
+ key.provider.expects(:values).returns(['val1', 'val2'])
+ key.eval_generate.must be_empty
+ end
+ end
+ end
+
+ describe "#autorequire" do
+ let :the_catalog do
+ Puppet::Resource::Catalog.new
+ end
+
+ let(:the_resource_name) { 'HKLM\Software\Vendor\PuppetLabs' }
+
+ let :the_resource do
+ # JJM Holy cow this is an intertangled mess. ;)
+ resource = Puppet::Type.type(:registry_key).new(:name => the_resource_name, :catalog => the_catalog)
+ the_catalog.add_resource resource
+ resource
+ end
+
+ it 'Should initialize the catalog instance variable' do
+ the_resource.catalog.must be the_catalog
+ end
+
+ it 'Should allow case insensitive lookup using the downcase path' do
+ the_resource.must be the_catalog.resource(:registry_key, the_resource_name.downcase)
+ end
+
+ it 'Should preserve the case of the user specified path' do
+ the_resource.must be the_catalog.resource(:registry_key, the_resource_name)
+ end
+
+ it 'Should return the same resource regardless of the alias used' do
+ the_resource.must be the_catalog.resource(:registry_key, the_resource_name)
+ the_resource.must be the_catalog.resource(:registry_key, the_resource_name.downcase)
+ end
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/type/registry_value_spec.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/type/registry_value_spec.rb
new file mode 100644
index 000000000..03deb1c7c
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/unit/puppet/type/registry_value_spec.rb
@@ -0,0 +1,161 @@
+#!/usr/bin/env ruby -S rspec
+require 'spec_helper'
+require 'puppet/type/registry_value'
+
+describe Puppet::Type.type(:registry_value) do
+ let (:catalog) do Puppet::Resource::Catalog.new end
+
+ [:ensure, :type, :data].each do |property|
+ it "should have a #{property} property" do
+ described_class.attrclass(property).ancestors.should be_include(Puppet::Property)
+ end
+
+ it "should have documentation for its #{property} property" do
+ described_class.attrclass(property).doc.should be_instance_of(String)
+ end
+ end
+
+ describe "path parameter" do
+ it "should have a path parameter" do
+ Puppet::Type.type(:registry_key).attrtype(:path).should == :param
+ end
+
+ %w[hklm\propname hklm\software\propname].each do |path|
+ it "should accept #{path}" do
+ described_class.new(:path => path, :catalog => catalog)
+ end
+ end
+
+ %w[hklm\\ hklm\software\\ hklm\software\vendor\\].each do |path|
+ it "should accept the unnamed (default) value: #{path}" do
+ described_class.new(:path => path, :catalog => catalog)
+ end
+ end
+
+ it "should strip trailling slashes from unnamed values" do
+ described_class.new(:path => 'hklm\\software\\\\', :catalog => catalog)
+ end
+
+ %w[HKEY_DYN_DATA\\ HKEY_PERFORMANCE_DATA\name].each do |path|
+ it "should reject #{path} as unsupported" do
+ expect { described_class.new(:path => path, :catalog => catalog) }.to raise_error(Puppet::Error, /Unsupported/)
+ end
+ end
+
+ %w[hklm hkcr unknown\\name unknown\\subkey\\name].each do |path|
+ it "should reject #{path} as invalid" do
+ pending 'wrong message'
+ expect { described_class.new(:path => path, :catalog => catalog) }.should raise_error(Puppet::Error, /Invalid registry key/)
+ end
+ end
+
+ %w[HKLM\\name HKEY_LOCAL_MACHINE\\name hklm\\name].each do |root|
+ it "should canonicalize root key #{root}" do
+ value = described_class.new(:path => root, :catalog => catalog)
+ value[:path].should == 'hklm\name'
+ end
+ end
+
+ it 'should validate the length of the value name'
+ it 'should validate the length of the value data'
+ it 'should be case-preserving'
+ it 'should be case-insensitive'
+ it 'should autorequire ancestor keys'
+ it 'should support 32-bit values' do
+ value = described_class.new(:path => '32:hklm\software\foo', :catalog => catalog)
+ end
+ end
+
+ describe "type property" do
+ let (:value) { described_class.new(:path => 'hklm\software\foo', :catalog => catalog) }
+
+ [:string, :array, :dword, :qword, :binary, :expand].each do |type|
+ it "should support a #{type.to_s} type" do
+ value[:type] = type
+ value[:type].should == type
+ end
+ end
+
+ it "should reject other types" do
+ expect { value[:type] = 'REG_SZ' }.to raise_error(Puppet::Error)
+ end
+ end
+
+ describe "data property" do
+ let (:value) { described_class.new(:path => 'hklm\software\foo', :catalog => catalog) }
+
+ context "string data" do
+ ['', 'foobar'].each do |data|
+ it "should accept '#{data}'" do
+ value[:type] = :string
+ value[:data] = data
+ end
+ end
+
+ pending "it should accept nil"
+ end
+
+ context "integer data" do
+ [:dword, :qword].each do |type|
+ context "for #{type}" do
+ [0, 0xFFFFFFFF, -1, 42].each do |data|
+ it "should accept #{data}" do
+ value[:type] = type
+ value[:data] = data
+ end
+ end
+
+ %w['foobar' :true].each do |data|
+ it "should reject #{data}" do
+ value[:type] = type
+ expect { value[:data] = data }.to raise_error(Puppet::Error)
+ end
+ end
+ end
+ end
+
+ context "for 64-bit integers" do
+ let :data do 0xFFFFFFFFFFFFFFFF end
+
+ it "should accept qwords" do
+ value[:type] = :qword
+ value[:data] = data
+ end
+
+ it "should reject dwords" do
+ value[:type] = :dword
+ expect { value[:data] = data }.to raise_error(Puppet::Error)
+ end
+ end
+ end
+
+ context "binary data" do
+ ['', 'CA FE BE EF', 'DEADBEEF'].each do |data|
+ it "should accept '#{data}'" do
+ value[:type] = :binary
+ value[:data] = data
+ end
+ end
+ [9,'1','A'].each do |data|
+ it "should accept '#{data}' and have a leading zero" do
+ value[:type] = :binary
+ value[:data] = data
+ end
+ end
+
+ ["\040\040", 'foobar', :true].each do |data|
+ it "should reject '#{data}'" do
+ value[:type] = :binary
+ expect { value[:data] = data }.to raise_error(Puppet::Error)
+ end
+ end
+ end
+
+ context "array data" do
+ it "should support array data" do
+ value[:type] = :array
+ value[:data] = ['foo', 'bar', 'baz']
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/registry/puppetlabs_registry_library/spec/watchr.rb b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/watchr.rb
new file mode 100644
index 000000000..5eff19b43
--- /dev/null
+++ b/modules/utilities/windows/registry/puppetlabs_registry_library/spec/watchr.rb
@@ -0,0 +1,85 @@
+ENV['AUTOTEST'] = 'true'
+ENV['WATCHR'] = '1'
+
+system 'clear'
+
+def growl(message)
+ growlnotify = `which growlnotify`.chomp
+ title = "Watchr Test Results"
+ image = case message
+ when /(\d+)\s+?(failure|error)/i
+ ($1.to_i == 0) ? "~/.watchr_images/passed.png" : "~/.watchr_images/failed.png"
+ else
+ '~/.watchr_images/unknown.png'
+ end
+ options = "-w -n Watchr --image '#{File.expand_path(image)}' -m '#{message}' '#{title}'"
+ system %(#{growlnotify} #{options} &)
+end
+
+def run(cmd)
+ puts(cmd)
+ `#{cmd}`
+end
+
+def run_spec_test(file)
+ if File.exist? file
+ result = run "rspec --format d --color #{file}"
+ growl result.split("\n").last
+ puts result
+ else
+ puts "FIXME: No test #{file} [#{Time.now}]"
+ end
+end
+
+def filter_rspec(data)
+ data.split("\n").find_all do |l|
+ l =~ /^(\d+)\s+exampl\w+.*?(\d+).*?failur\w+.*?(\d+).*?pending/
+ end.join("\n")
+end
+
+def run_all_tests
+ system('clear')
+ files = Dir.glob("spec/**/*_spec.rb").join(" ")
+ result = run "rspec --format d --color #{files}"
+ growl_results = filter_rspec result
+ growl growl_results
+ puts result
+ puts "GROWL: #{growl_results}"
+end
+
+# Ctrl-\
+Signal.trap 'QUIT' do
+ puts " --- Running all tests ---\n\n"
+ run_all_tests
+end
+
+@interrupted = false
+
+# Ctrl-C
+Signal.trap 'INT' do
+ if @interrupted then
+ @wants_to_quit = true
+ abort("\n")
+ else
+ puts "Interrupt a second time to quit"
+ @interrupted = true
+ Kernel.sleep 1.5
+ # raise Interrupt, nil # let the run loop catch it
+ run_suite
+ end
+end
+
+def file2spec(file)
+ result = file.sub('lib/puppet/', 'spec/unit/puppet/').gsub(/\.rb$/, '_spec.rb')
+ result = file.sub('lib/facter/', 'spec/unit/facter/').gsub(/\.rb$/, '_spec.rb')
+end
+
+
+watch( 'spec/.*_spec\.rb' ) do |md|
+ #run_spec_test(md[0])
+ run_all_tests
+end
+watch( 'lib/.*\.rb' ) do |md|
+ # run_spec_test(file2spec(md[0]))
+ run_all_tests
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/CHANGELOG.md b/modules/utilities/windows/repository_managers/chocolatey/CHANGELOG.md
new file mode 100644
index 000000000..ca4f59c21
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/CHANGELOG.md
@@ -0,0 +1,172 @@
+## 2016-12-30 Supported Release 2.0.1
+
+### Summary
+
+This is a bug fix release, correcting some issues in the original supported release and one that was introduced by the switchover to the puppetlabs-powershell v2 module.
+
+### Bug Fixes
+
+- Fix: ChocolateyInstall environment variable not set for alternate installation directory ([MODULES-4091](https://tickets.puppetlabs.com/browse/MODULES-4091))
+- Fix: Unsuitable providers should not cause errors ([MODULES-4149](https://tickets.puppetlabs.com/browse/MODULES-4149))
+- Fix: version is malformed with any extraneous messages ([MODULES-4135](https://tickets.puppetlabs.com/browse/MODULES-4135))
+- Fix: module does not propagate null source error correctly ([MODULES-4056](https://tickets.puppetlabs.com/browse/MODULES-4056))
+- Fix: install fails on Windows 10 when using built-in compression ([MODULES-4210](https://tickets.puppetlabs.com/browse/MODULES-4210))
+
+### Improvements
+
+- Set TLS 1.1+ when available
+- Document considerations for install to "C:\Chocolatey" ([MODULES-4090](https://tickets.puppetlabs.com/browse/MODULES-4090))
+
+
+## 2016-09-29 First Supported Release 2.0.0
+
+### Summary
+
+Puppetlabs-Chocolatey is now a supported module! This includes everything from the approved chocolatey-chocolatey module, plus the improvements in the unsupported releases 0.7.0 and 0.8.0. It also adds the following additional changes and fixes.
+
+### Features
+
+- `chocolateysource` - explicitly require location in ensure ([MODULES-3430](https://tickets.puppet.com/browse/MODULES-3430))
+- set ignore package exit codes when Chocolatey is on version 0.9.10+ ([MODULES-3880](https://tickets.puppet.com/browse/MODULES-3880))
+
+### Bug Fixes
+
+- Fix: Ensure config file exists before `chocolateyfeature`, `chocolateyconfig`, or `chocolateysource` ([MODULES-3677](https://tickets.puppet.com/browse/MODULES-3677))
+- Fix: `chocolateysource` - ensure flush when disabling source ([MODULES-3430](https://tickets.puppet.com/browse/MODULES-3430))
+- Fix: `chocolateysource` - erroneous user sync messages ([MODULES-3758](https://tickets.puppet.com/browse/MODULES-3758))
+
+
+## 2016-07-13 Unsupported Release 0.8.0
+
+This brings the unsupported puppetlabs-chocolatey provider on par with the approved chocolatey-chocolatey at 1.2.6 and adds additional features.
+
+ * Includes community module releases up to 1.2.6 (changelog below).
+ * Manage features - `chocolateyfeature` - see [MODULES-3034](https://tickets.puppet.com/browse/MODULES-3034)
+ * Manage config settings - `chocolateyconfig` - see [MODULES-3035](https://tickets.puppet.com/browse/MODULES-3035)
+
+
+## 2016-06-01 Unsupported Release 0.7.0
+
+ * Manage sources - `chocolateysource` - see [MODULES-3037](https://tickets.puppetlabs.com/browse/MODULES-3037)
+ * Includes community module releases up to 1.2.1 (changelog below up to 1.2.1), plus these additional fixes:
+ * $::chocolateyversion fact is optional - see [#110](https://github.com/chocolatey/puppet-chocolatey/issues/110)
+ * Fix: puppet apply works again - see [#105](https://github.com/chocolatey/puppet-chocolatey/issues/105)
+
+
+# Approved Community Module Changelog - Chocolatey Team
+
+The Chocolatey team has graciously agreed to allow Puppet to take this module
+to the next level. Puppet will rerelease a supported module under the original
+versioning scheme. For now we are using a number less than 1.0 to show that this
+could have some technical issues and should be treated as a prerelease version.
+
+## 2016-07-11 Release 1.2.6
+
+- Fix - AutoUninstaller runs every time in 0.9.9.x [#134](https://github.com/chocolatey/puppet-chocolatey/issues/134)
+
+
+## 2016-06-20 Release 1.2.5
+
+- Support feature list changes in v0.9.10+ [#133](https://github.com/chocolatey/puppet-chocolatey/issues/133)
+- Fix - Chocolatey fails to install in PowerShell v2 with PowerShell Module 1.x [#128](https://github.com/chocolatey/puppet-chocolatey/issues/128)
+
+
+## 2016-06-04 Release 1.2.4
+
+- Compatibility with puppetlabs-powershell 2.x [#125](https://github.com/chocolatey/puppet-chocolatey/issues/125).
+
+
+## 2016-05-06 Release 1.2.3
+
+- Do not call choco with --debug --verbose by default [#100](https://github.com/chocolatey/puppet-chocolatey/issues/100).
+- Announce [Chocolatey for Business](https://chocolatey.org/compare) in ReadMe.
+
+
+## 2016-05-06 Release 1.2.3
+
+- Do not call choco with --debug --verbose by default [#100](https://github.com/chocolatey/puppet-chocolatey/issues/100).
+- Announce Chocolatey for Business in ReadMe.
+
+
+## 2016-04-06 Release 1.2.2
+
+- Fix: puppet apply works again [#105](https://github.com/chocolatey/puppet-chocolatey/issues/105).
+- `$::chocolateyversion` fact is optional - see [#110](https://github.com/chocolatey/puppet-chocolatey/issues/110)
+- Fix: Implement PowerShell Redirection Fix for Windows 2008 / PowerShell v2 - see [#119](https://github.com/chocolatey/puppet-chocolatey/issues/119)
+
+
+## 2015-12-08 Release 1.2.1
+
+- Small release for support of newer PE versions.
+
+
+##2015-11-03 Release 1.2.0
+
+- Implement holdable ([#95](https://github.com/chocolatey/puppet-chocolatey/issues/95))
+- Fix - Use install unless version specified in install ([#71](https://github.com/chocolatey/puppet-chocolatey/issues/71))
+
+
+## 2015-10-02 Release 1.1.2
+
+- Ensure 0.9.9.9 compatibility ([#94](https://github.com/chocolatey/puppet-chocolatey/issues/94))
+- Fix - Mixed stale environment variables of existing choco install causing issues ([#86](https://github.com/chocolatey/puppet-chocolatey/issues/86))
+- Upgrade From POSH Version of Chocolatey Fails from Puppet ([#60](https://github.com/chocolatey/puppet-chocolatey/issues/60))
+
+
+## 2015-09-25 Release 1.1.1
+
+- Add log_output for chocolatey bootstrap installer script
+- Ensure bootstrap enforces chocolatey.nupkg in libs folder
+- Allow file location for installing nupkg file.
+
+
+## 2015-09-09 Release 1.1.0
+
+- Install Chocolatey itself / ensure Chocolatey is installed (PUP-1691)
+- Adds custom facts for chocolateyversion and choco_install_path
+
+
+## 2015-07-23 Release 1.0.2
+
+- Fixes [#71](https://github.com/chocolatey/puppet-chocolatey/issues/71) - Allow `ensure => $version` to work with already installed packages
+
+
+## 2015-07-01 Release 1.0.1
+
+- Fixes [#66](https://github.com/chocolatey/puppet-chocolatey/issues/66) - Check for choco existence more comprehensively
+
+
+## 2015-06-08 Release 1.0.0
+
+- No change, bumping to 1.0.0
+
+
+## 2015-05-22 Release 0.5.3
+
+- Fix manifest issue
+- Fix choco path issue
+- Update ReadMe - fix/clarify how options with quotes need to be passed.
+
+
+## 2015-04-23 Release 0.5.2
+
+- Update ReadMe
+- Add support for Windows 10.
+- Fixes [#56](https://github.com/chocolatey/puppet-chocolatey/pull/56) - Avoiding puppet returning 2 instead of 0 when there are no changes to be done.
+
+
+## 2015-03-31 Release 0.5.1
+
+- Fixes [#54](https://github.com/chocolatey/puppet-chocolatey/issues/54) - Blocking: Linux masters throw error if module is present
+
+
+## 2015-03-30 Release 0.5.0
+
+- Provider enhancements
+- Better docs
+- Works with both compiled and powershell Chocolatey clients
+- Fixes #50 - work with newer compiled Chocolatey client (0.9.9+)
+- Fixes #43 - check for installed packages is case sensitive
+- Fixes #18 - The OS handle's position is not what FileStream expected.
+- Fixes #52 - Document best way to pass options with spaces (#15 also related)
+- Fixes #26 - Document Chocolatey needs to be installed by other means
diff --git a/modules/utilities/windows/repository_managers/chocolatey/CONTRIBUTING.md b/modules/utilities/windows/repository_managers/chocolatey/CONTRIBUTING.md
new file mode 100644
index 000000000..dd2b5c4ff
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/CONTRIBUTING.md
@@ -0,0 +1,219 @@
+Checklist (and a short version for the impatient)
+=================================================
+
+ * Commits:
+
+ - Make commits of logical units.
+
+ - Check for unnecessary whitespace with "git diff --check" before
+ committing.
+
+ - Commit using Unix line endings (check the settings around "crlf" in
+ git-config(1)).
+
+ - Do not check in commented out code or unneeded files.
+
+ - The first line of the commit message should be a short
+ description (50 characters is the soft limit, excluding ticket
+ number(s)), and should skip the full stop.
+
+ - Associate the issue in the message. The first line should include
+ the issue number in the form "(#XXXX) Rest of message".
+
+ - The body should provide a meaningful commit message, which:
+
+ - uses the imperative, present tense: "change", not "changed" or
+ "changes".
+
+ - includes motivation for the change, and contrasts its
+ implementation with the previous behavior.
+
+ - Make sure that you have tests for the bug you are fixing, or
+ feature you are adding.
+
+ - Make sure the test suites passes after your commit:
+ `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below
+
+ - When introducing a new feature, make sure it is properly
+ documented in the README.md
+
+ * Submission:
+
+ * Pre-requisites:
+
+ - Make sure you have a [GitHub account](https://github.com/join)
+
+ - [Create a ticket](https://tickets.puppet.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppet.com/browse/) you are patching for.
+
+ * Preferred method:
+
+ - Fork the repository on GitHub.
+
+ - Push your changes to a topic branch in your fork of the
+ repository. (the format ticket/1234-short_description_of_change is
+ usually preferred for this project).
+
+ - Submit a pull request to the repository in the puppetlabs
+ organization.
+
+The long version
+================
+
+ 1. Make separate commits for logically separate changes.
+
+ Please break your commits down into logically consistent units
+ which include new or changed tests relevant to the rest of the
+ change. The goal of doing this is to make the diff easier to
+ read for whoever is reviewing your code. In general, the easier
+ your diff is to read, the more likely someone will be happy to
+ review it and get it into the code base.
+
+ If you are going to refactor a piece of code, please do so as a
+ separate commit from your feature or bug fix changes.
+
+ We also really appreciate changes that include tests to make
+ sure the bug is not re-introduced, and that the feature is not
+ accidentally broken.
+
+ Describe the technical detail of the change(s). If your
+ description starts to get too long, that is a good sign that you
+ probably need to split up your commit into more finely grained
+ pieces.
+
+ Commits which plainly describe the things which help
+ reviewers check the patch and future developers understand the
+ code are much more likely to be merged in with a minimum of
+ bike-shedding or requested changes. Ideally, the commit message
+ would include information, and be in a form suitable for
+ inclusion in the release notes for the version of Puppet that
+ includes them.
+
+ Please also check that you are not introducing any trailing
+ whitespace or other "whitespace errors". You can do this by
+ running "git diff --check" on your changes before you commit.
+
+ 2. Sending your patches
+
+ To submit your changes via a GitHub pull request, we _highly_
+ recommend that you have them on a topic branch, instead of
+ directly on "master".
+ It makes things much easier to keep track of, especially if
+ you decide to work on another thing before your first change
+ is merged in.
+
+ GitHub has some pretty good
+ [general documentation](http://help.github.com/) on using
+ their site. They also have documentation on
+ [creating pull requests](http://help.github.com/send-pull-requests/).
+
+ In general, after pushing your topic branch up to your
+ repository on GitHub, you can switch to the branch in the
+ GitHub UI and click "Pull Request" towards the top of the page
+ in order to open a pull request.
+
+
+ 3. Update the related GitHub issue.
+
+ If there is a GitHub issue associated with the change you
+ submitted, then you should update the ticket to include the
+ location of your branch, along with any other commentary you
+ may wish to make.
+
+Testing
+=======
+
+Getting Started
+---------------
+
+Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby
+package manager such as [bundler](http://bundler.io/) what Ruby packages,
+or Gems, are required to build, develop, and test this software.
+
+Please make sure you have [bundler installed](http://bundler.io/#getting-started)
+on your system, then use it to install all dependencies needed for this project,
+by running
+
+```shell
+% bundle install
+Fetching gem metadata from https://rubygems.org/........
+Fetching gem metadata from https://rubygems.org/..
+Using rake (10.1.0)
+Using builder (3.2.2)
+-- 8><-- many more --><8 --
+Using rspec-system-puppet (2.2.0)
+Using serverspec (0.6.3)
+Using rspec-system-serverspec (1.0.0)
+Using bundler (1.3.5)
+Your bundle is complete!
+Use `bundle show [gemname]` to see where a bundled gem is installed.
+```
+
+NOTE some systems may require you to run this command with sudo.
+
+If you already have those gems installed, make sure they are up-to-date:
+
+```shell
+% bundle update
+```
+
+With all dependencies in place and up-to-date we can now run the tests:
+
+```shell
+% rake spec
+```
+
+This will execute all the [rspec tests](http://rspec-puppet.com/) tests
+under [spec/defines](./spec/defines), [spec/classes](./spec/classes),
+and so on. rspec tests may have the same kind of dependencies as the
+module they are testing. While the module defines in its [Modulefile](./Modulefile),
+rspec tests define them in [.fixtures.yml](./fixtures.yml).
+
+Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker)
+tests. These tests spin up a virtual machine under
+[VirtualBox](https://www.virtualbox.org/)) with, controlling it with
+[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test
+scenarios. In order to run these, you will need both of those tools
+installed on your system.
+
+You can run them by issuing the following command
+
+```shell
+% rake spec_clean
+% rspec spec/acceptance
+```
+
+This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml),
+install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb)
+and then run all the tests under [spec/acceptance](./spec/acceptance).
+
+Writing Tests
+-------------
+
+XXX getting started writing tests.
+
+If you have commit access to the repository
+===========================================
+
+Even if you have commit access to the repository, you will still need to
+go through the process above, and have someone else review and merge
+in your changes. The rule is that all changes must be reviewed by a
+developer on the project (that did not write the code) to ensure that
+all changes go through a code review process.
+
+Having someone other than the author of the topic branch recorded as
+performing the merge is the record that they performed the code
+review.
+
+
+Additional Resources
+====================
+
+* [Getting additional help](http://puppet.com/community/get-help)
+
+* [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing)
+
+* [General GitHub documentation](http://help.github.com/)
+
+* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
+
+
diff --git a/modules/utilities/windows/repository_managers/chocolatey/Gemfile b/modules/utilities/windows/repository_managers/chocolatey/Gemfile
new file mode 100644
index 000000000..d8669c00e
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/Gemfile
@@ -0,0 +1,152 @@
+#This file is generated by ModuleSync, do not edit.
+
+source ENV['GEM_SOURCE'] || "https://rubygems.org"
+
+# Determines what type of gem is requested based on place_or_version.
+def gem_type(place_or_version)
+ if place_or_version =~ /^git:/
+ :git
+ elsif place_or_version =~ /^file:/
+ :utility
+ else
+ :gem
+ end
+end
+
+# Find a location or specific version for a gem. place_or_version can be a
+# version, which is most often used. It can also be git, which is specified as
+# `git://somewhere.git#branch`. You can also use a file source location, which
+# is specified as `file://some/location/on/disk`.
+def location_for(place_or_version, fake_version = nil)
+ if place_or_version =~ /^(git[:@][^#]*)#(.*)/
+ [fake_version, { :git => $1, :branch => $2, :require => false }].compact
+ elsif place_or_version =~ /^file:\/\/(.*)/
+ ['>= 0', { :path => File.expand_path($1), :require => false }]
+ else
+ [place_or_version, { :require => false }]
+ end
+end
+
+# Used for gem conditionals
+supports_windows = true
+
+# The following gems are not included by default as they require DevKit on Windows.
+# You should probably include them in a Gemfile.local or a ~/.gemfile
+#gem 'pry' #this may already be included in the gemfile
+#gem 'pry-stack_explorer', :require => false
+#if RUBY_VERSION =~ /^2/
+# gem 'pry-byebug'
+#else
+# gem 'pry-debugger'
+#end
+
+group :development do
+ gem 'rake', :require => false
+ gem 'rspec', '~>3.0', :require => false
+ gem 'puppet-lint', :require => false
+ gem 'puppetlabs_spec_helper', '~>0.10.3', :require => false
+ gem 'puppet_facts', :require => false
+ gem 'mocha', '~>0.10.5', :require => false
+ gem 'pry', :require => false
+ gem 'json_pure', '<= 2.0.1', :require => false if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0')
+end
+
+group :system_tests do
+ gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '~> 2.20')
+ gem 'master_manipulator', '~> 1.2', :require => false
+ gem 'beaker-windows', '~> 0.6', :require => false
+ gem 'beaker-hostgenerator', '~> 0.6', :require => false
+end
+
+gem 'puppet', *location_for(ENV['PUPPET_GEM_VERSION'])
+
+# Only explicitly specify Facter/Hiera if a version has been specified.
+# Otherwise it can lead to strange bundler behavior. If you are seeing weird
+# gem resolution behavior, try setting `DEBUG_RESOLVER` environment variable
+# to `1` and then run bundle install.
+gem 'facter', *location_for(ENV['FACTER_GEM_VERSION']) if ENV['FACTER_GEM_VERSION']
+gem 'hiera', *location_for(ENV['HIERA_GEM_VERSION']) if ENV['HIERA_GEM_VERSION']
+
+# For Windows dependencies, these could be required based on the version of
+# Puppet you are requiring. Anything greater than v3.5.0 is going to have
+# Windows-specific dependencies dictated by the gem itself. The other scenario
+# is when you are faking out Puppet to use a local file path / git path.
+explicitly_require_windows_gems = false
+puppet_gem_location = gem_type(ENV['PUPPET_GEM_VERSION'])
+# This is not a perfect answer to the version check
+if puppet_gem_location != :gem || (ENV['PUPPET_GEM_VERSION'] && Gem::Version.correct?(ENV['PUPPET_GEM_VERSION']) && Gem::Requirement.new('< 3.5.0').satisfied_by?(Gem::Version.new(ENV['PUPPET_GEM_VERSION'].dup)))
+ if Gem::Platform.local.os == 'mingw32'
+ explicitly_require_windows_gems = true
+ end
+ if puppet_gem_location == :gem
+ # If facterversion hasn't been specified and we are
+ # looking for a Puppet Gem version less than 3.5.0, we
+ # need to ensure we get a good Facter for specs.
+ gem "facter",">= 1.6.11","<= 1.7.5",:require => false unless ENV['FACTER_GEM_VERSION']
+ # If hieraversion hasn't been specified and we are
+ # looking for a Puppet Gem version less than 3.5.0, we
+ # need to ensure we get a good Hiera for specs.
+ gem "hiera",">= 1.0.0","<= 1.3.0",:require => false unless ENV['HIERA_GEM_VERSION']
+ end
+end
+
+if explicitly_require_windows_gems
+ # This also means Puppet Gem less than 3.5.0 - this has been tested back
+ # to 3.0.0. Any further back is likely not supported.
+ if puppet_gem_location == :gem
+ gem "ffi", "1.9.0", :require => false
+ gem "win32-eventlog", "0.5.3","<= 0.6.5", :require => false
+ gem "win32-process", "0.6.5","<= 0.7.5", :require => false
+ gem "win32-security", "~> 0.1.2","<= 0.2.5", :require => false
+ gem "win32-service", "0.7.2","<= 0.8.8", :require => false
+ gem "minitar", "0.5.4", :require => false
+ else
+ gem "ffi", "~> 1.9.0", :require => false
+ gem "win32-eventlog", "~> 0.5","<= 0.6.5", :require => false
+ gem "win32-process", "~> 0.6","<= 0.7.5", :require => false
+ gem "win32-security", "~> 0.1","<= 0.2.5", :require => false
+ gem "win32-service", "~> 0.7","<= 0.8.8", :require => false
+ gem "minitar", "~> 0.5.4", :require => false
+ end
+
+ gem "win32-dir", "~> 0.3","<= 0.4.9", :require => false
+ gem "win32console", "1.3.2", :require => false if RUBY_VERSION =~ /^1\./
+
+ # sys-admin was removed in Puppet 3.7.0+, and doesn't compile
+ # under Ruby 2.3 - so restrict it to Ruby 1.x
+ gem "sys-admin", "1.5.6", :require => false if RUBY_VERSION =~ /^1\./
+
+ # Puppet less than 3.7.0 requires these.
+ # Puppet 3.5.0+ will control the actual requirements.
+ # These are listed in formats that work with all versions of
+ # Puppet from 3.0.0 to 3.6.x. After that, these were no longer used.
+ # We do not want to allow newer versions than what came out after
+ # 3.6.x to be used as they constitute some risk in breaking older
+ # functionality. So we set these to exact versions.
+ gem "win32-api", "1.4.8", :require => false
+ gem "win32-taskscheduler", "0.2.2", :require => false
+ gem "windows-api", "0.4.3", :require => false
+ gem "windows-pr", "1.2.3", :require => false
+else
+ if Gem::Platform.local.os == 'mingw32'
+ # If we're using a Puppet gem on windows, which handles its own win32-xxx gem dependencies (Pup 3.5.0 and above), set maximum versions
+ # Required due to PUP-6445
+ gem "win32-dir", "<= 0.4.9", :require => false
+ gem "win32-eventlog", "<= 0.6.5", :require => false
+ gem "win32-process", "<= 0.7.5", :require => false
+ gem "win32-security", "<= 0.2.5", :require => false
+ gem "win32-service", "<= 0.8.8", :require => false
+ end
+end
+
+# Evaluate Gemfile.local if it exists
+if File.exists? "#{__FILE__}.local"
+ eval(File.read("#{__FILE__}.local"), binding)
+end
+
+# Evaluate ~/.gemfile if it exists
+if File.exists?(File.join(Dir.home, '.gemfile'))
+ eval(File.read(File.join(Dir.home, '.gemfile')), binding)
+end
+
+# vim:ft=ruby
diff --git a/modules/utilities/windows/repository_managers/chocolatey/LICENSE b/modules/utilities/windows/repository_managers/chocolatey/LICENSE
new file mode 100644
index 000000000..7a4a3ea24
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
\ No newline at end of file
diff --git a/modules/utilities/windows/repository_managers/chocolatey/MAINTAINERS.md b/modules/utilities/windows/repository_managers/chocolatey/MAINTAINERS.md
new file mode 100644
index 000000000..980e355ee
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/MAINTAINERS.md
@@ -0,0 +1,6 @@
+## Maintenance
+
+Maintainers:
+ - Puppet Windows Team `windows |at| puppet |dot| com`
+
+Tickets: https://tickets.puppet.com/browse/MODULES. Make sure to set component to `chocolatey`.
\ No newline at end of file
diff --git a/modules/utilities/windows/repository_managers/chocolatey/NOTICE b/modules/utilities/windows/repository_managers/chocolatey/NOTICE
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/utilities/windows/repository_managers/chocolatey/README.md b/modules/utilities/windows/repository_managers/chocolatey/README.md
new file mode 100644
index 000000000..73bdbbf6d
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/README.md
@@ -0,0 +1,783 @@
+# Chocolatey Package Provider for Puppet
+
+### Chocolatey for Business Now Available!
+
+We're excited for you to learn more about what's available in the [Business editions](https://chocolatey.org/compare)!
+
+## Build Status
+
+Travis | AppVeyor
+------------- | -------------
+[![Build Status](https://api.travis-ci.org/puppetlabs/puppetlabs-chocolatey.png?branch=master)](https://travis-ci.org/puppetlabs/puppetlabs-chocolatey) | [![Build status](https://ci.appveyor.com/api/projects/status/uosorvcyhnayv70m/branch/master?svg=true)](https://ci.appveyor.com/project/puppetlabs/puppetlabs-chocolatey/branch/master)
+
+#### Table of Contents
+
+1. [Overview](#overview)
+2. [Module Description - What the chocolatey module does and why it is useful](#module-description)
+ * [Why Chocolatey](#why-chocolatey)
+3. [Setup - The basics of getting started with chocolatey](#setup)
+ * [What chocolatey affects](#what-chocolatey-affects)
+ * [Setup requirements](#setup-requirements)
+ * [Beginning with Chocolatey provider](#beginning-with-chocolatey-provider)
+4. [Usage - Configuration options and additional functionality](#usage)
+5. [Reference](#reference)
+ * [Classes](#public-classes)
+ * [Facts](#facts)
+ * [Types/Providers](#typesproviders)
+ * [Package provider: Chocolatey](#package-provider-chocolatey)
+ * [Chocolatey source configuration](#chocolateysource)
+ * [Chocolatey feature configuration](#chocolateyfeature)
+ * [Chocolatey config configuration](#chocolateyconfig)
+ * [Class: chocolatey](#class-chocolatey)
+6. [Limitations - OS compatibility, etc.](#limitations)
+ * [Known Issues](#known-issues)
+7. [Development - Guide for contributing to the module](#development)
+8. [Attributions](#attributions)
+
+## Overview
+
+This is a [Puppet](http://docs.puppet.com/) package provider for
+[Chocolatey](https://github.com/chocolatey/chocolatey), which is
+like apt-get, but for Windows. Check the module's metadata.json for
+compatible Puppet and Puppet Enterprise versions.
+
+## Module Description
+
+This is the official module for working with the [Chocolatey](https://chocolatey.org/about)
+package manager.
+
+This module supports all editions of Chocolatey, including FOSS, [Professional](https://chocolatey.org/compare) and [Chocolatey for Business](https://chocolatey.org/compare).
+
+This module is able to:
+
+* Install Chocolatey
+* Work with custom location installations
+* Configure Chocolatey
+* Use Chocolatey as a package provider
+
+### Why Chocolatey
+
+Chocolatey closely mimics how package managers on other operating systems work. If you can imagine the built-in provider for
+Windows versus Chocolatey, take a look at the use case of installing git:
+
+~~~puppet
+# Using built-in provider
+package { "Git version 1.8.4-preview20130916":
+ ensure => installed,
+ source => 'C:\temp\Git-1.8.4-preview20130916.exe',
+ install_options => ['/VERYSILENT']
+}
+~~~
+
+~~~puppet
+# Using Chocolatey (set as default for Windows)
+package { 'git':
+ ensure => latest,
+}
+~~~
+
+With the built-in provider:
+ * The [package name must match ***exactly***](https://docs.puppet.com/puppet/latest/reference/resources_package_windows.html#package-name-must-be-the-displayname) the name from installed programs.
+ * The package name has issues with unicode characters.
+ * The [source must point to the location](https://docs.puppet.com/puppet/latest/reference/resources_package_windows.html#the-source-attribute-is-required) of the executable installer.
+ * It cannot `ensure => latest`. Read about [handling versions and upgrades](https://docs.puppet.com/puppet/latest/reference/resources_package_windows.html#handling-versions-and-upgrades) in the Puppet documentation.
+
+With Chocolatey's provider:
+ * The package name only has to match the name of the package, which can be whatever you choose.
+ * The package knows how to install the software silently.
+ * The package knows where to get the executable installer.
+ * The source is able to specify different Chocolatey feeds.
+ * Chocolatey makes `package` more platform agnostic, because it looks exactly like other platforms.
+
+For reference, read about the [provider features available](https://docs.puppet.com/references/latest/type.html#package-provider-features) from the built-in provider, compared to other package managers:
+
+| Provider | holdable | install options | installable | package settings | purgeable | reinstallable | uninstall options | uninstallable | upgradeable | versionable | virtual packages |
+|------------|----------|-----------------|-------------|------------------|-----------|---------------|-------------------|---------------|-------------|-------------|------------------|
+| Windows | | x | x | | | | x | x | | x | |
+| Chocolatey | x | x | x | | | | x | x | x | x | |
+| apt | x | x | x | | x | | | x | x | x | |
+| yum | | x | x | | x | | | x | x | x | x |
+
+## Setup
+
+### What Chocolatey affects
+
+Chocolatey affects your system and what software is installed on it, ranging
+from tools and portable software, to natively installed applications.
+
+### Setup Requirements
+
+Chocolatey requires the following components:
+
+ * Powershell v2+ (Installed on most systems by default)
+ * .NET Framework v4+
+
+### Beginning with Chocolatey provider
+
+Install this module via any of these approaches:
+
+* [Puppet Forge](http://forge.puppet.com/chocolatey/chocolatey)
+* git-submodule ([tutorial](http://goo.gl/e9aXh))
+* [librarian-puppet](https://github.com/rodjek/librarian-puppet)
+* [r10k](https://github.com/puppetlabs/r10k)
+
+## Usage
+
+### Manage Chocolatey installation
+
+Ensure Chocolatey is installed and configured:
+
+~~~puppet
+include chocolatey
+~~~
+
+#### Override default Chocolatey install location
+
+~~~puppet
+class {'chocolatey':
+ choco_install_location => 'D:\secured\choco',
+}
+~~~
+
+**NOTE:** This will affect suitability on first install. There are also
+special considerations for `C:\Chocolatey` as an install location, see
+[`choco_install_location`](#choco_install_location) for details.
+
+#### Use an internal chocolatey.nupkg for Chocolatey installation
+
+~~~puppet
+class {'chocolatey':
+ chocolatey_download_url => 'https://internalurl/to/chocolatey.nupkg',
+ use_7zip => false,
+ choco_install_timeout_seconds => 2700,
+}
+~~~
+
+#### Use a file chocolatey.0.9.9.9.nupkg for installation
+
+~~~puppet
+class {'chocolatey':
+ chocolatey_download_url => 'file:///c:/location/of/chocolatey.0.9.9.9.nupkg',
+ use_7zip => false,
+ choco_install_timeout_seconds => 2700,
+}
+~~~
+
+#### Specify the version of chocolatey by class parameters
+
+~~~puppet
+class {'chocolatey':
+ chocolatey_download_url => 'file:///c:/location/of/chocolatey.0.9.9.9.nupkg',
+ use_7zip => false,
+ choco_install_timeout_seconds => 2700,
+ chocolatey_version => '0.9.9.9',
+}
+~~~
+
+
+#### Log chocolatey bootstrap installer script output
+
+~~~puppet
+class {'chocolatey':
+ log_output => true,
+}
+~~~
+
+
+### Configuration
+
+If you have Chocolatey 0.9.9.x or above, you can take advantage of configuring different aspects of Chocolatey.
+
+#### Sources Configuration
+
+You can specify sources that Chocolatey uses by default, along with priority.
+
+Requires Chocolatey v0.9.9.0+.
+
+##### Disable the default community repository source
+
+~~~puppet
+chocolateysource {'chocolatey':
+ ensure => disabled,
+}
+~~~
+
+##### Set a priority on a source
+
+~~~puppet
+chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ priority => 1,
+}
+~~~
+
+##### Add credentials to a source
+
+~~~puppet
+chocolateysource {'sourcename':
+ ensure => present,
+ location => 'https://internal/source',
+ user => 'username',
+ password => 'password',
+}
+~~~
+
+**NOTE:** Chocolatey encrypts the password in a way that is not
+verifiable. If you need to rotate passwords, you cannot use this
+resource to do so unless you also change the location, user, or priority
+(because those are ensurable properties).
+
+#### Features Configuration
+
+You can configure features that Chocolatey has available. Run
+`choco feature list` to see the available configuration features.
+
+Requires Chocolatey v0.9.9.0+.
+
+##### Enable Auto Uninstaller
+
+Uninstall from Programs and Features without requiring an explicit
+uninstall script.
+
+~~~puppet
+chocolateyfeature {'autouninstaller':
+ ensure => enabled,
+}
+~~~
+
+##### Disable Use Package Exit Codes
+
+Requires 0.9.10+ for this feature.
+
+**Use Package Exit Codes** - Allows package scripts to provide exit codes. With
+this enabled, Chocolatey uses package exit codes for exit when
+non-zero (this value can come from a dependency package). Chocolatey
+defines valid exit codes as 0, 1605, 1614, 1641, 3010. With this feature
+disabled, Chocolatey exits with a 0 or a 1 (matching previous behavior).
+
+~~~puppet
+chocolateyfeature {'usepackageexitcodes':
+ ensure => disabled,
+}
+~~~
+
+##### Enable Virus Check
+
+Requires 0.9.10+ and [Chocolatey Pro / Business](https://chocolatey.org/compare)
+for this feature.
+
+**Virus Check** - Performs virus checking on downloaded files. *(Licensed versions only.)*
+
+~~~puppet
+chocolateyfeature {'viruscheck':
+ ensure => enabled,
+}
+~~~
+
+##### Enable FIPS Compliant Checksums
+
+Requires 0.9.10+ for this feature.
+
+**Use FIPS Compliant Checksums** - Ensures checksumming done by Chocolatey uses
+FIPS compliant algorithms. *Not recommended unless required by FIPS Mode.*
+Enabling on an existing installation could have unintended consequences
+related to upgrades or uninstalls.
+
+~~~puppet
+chocolateyfeature {'usefipscompliantchecksums':
+ ensure => enabled,
+}
+~~~
+
+#### Config configuration
+
+You can configure config values that Chocolatey has available. Run
+`choco config list` to see the config settings available (just the
+config settings section).
+
+Requires Chocolatey v0.9.10.0+.
+
+##### Set cache location
+
+The cache location defaults to the TEMP directory. You can set an explicit directory
+to cache downloads to instead of the default.
+
+~~~puppet
+chocolateyconfig {'cachelocation':
+ value => "c:\\downloads",
+}
+~~~
+
+##### Unset cache location
+
+Removes cache location setting, returning the setting to its default.
+
+~~~puppet
+chocolateyconfig {'cachelocation':
+ ensure => absent,
+}
+~~~
+
+##### Use an explicit proxy
+
+When using Chocolatey behind a proxy, set `proxy` and optionally
+`proxyUser` and `proxyPassword`.
+
+**NOTE:** The `proxyPassword` value is not verifiable.
+
+~~~puppet
+chocolateyconfig {'proxy':
+ value => 'https://someproxy.com',
+}
+
+chocolateyconfig {'proxyUser':
+ value => 'bob',
+}
+
+# not verifiable
+chocolateyconfig {'proxyPassword':
+ value => 'securepassword',
+}
+~~~
+
+#### Set Chocolatey as Default Windows Provider
+
+If you want to set this provider as the site-wide default,
+add to your `site.pp`:
+
+~~~puppet
+if $::kernel == 'windows' {
+ Package { provider => chocolatey, }
+}
+
+# OR
+
+case $operatingsystem {
+ 'windows': {
+ Package { provider => chocolatey, }
+ }
+}
+~~~
+
+### Packages
+
+#### With all options
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => installed|latest|'1.0.0'|absent,
+ provider => 'chocolatey',
+ install_options => ['-pre','-params','"','param1','param2','"'],
+ uninstall_options => ['-r'],
+ source => 'https://myfeed.example.com/api/v2',
+}
+~~~
+
+* Supports `installable` and `uninstallable`.
+* Supports `versionable` so that `ensure => '1.0'` works.
+* Supports `upgradeable`.
+* Supports `latest` (checks upstream), `absent` (uninstall).
+* Supports `install_options` for pre-release, and other command-line options.
+* Supports `uninstall_options` for pre-release, and other command-line options.
+* Supports `holdable`, requires Chocolatey v0.9.9.0+.
+
+#### Simple install
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => installed,
+ provider => 'chocolatey',
+}
+~~~
+
+#### To always ensure using the newest version available
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => latest,
+ provider => 'chocolatey',
+}
+~~~
+
+#### To ensure a specific version
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => '6.7.5',
+ provider => 'chocolatey',
+}
+~~~
+
+#### To specify custom source
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => '6.7.5',
+ provider => 'chocolatey',
+ source => 'C:\local\folder\packages',
+}
+~~~
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => '6.7.5',
+ provider => 'chocolatey',
+ source => '\\unc\source\packages',
+}
+~~~
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => '6.7.5',
+ provider => 'chocolatey',
+ source => 'https://custom.nuget.odata.feed/api/v2/',
+}
+~~~
+
+~~~puppet
+package { 'notepadplusplus':
+ ensure => '6.7.5',
+ provider => 'chocolatey',
+ source => 'C:\local\folder\packages;https://chocolatey.org/api/v2/',
+}
+~~~
+
+#### Install options with spaces
+
+Spaces in arguments **must always** be covered with a separation. Shown
+below is an example of how you configure `-installArgs "/VERYSILENT /NORESTART"`.
+
+~~~puppet
+package {'launchy':
+ ensure => installed,
+ provider => 'chocolatey',
+ install_options => ['-override', '-installArgs', '"', '/VERYSILENT', '/NORESTART', '"'],
+}
+~~~
+
+#### Install options with quotes or spaces
+
+The underlying installer may need quotes passed to it. This is possible, but not
+as intuitive. The example below covers passing `/INSTALLDIR="C:\Program Files\somewhere"`.
+
+For this to be passed through with Chocolatey, you need a set of double
+quotes surrounding the argument and two sets of double quotes surrounding the
+item that must be quoted (see [how to pass/options/switches](https://github.com/chocolatey/choco/wiki/CommandsReference#how-to-pass-options--switches)). This makes the
+string look like `-installArgs "/INSTALLDIR=""C:\Program Files\somewhere"""` for
+proper use with Chocolatey.
+
+Then, for Puppet to handle that appropriately, you must split on ***every*** space.
+Yes, on **every** space you must split the string or the result comes out
+incorrectly. This means it will look like the following:
+
+~~~puppet
+install_options => ['-installArgs',
+ '"/INSTALLDIR=""C:\Program', 'Files\somewhere"""']
+~~~
+
+Make sure you have all of the right quotes - start it off with a single double
+quote, then two double quotes, then close it all by closing the two double
+quotes and then the single double quote or a possible three double quotes at
+the end.
+
+~~~puppet
+package {'mysql':
+ ensure => latest,
+ provider => 'chocolatey',
+ install_options => ['-override', '-installArgs',
+ '"/INSTALLDIR=""C:\Program', 'Files\somewhere"""'],
+}
+~~~
+
+You can split it up a bit for readability if it suits you:
+
+~~~puppet
+package {'mysql':
+ ensure => latest,
+ provider => 'chocolatey',
+ install_options => ['-override', '-installArgs', '"'
+ '/INSTALLDIR=""C:\Program', 'Files\somewhere""',
+ '"'],
+}
+~~~
+
+**Note:** The above is for Chocolatey v0.9.9+. You may need to look for an
+alternative method to pass args if you have 0.9.8.x and below.
+
+## Reference
+
+### Classes
+
+#### Public classes
+
+* [`chocolatey`](#class-chocolatey)
+
+#### Private classes
+
+* `chocolatey::install.pp`: Ensures Chocolatey is installed.
+* `chocolatey::config.pp`: Ensures Chocolatey is configured.
+
+### Facts
+
+* `chocolateyversion` - The version of the installed Chocolatey client (could also be provided by class parameter `chocolatey_version`).
+* `choco_install_path` - The location of the installed Chocolatey client (could also be provided by class parameter `choco_install_location`).
+
+### Types/Providers
+
+* [Chocolatey provider](#package-provider-chocolatey)
+* [Chocolatey source configuration](#chocolateysource)
+* [Chocolatey feature configuration](#chocolateyfeature)
+
+
+### Package provider: Chocolatey
+
+Chocolatey implements a [package type](http://docs.puppet.com/references/latest/type.html#package) with a resource provider, which is built into Puppet.
+
+This provider supports the `install_options` and `uninstall_options` attributes,
+which allow command-line options to be passed to the `choco` command. These options
+should be specified as documented below.
+
+ * Required binaries: `choco.exe`, usually found in `C:\Program Data\chocolatey\bin\choco.exe`.
+ * The binary is searched for using the environment variable `ChocolateyInstall`, then by two known locations (`C:\Chocolatey\bin\choco.exe` and `C:\ProgramData\chocolatey\bin\choco.exe`).
+ * Supported features: `install_options`, `installable`, `uninstall_options`,
+`uninstallable`, `upgradeable`, `versionable`.
+
+**NOTE**: the root of `C:\` is not a secure location by default, so you may want to update the security on the folder.
+
+#### Properties/Parameters
+
+##### `ensure`
+
+(**Property**: This attribute represents a concrete state on the target system.)
+
+Specifies what state the package should be in. You can choose which package to retrieve by
+specifying a version number or `latest` as the ensure value. Valid options: `present` (also called `installed`), `absent`, `latest`,
+`held` or a version number. Default: `installed`.
+
+##### `install_options`
+
+Specifies an array of additional options to pass when installing a package. These options are
+package-specific, and should be documented by the software vendor. One commonly
+implemented option is `INSTALLDIR`:
+
+~~~puppet
+package {'launchy':
+ ensure => installed,
+ provider => 'chocolatey',
+ install_options => ['-installArgs', '"', '/VERYSILENT', '/NORESTART', '"'],
+}
+~~~
+
+**NOTE:** The above method of single quotes in an array is the only method you should use
+in passing `install_options` with the Chocolatey provider. There are other ways
+to do it, but they are passed through to Chocolatey in ways that may not be
+sufficient.
+
+This is the **only** place in Puppet where backslash separators should be used.
+Note that backslashes in double-quoted strings *must* be double-escaped and
+backslashes in single-quoted strings *may* be double-escaped.
+
+##### `name`
+
+Specifies the package name. This is the name that the packaging system uses internally. Valid options: String. Default: The resource's title.
+
+##### `provider`
+
+**Required.** Sets the specific backend to use for the `package` resource. Chocolatey is not the
+default provider for Windows, so it must be specified (or by using a resource
+default, shown in the Usage section above). Valid options: `'chocolatey'`.
+
+##### `source`
+
+Specifies where to find the package file. Use this to override the default
+source(s). Valid options: String of either an absolute path to a local
+directory containing packages stored on the target system, a URL (to OData feeds), or a network
+drive path. Chocolatey maintains default sources in its configuration file that it uses by default.
+
+Puppet will not automatically retrieve source files for you, and
+usually just passes the value of the source to the package installation command.
+You can use a `file` resource if you need to manually copy package files to the
+target system.
+
+##### `uninstall_options`
+
+Specifies an array of additional options to pass when uninstalling a package. These options
+are package-specific, and should be documented by the software vendor.
+
+~~~puppet
+package {'launchy':
+ ensure => absent,
+ provider => 'chocolatey',
+ uninstall_options => ['-uninstallargs', '"', '/VERYSILENT', '/NORESTART', '"'],
+}
+~~~
+
+The above method of single quotes in an array is the only method you should use
+in passing `uninstall_options` with the Chocolatey provider. There are other ways
+to do it, but they are passed to Chocolatey in ways that may not be
+sufficient.
+
+**NOTE:** This is the **only** place in Puppet where backslash separators should be used.
+Backslashes in double-quoted strings *must* be double-escaped and
+backslashes in single-quoted strings *may* be double-escaped.
+
+
+### ChocolateySource
+
+Allows managing default sources for Chocolatey. A source can be a folder, a CIFS share,
+a NuGet Http OData feed, or a full Package Gallery. Learn more about sources at
+[How To Host Feed](https://chocolatey.org/docs/how-to-host-feed). Requires
+Chocolatey v0.9.9.0+.
+
+#### Properties/Parameters
+
+##### `name`
+
+Specifies the name of the source. Used for uniqueness. Also sets the `location` to this value if `location` is unset. Valid options: String. Default: The resource's title.
+
+##### `ensure`
+
+(**Property**: This parameter represents a concrete state on the target system.)
+
+Specifies what state the source should be in. Default: `present`. Valid options: `present`, `disabled`, or `absent`. `disabled` should only be used with existing sources.
+
+##### `location`
+
+(**Property**: This parameter represents a concrete state on the target system.)
+
+Specifies the location of the source repository. Valid options: String of a URL pointing to an OData feed (such as chocolatey/chocolatey_server), a CIFS (UNC) share, or a local folder. Required when `ensure => present` (`present` is default value for `ensure`).
+
+##### `user`
+
+(**Property**: This parameter represents a concrete state on the target system.)
+
+Specifies an optional user name for authenticated feeds. Requires at least Chocolatey v0.9.9.0. Default: `nil`. Specifying an empty value is the same as setting the value to `nil` or not specifying the property at all.
+
+##### `password`
+
+Specifies an optional user password for authenticated feeds. Not ensurable. Value cannot be checked with current value. If you need to update the password, update another setting as well to force the update. Requires at least Chocolatey v0.9.9.0. Default: `nil`. Specifying an empty value is the same as setting the value to `nil` or not specifying the property at all.
+
+##### `priority`
+
+(**Property**: This parameter represents a concrete state on the target system.)
+
+Specifies an optional priority for explicit feed order when searching for packages across multiple feeds. The lower the number, the higher the priority. Sources with a 0 priority are considered no priority and are added after other sources with a priority number. Requires at least Chocolatey v0.9.9.9. Default: `0`.
+
+### ChocolateyFeature
+
+Allows managing features for Chocolatey. Features are configurations that
+act as switches to turn on or off certain aspects of how
+Chocolatey works. Learn more about features in the
+[Chocolatey documentation](https://chocolatey.org/docs/commands-feature). Requires
+Chocolatey v0.9.9.0+.
+
+#### Properties/Parameters
+
+##### `name`
+
+Specifies the name of the feature. Used for uniqueness. Valid options: String. Default: The resource's title.
+
+##### `ensure`
+
+(**Property**: This parameter represents a concrete state on the target system.)
+
+Specifies what state the feature should be in. Valid options: `enabled` or `disabled`. Default: `disabled`.
+
+
+### ChocolateyConfig
+
+Allows managing config settings for Chocolatey. Configuration values
+provide settings for users to configure aspects of Chocolatey and the
+way it functions. Similar to features, except allow for user configured
+values. Learn more about config settings at
+[Config](https://chocolatey.org/docs/commands-config). Requires
+Chocolatey v0.9.9.9+.
+
+#### Properties/Parameters
+
+##### `name`
+
+(**Namevar**: If ommitted, this parameter's value will default to the resource's
+title.)
+
+Specifies the name of the config setting. Used for uniqueness. Puppet is not able to
+easily manage any values that include "password" in the key name because they
+will be encrypted in the configuration file.
+
+##### `ensure`
+
+(**Property**: This parameter represents a concrete state on the target system.)
+
+Specifies what state the config should be in. Valid options: `present` or `absent`. Default: `present`.
+
+##### `value`
+
+(**Property**: This parameter represents a concrete state on the target system.)
+
+Specifies the value of the config setting. If the name includes "password", then the value
+is not ensurable due to being encrypted in the configuration file.
+
+
+### Class: chocolatey
+
+Manages installation and configuration of Chocolatey itself.
+
+#### Parameters
+
+##### `choco_install_location`
+
+Specifies where Chocolatey install should be located. Valid options: Must be an absolute path starting with a drive letter, for example: `c:\`. Default: The currently detected install location based on the `ChocolateyInstall` environment variable. If not specified, falls back to `'C:\ProgramData\chocolatey'`.
+
+**NOTE:** Puppet can install Chocolatey and configure Chocolatey install packages during the same run *UNLESS* you specify this setting. This is due to the way the providers search for suitability of the command, falling back to the default install for the executable when none is found. Because environment variables and commands do not refresh during the same Puppet run (due somewhat to a Windows limitation with updating environment information for currently running processes), installing to a directory that is not the default won't be detected until the next time Puppet runs. So unless you really want this installed elsewhere and don't have a current existing install in that non-default location, do not set this value.
+
+Specifying `C:\Chocolatey` as the install directory will trigger Chocolatey to attempt to upgrade that directory. This is due to that location being the original install location for Chocolatey before it was moved to another directory and subsequently locked down. If you need this to be the installation directory, please define an environment variable `ChocolateyAllowInsecureRootDirectory` and set it to `'true'`. For more information, please see the [CHANGELOG for 0.9.9](https://github.com/chocolatey/choco/blob/master/CHANGELOG.md#099-march-3-2015).
+
+If you override the default installation directory you need to set appropriate permissions on that install location, because Chocolatey does not restrict access to the custom directory to only Administrators. Chocolatey only restricts access to the directory in the default install location, to avoid permissions issues with custom locations, among other reasons. See ["Can I install Chocolatey to another location?"](https://chocolatey.org/install#can-i-install-chocolatey-to-another-location) for more information.
+
+##### `use_7zip`
+
+Specifies whether to use the built-in shell or allow the installer to download 7zip to extract `chocolatey.nupkg` during installation. Valid options: `true`, `false`. Default: `false`.
+
+##### `choco_install_timeout_seconds`
+
+Specifies how long in seconds should be allowed for the install of Chocolatey (including .NET Framework 4 if necessary). Valid options: Number. Default: `1500` (25 minutes).
+
+##### `chocolatey_download_url`
+
+Specifies the URL that returns `chocolatey.nupkg`. Valid options: String of URL, not necessarily from an OData feed. Any URL location will work, but it must result in the chocolatey nupkg file being downloaded. Default: `'https://chocolatey.org/api/v2/package/chocolatey/'`.
+
+##### `enable_autouninstaller`
+
+*Only for 0.9.9.x users. Chocolatey 0.9.10.x+ ignores this setting.* Specifies whether auto uninstaller is enabled. Auto uninstaller allows Chocolatey to automatically manage the uninstall of software from Programs and Features without necessarily requiring a `chocolateyUninstall.ps1` file in the package. Valid options: `true`, `false`. Default: `true`.
+
+##### `log_output`
+
+Specifies whether to log output from the installer. Valid options: `true`, `false`. Default: `false`.
+
+
+## Limitations
+
+1. Works with Windows only.
+2. If you override an existing install location of Chocolatey using `choco_install_location =>` in the Chocolatey class, it does not bring any of the existing packages with it. You will need to handle that through some other means.
+3. Overriding the install location will also not allow Chocolatey to be configured or install packages on the same run that it is installed on. See [`choco_install_location`](#choco_install_location) for details.
+
+### Known Issues
+
+1. This module doesn't support side by side scenarios.
+2. This module may have issues upgrading Chocolatey itself using the package resource.
+3. If .NET 4.0 is not installed, it may have trouble installing Chocolatey. Chocolatey version 0.9.9.9+ helps alleviate this issue.
+4. If there is an error in the installer (`InstallChocolatey.ps1.erb`), it may not show as an error. This may be an issue with the PowerShell provider and is still under investigation.
+
+## Development
+
+Puppet Inc modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t access the huge number of platforms and myriad of hardware, software, and deployment configurations that Puppet is intended to serve.
+
+We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things.
+
+For more information, see our [module contribution guide.](https://docs.puppet.com/forge/contributing.html)
+
+## Attributions
+
+A special thanks goes out to [Rich Siegel](https://github.com/rismoney) and [Rob Reynolds](https://github.com/ferventcoder) who wrote the original
+provider and continue to contribute to the development of this provider.
diff --git a/modules/utilities/windows/repository_managers/chocolatey/Rakefile b/modules/utilities/windows/repository_managers/chocolatey/Rakefile
new file mode 100644
index 000000000..8e029301c
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/Rakefile
@@ -0,0 +1,104 @@
+require 'rake'
+require 'rspec/core/rake_task'
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'puppet'
+
+begin
+ require 'beaker/tasks/test' unless RUBY_PLATFORM =~ /win32/
+rescue LoadError
+ #Do nothing, only installed with system_tests group
+end
+
+
+# If puppet does not support symlinks (e.g., puppet <= 3.5) we cannot use
+# puppetlabs_spec_helper's `rake spec` task because it requires symlink
+# support. Redefine `rake spec` to avoid calling `rake spec_prep` (requires
+# symlinks to place fixtures) and restrict the pattern match only files under
+# the 'unit' directory (tests in other dirs require fixtures).
+if Puppet::Util::Platform.windows? and !Puppet::FileSystem.respond_to?(:symlink)
+ ENV["SPEC"] = "./spec/{unit,integration}/**/*_spec.rb"
+ Rake::Task[:spec].clear if Rake::Task.task_defined?(:spec)
+ task :spec do
+ Rake::Task[:spec_standalone].invoke
+ Rake::Task[:spec_clean].invoke
+ end
+end
+
+# These lint exclusions are in puppetlabs_spec_helper but needs a version above 0.10.3
+# Line length test is 80 chars in puppet-lint 1.1.0
+PuppetLint.configuration.send('disable_80chars')
+# Line length test is 140 chars in puppet-lint 2.x
+PuppetLint.configuration.send('disable_140chars')
+
+task :default => [:spec]
+
+desc 'Generate code coverage'
+RSpec::Core::RakeTask.new(:coverage) do |t|
+ t.rcov = true
+ t.rcov_opts = ['--exclude', 'spec']
+end
+
+
+platform = ENV["PLATFORM"]
+
+# Create the directory, if it exists already you'll get an error, but this should not stop the execution
+begin
+ sh 'mkdir tests/configs'
+rescue => e
+ puts e.message
+end
+
+desc 'Executes reference tests (agent only) intended for use in CI'
+task :reference_tests do
+ command = "bundle exec beaker-hostgenerator --global-config {masterless=true} #{platform} > tests/configs/#{platform}" # should we assume the "configs" directory is present?
+ sh command
+
+ command =<<-EOS
+bundle exec beaker \
+ --debug \
+ --preserve-hosts never \
+ --config tests/configs/$PLATFORM \
+ --keyfile ~/.ssh/id_rsa-acceptance \
+ --load-path tests/lib \
+ --type aio \
+ --pre-suite tests/reference/pre-suite \
+ --tests tests/reference/tests
+ EOS
+ sh command
+end
+
+desc 'Executes acceptance tests (master and agent) intended for use in CI'
+task :acceptance_tests do
+ command = "bundle exec beaker-hostgenerator #{platform} > tests/configs/#{platform}"
+ sh command
+
+ command =<<-EOS
+bundle exec beaker \
+ --debug \
+ --preserve-hosts never \
+ --config tests/configs/$PLATFORM \
+ --keyfile ~/.ssh/id_rsa-acceptance \
+ --load-path tests/lib \
+ --pre-suite tests/acceptance/pre-suite \
+ --tests tests/acceptance/tests
+ EOS
+ sh command
+end
+
+task :acceptance_tests => [:basic_enviroment_variable_check, :acceptance_enviroment_varible_check]
+task :reference_tests => [:basic_enviroment_variable_check]
+
+task :basic_enviroment_variable_check do
+ abort('PLATFORM variable not present, aborting test.') unless ENV["PLATFORM"]
+ abort('MODULE_VERSION variable not present, aborting test.') unless ENV["MODULE_VERSION"]
+end
+
+task :acceptance_enviroment_varible_check do
+ if ENV["BEAKER_PE_DIR"] && ENV["PE_DIST_DIR"]
+ abort('Either BEAKER_PE_DIR or PE_DIST_DIR variable should be set but not both, aborting test.')
+ end
+ if !ENV["BEAKER_PE_DIR"] && !ENV["PE_DIST_DIR"]
+ abort('Neither BEAKER_PE_DIR or PE_DIST_DIR variable is set, aborting test.')
+ end
+end
+
diff --git a/modules/utilities/windows/repository_managers/chocolatey/appveyor.yml b/modules/utilities/windows/repository_managers/chocolatey/appveyor.yml
new file mode 100644
index 000000000..543c81d2b
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/appveyor.yml
@@ -0,0 +1,44 @@
+version: 1.1.x.{build}
+skip_commits:
+ message: /^\(?doc\)?.*/
+clone_depth: 10
+init:
+- SET
+- 'mkdir C:\ProgramData\PuppetLabs\code && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\facter && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\hiera && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\puppet\var && exit 0'
+environment:
+ matrix:
+ - PUPPET_GEM_VERSION: ~> 3.0
+ RUBY_VER: 193
+ - PUPPET_GEM_VERSION: ~> 3.0
+ RUBY_VER: 200-x64
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 21
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 21-x64
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 23
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 23-x64
+ - PUPPET_GEM_VERSION: 3.0.0
+ RUBY_VER: 193
+matrix:
+ fast_finish: true
+install:
+- SET PATH=C:\Ruby%RUBY_VER%\bin;%PATH%
+- bundle install --jobs 4 --retry 2 --without system_tests
+- type Gemfile.lock
+build: off
+test_script:
+- bundle exec puppet -V
+- ruby -v
+- bundle exec rspec spec/unit -fd -b
+notifications:
+- provider: Email
+ to:
+ - nobody@nowhere.com
+ on_build_success: false
+ on_build_failure: false
+ on_build_status_changed: false
diff --git a/modules/utilities/windows/repository_managers/chocolatey/checksums.json b/modules/utilities/windows/repository_managers/chocolatey/checksums.json
new file mode 100644
index 000000000..4cbc88179
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/checksums.json
@@ -0,0 +1,91 @@
+{
+ "CHANGELOG.md": "ed75f911df877999ffe0b3d85c6d4c84",
+ "CONTRIBUTING.md": "2398672bb3e7c2a5ec11a9cea0f809e9",
+ "Gemfile": "e0713c9514c65b3bcce69e0b2bace8a7",
+ "LICENSE": "175792518e4ac015ab6696d16c4f607e",
+ "MAINTAINERS.md": "00d7777a4e0e4eed90437b4a972cdd6c",
+ "NOTICE": "267e1dc2b672516ff482a3fe8b857e2c",
+ "README.md": "a76ff6fb65c846cc273cb66333c2bbb5",
+ "Rakefile": "cde4be099ca764cc6bd7d706d2e37c90",
+ "appveyor.yml": "00752619ea15b8c6d287a7ae23a6c668",
+ "examples/init.pp": "1af719123c9af01ce6efd8de8465ad03",
+ "lib/facter/choco_install_path.rb": "36ece697e936cc950f8946ca2ac11729",
+ "lib/facter/chocolateyversion.rb": "ebbfc8535589aad01717852c93975ac3",
+ "lib/puppet/provider/chocolateyconfig/windows.rb": "048191d42df7e85cfc330569f500f88e",
+ "lib/puppet/provider/chocolateyfeature/windows.rb": "218b285cde1d86b2a530c194501de8d4",
+ "lib/puppet/provider/chocolateysource/windows.rb": "0d95da93a40c687a8a631d3c0c539609",
+ "lib/puppet/provider/package/chocolatey.rb": "80471c41d85faf45a521b5d8467fdd8b",
+ "lib/puppet/type/chocolateyconfig.rb": "1d86ef49b0faf6a321ea3ca24f9f39b1",
+ "lib/puppet/type/chocolateyfeature.rb": "1515df1d174d814ffc9bfff073a9051e",
+ "lib/puppet/type/chocolateysource.rb": "e2054da90dd124161fc64af9c11faf60",
+ "lib/puppet_x/chocolatey/chocolatey_common.rb": "03d3bf0cf1d2ef4a6843c3cd2e616c53",
+ "lib/puppet_x/chocolatey/chocolatey_install.rb": "e1e172d99659eb31882d64f88aacff60",
+ "lib/puppet_x/chocolatey/chocolatey_version.rb": "3155abe3c0a0b20770d322654c02e78d",
+ "manifests/config.pp": "307f680eb54988b9df8bd16e597a5843",
+ "manifests/init.pp": "984c562211c9bb0a7262531e1e5b2ef0",
+ "manifests/install.pp": "091c863df390704a9b01c2eb10c5b133",
+ "manifests/params.pp": "4a73fc3b0c6379551bdad6e5d3f357b4",
+ "metadata.json": "140403f7468691a05d49f54a52da9bc1",
+ "spec/classes/config_spec.rb": "8e6bb682200b29e2d622fa1d20e2a382",
+ "spec/classes/coverage_spec.rb": "5b6dfa0dd426aca0ccfae23c0a629f0b",
+ "spec/classes/init_spec.rb": "9d1316b761393c24fe7346aca679e69a",
+ "spec/classes/install_spec.rb": "76c2b4d46d8ddeac0d87ecb6612d58a4",
+ "spec/spec_helper.rb": "7adb146e2bdbb16f41786e325f33b1c5",
+ "spec/unit/facter/choco_install_path_spec.rb": "f23010110c2084aadc544b5c4d42c558",
+ "spec/unit/facter/chocolateyversion_spec.rb": "9ae92a85ed91c5f08edc7b8c4e8e53ac",
+ "spec/unit/puppet/provider/chocolateyconfig/windows_spec.rb": "ad562ea5608d75115d33111aca94b524",
+ "spec/unit/puppet/provider/chocolateyfeature/windows_spec.rb": "1e5bbb2cafae10e6a57b4c02f223ba80",
+ "spec/unit/puppet/provider/chocolateysource/windows_spec.rb": "1ad0760ce83a9590de51866f9f1de3d3",
+ "spec/unit/puppet/provider/package/chocolatey_spec.rb": "f6fdd633b4765f8ff0e0bc74bcd09769",
+ "spec/unit/puppet/type/chocolateyconfig_spec.rb": "b97b3604c59f9f8d011c4cd67cff4260",
+ "spec/unit/puppet/type/chocolateyfeature_spec.rb": "a2bf2e2fb8a45f8eefa43a0f289b0e40",
+ "spec/unit/puppet/type/chocolateysource_spec.rb": "1a71f966fac89b623decd08a401f08c6",
+ "spec/unit/puppet_x/chocolatey/chocolatey_common_spec.rb": "cb9087f4aa76641719bb5454fb474aa3",
+ "spec/unit/puppet_x/chocolatey/chocolatey_install_spec.rb": "5af79f6838ea628b1f55afcce1020279",
+ "spec/unit/puppet_x/chocolatey/chocolatey_version_spec.rb": "3f8e5abf71112d84a5be8ad096aad6ca",
+ "templates/InstallChocolatey.ps1.erb": "2adb1ce976d25907164b15cf1f16fd02",
+ "tests/acceptance/pre-suite/00_pe_install.rb": "cfe3fa761b0560792eacb872442dc9c7",
+ "tests/acceptance/pre-suite/01_chocolatey_module.install.rb": "9d11cc217d7f53ddffa36de7610c09b7",
+ "tests/acceptance/pre-suite/02_chocolatey_application_install.rb": "15205c3858c4d0a9c098541d9e0a7191",
+ "tests/acceptance/tests/hello.rb": "44c8a17a9a8ace86055fca4cf66d5a90",
+ "tests/lib/chocolatey_helper.rb": "c12b7e4bb5f06046bffc1031a866dd6b",
+ "tests/reference/pre-suite/00_install_certs.rb": "0814e2c5639947378749d0001ada530a",
+ "tests/reference/pre-suite/01_puppet_agent_install.rb": "34107ab52e53672aebe2f11b0759d9d3",
+ "tests/reference/pre-suite/02_chocolatey_module_install.rb": "515a96abf6a9660a113bcee882c7dd9c",
+ "tests/reference/pre-suite/03_chocolatey_application_install.rb": "33e65471ef20e5a34fdad72142c4a6b0",
+ "tests/reference/tests/chocolateyconfig/add_new_config_item.rb": "ca7e6cb60da8f6e41d1bb039a0f08af3",
+ "tests/reference/tests/chocolateyconfig/add_value_to_existing_config.rb": "407e1ea27b86a7e255f60330e7d3410e",
+ "tests/reference/tests/chocolateyconfig/change_config_value.rb": "3e6184e3a44a5ef3e82ac88e4c2da54b",
+ "tests/reference/tests/chocolateyconfig/ensure_config_value_with_password_in_name.rb": "c1171b2d053318df65af8fa7641b6334",
+ "tests/reference/tests/chocolateyconfig/fail_to_appy_bad_manifest.rb": "cc4261382302a4b18cbceedcb6bccdb1",
+ "tests/reference/tests/chocolateyconfig/fail_to_set_present_without_value.rb": "eb9093717f14d2102140d9a995321e60",
+ "tests/reference/tests/chocolateyconfig/remove_config_value_with_password_in_name.rb": "123e9d59bd3e1e449d0e9ec2eaae1f86",
+ "tests/reference/tests/chocolateyconfig/remove_value_from_config.rb": "1d4f7f8dc78e728b1d582e29845bc9d7",
+ "tests/reference/tests/chocolateyfeature/disable_disabled_feature.rb": "b0e6527d227f49b1baf67082891ab43e",
+ "tests/reference/tests/chocolateyfeature/disable_enabled_feature.rb": "201db0be084202704ec41967eda28865",
+ "tests/reference/tests/chocolateyfeature/enable_disabled_feature.rb": "87a8704e1c5b70bca03f1d2a715198b4",
+ "tests/reference/tests/chocolateyfeature/enable_enabled_feature.rb": "eedaf5068e428db99e409a3f912cae97",
+ "tests/reference/tests/chocolateyfeature/fail_to_enable_nonexistent_feature.rb": "8000a68d7ccb62e5f6b472facb7065a1",
+ "tests/reference/tests/chocolateyfeature/fail_to_remove_feature.rb": "aea6bb681d46924fe98d34295cf07807",
+ "tests/reference/tests/chocolateypackage/install_and_remove_good_package.rb": "b1a25035d4dad0ac42caeb1becfd56f1",
+ "tests/reference/tests/chocolateypackage/install_and_remove_good_package_utf-8.rb": "ea75ffbe326df41a35e4731fe08a3b61",
+ "tests/reference/tests/chocolateysource/add_priority_to_existing_source.rb": "c6788dd1224006c819549de936076be2",
+ "tests/reference/tests/chocolateysource/add_source_all_options.rb": "47c6aa9fc3f062e3fdc638b001051636",
+ "tests/reference/tests/chocolateysource/add_source_minimal.rb": "bbc7ed0b2d3faaae707872d6d73fdd09",
+ "tests/reference/tests/chocolateysource/add_source_normal.rb": "29fbe2335387aa97d54e994dffe26cae",
+ "tests/reference/tests/chocolateysource/add_user_pass_to_existing_source.rb": "e2cbd76cc086b16d1fde5a85ac7c1fb7",
+ "tests/reference/tests/chocolateysource/change_existing_priority.rb": "81f5bfe7f7f2d42ce0907abbf9513185",
+ "tests/reference/tests/chocolateysource/change_existing_source_location.rb": "afdadbae4c3373620eef0d23ff001716",
+ "tests/reference/tests/chocolateysource/change_user_pass.rb": "6e6af759af2de6429cd49d9c19564934",
+ "tests/reference/tests/chocolateysource/disable_existing_source.rb": "bf860a4876de665781735f370d23aa6f",
+ "tests/reference/tests/chocolateysource/disable_existing_source_two_runs.rb": "2ecb9c4b684ab92ba2af5ea8bdc3323d",
+ "tests/reference/tests/chocolateysource/fail_to_apply_source_without_location.rb": "7ebcb851a3402b93b8ca99dce2afacb6",
+ "tests/reference/tests/chocolateysource/fail_to_appy_bad_manifest.rb": "c81e640adf6b408db72dd2216819bfe9",
+ "tests/reference/tests/chocolateysource/fail_to_set_password_without_user.rb": "c27cbaadb8f2df3edebf4beca8aca73c",
+ "tests/reference/tests/chocolateysource/fail_to_set_user_without_password.rb": "2abfeb800092c7217d7e36dcbab75d78",
+ "tests/reference/tests/chocolateysource/remove_existing_source.rb": "e5fa96e486e522300e03270caf3bc613",
+ "tests/reference/tests/chocolateysource/remove_priority_from_existing_source.rb": "d49e25470cf51d9bc69d8670c0b600ad",
+ "tests/reference/tests/chocolateysource/remove_user_pass_from_existing_source.rb": "87920f736ad9958ce152944629292ffc",
+ "tests/test_run_scripts/acceptance_tests.sh": "d7bdd1185f73fd28389a6d60da0f0d69",
+ "tests/test_run_scripts/reference_tests.sh": "22220232bde40afa6c90fcdfc46943e0"
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/repository_managers/chocolatey/chocolatey.pp b/modules/utilities/windows/repository_managers/chocolatey/chocolatey.pp
new file mode 100644
index 000000000..0e42ca6db
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/chocolatey.pp
@@ -0,0 +1 @@
+include chocolatey
\ No newline at end of file
diff --git a/modules/utilities/windows/repository_managers/chocolatey/examples/init.pp b/modules/utilities/windows/repository_managers/chocolatey/examples/init.pp
new file mode 100644
index 000000000..8154edd19
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/examples/init.pp
@@ -0,0 +1,23 @@
+# The baseline for module testing used by Puppet Labs is that each manifest
+# should have a corresponding test manifest that declares that class or defined
+# type.
+#
+# Tests are then run by using puppet apply --noop (to check for compilation errors
+# and view a log of events) or by fully applying the test in a virtual environment
+# (to compare the resulting system state to the desired state).
+#
+# Learn more about module testing here: http://docs.puppetlabs.com/guides/tests_smoke.html
+#
+# With symlinks on Windows, please run the following command an administrative command prompt (substituting the proper directories):
+
+package { $pkg:
+ ensure => 'latest',
+ provider => 'chocolatey',
+}
+
+# mklink /D C:\ProgramData\PuppetLabs\puppet\etc\modules\chocolatey C:\code\puppetlabs\puppetlabs-chocolatey
+# mklink /D C:\ProgramData\PuppetLabs\code\environments\production\modules\chocolatey C:\code\puppetlabs\puppetlabs-chocolatey
+
+chocolateysource { 'local':
+ location => 'c:\packages',
+}
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/facter/choco_install_path.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/facter/choco_install_path.rb
new file mode 100644
index 000000000..3301fd0ba
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/facter/choco_install_path.rb
@@ -0,0 +1,9 @@
+require 'pathname'
+require Pathname.new(__FILE__).dirname + '../' + 'puppet_x/chocolatey/chocolatey_install'
+
+Facter.add('choco_install_path') do
+ confine :osfamily => :windows
+ setcode do
+ PuppetX::Chocolatey::ChocolateyInstall.install_path || 'C:\ProgramData\chocolatey'
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/facter/chocolateyversion.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/facter/chocolateyversion.rb
new file mode 100644
index 000000000..3d049c53b
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/facter/chocolateyversion.rb
@@ -0,0 +1,9 @@
+require 'pathname'
+require Pathname.new(__FILE__).dirname + '../' + 'puppet_x/chocolatey/chocolatey_version'
+
+Facter.add('chocolateyversion') do
+ confine :osfamily => :windows
+ setcode do
+ PuppetX::Chocolatey::ChocolateyVersion.version || '0'
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateyconfig/windows.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateyconfig/windows.rb
new file mode 100644
index 000000000..69d3ffaa3
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateyconfig/windows.rb
@@ -0,0 +1,144 @@
+require 'puppet/type'
+require 'pathname'
+require 'rexml/document'
+
+Puppet::Type.type(:chocolateyconfig).provide(:windows) do
+ confine :operatingsystem => :windows
+ defaultfor :operatingsystem => :windows
+
+ require Pathname.new(__FILE__).dirname + '../../../' + 'puppet_x/chocolatey/chocolatey_common'
+ include PuppetX::Chocolatey::ChocolateyCommon
+
+ CONFIG_MINIMUM_SUPPORTED_CHOCO_VERSION = '0.9.10.0'
+
+ commands :chocolatey => PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command
+
+ def initialize(value={})
+ super(value)
+ @property_flush = {}
+ end
+
+ def properties
+ if @property_hash.empty?
+ @property_hash = query || { :ensure => ( :absent )}
+ @property_hash[:ensure] = :absent if @property_hash.empty?
+ end
+ @property_hash.dup
+ end
+
+ def query
+ self.class.configs.each do |config|
+ return config.properties if @resource[:name][/\A\S*/].downcase == config.name.downcase
+ end
+
+ return {}
+ end
+
+ def self.get_configs
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+
+ choco_config = PuppetX::Chocolatey::ChocolateyCommon.choco_config_file
+ raise Puppet::ResourceError, "Config file not found for Chocolatey. Please make sure you have Chocolatey installed." if choco_config.nil?
+ raise Puppet::ResourceError, "An install was detected, but was unable to locate config file at #{choco_config}." unless PuppetX::Chocolatey::ChocolateyCommon.file_exists?(choco_config)
+
+ Puppet.debug("Gathering sources from '#{choco_config}'.")
+ config = REXML::Document.new File.new(choco_config, 'r')
+
+ config.elements.to_a( '//add' )
+ end
+
+ def self.get_config(element)
+ config = {}
+ return config if element.nil?
+
+ config[:name] = element.attributes['key'] if element.attributes['key']
+ config[:value] = element.attributes['value'] if element.attributes['value']
+ config[:description] = element.attributes['description'] if element.attributes['description']
+
+ config[:ensure] = :present
+
+ Puppet.debug("Loaded config '#{config.inspect}'.")
+
+ config
+ end
+
+ def self.configs
+ @configs ||= get_configs.collect do |item|
+ config = get_config(item)
+ new(config)
+ end
+ end
+
+ def self.refresh_configs
+ @configs = nil
+ self.configs
+ end
+
+ def self.instances
+ configs
+ end
+
+ def self.prefetch(resources)
+ instances.each do |provider|
+ if (resource = resources[provider.name])
+ resource.provider = provider
+ end
+ end
+ end
+
+ def create
+ @property_flush[:ensure] = :present
+ end
+
+ def exists?
+ @property_hash[:ensure] == :present
+ end
+
+ def destroy
+ @property_flush[:ensure] = :absent
+ end
+
+ def validate
+ choco_version = Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version)
+ if PuppetX::Chocolatey::ChocolateyCommon.file_exists?(PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command) && choco_version < Gem::Version.new(CONFIG_MINIMUM_SUPPORTED_CHOCO_VERSION)
+ raise Puppet::ResourceError, "Chocolatey version must be '#{CONFIG_MINIMUM_SUPPORTED_CHOCO_VERSION}' to manage configuration values. Detected '#{choco_version}' as your version. Please upgrade Chocolatey."
+ end
+ end
+
+ mk_resource_methods
+
+ def flush
+ choco_version = Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version)
+
+ args = []
+ args << 'config'
+
+ # look at the hash, then flush if present.
+ # If all else fails, looks at resource[:ensure]
+ property_ensure = @property_hash[:ensure]
+ property_ensure = @property_flush[:ensure] if @property_flush[:ensure]
+ property_ensure = resource[:ensure] if property_ensure.nil?
+
+ command = 'set'
+ command = 'unset' if property_ensure == :absent
+
+ args << command
+ args << '--name' << resource[:name]
+
+ if property_ensure != :absent
+ args << '--value' << resource[:value]
+ end
+
+ begin
+ Puppet::Util::Execution.execute([command(:chocolatey), *args])
+ rescue Puppet::ExecutionFailure => e
+ raise Puppet::Error, "An error occurred running choco. Unable to set Chocolateyconfig[#{self.name}]: #{e}"
+ end
+
+ @property_hash.clear
+ @property_flush.clear
+
+ self.class.refresh_configs
+ @property_hash = query
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateyfeature/windows.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateyfeature/windows.rb
new file mode 100644
index 000000000..de0ccfd22
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateyfeature/windows.rb
@@ -0,0 +1,126 @@
+require 'puppet/type'
+require 'pathname'
+require 'rexml/document'
+
+Puppet::Type.type(:chocolateyfeature).provide(:windows) do
+ confine :operatingsystem => :windows
+ defaultfor :operatingsystem => :windows
+
+ require Pathname.new(__FILE__).dirname + '../../../' + 'puppet_x/chocolatey/chocolatey_common'
+ include PuppetX::Chocolatey::ChocolateyCommon
+
+ FEATURE_MINIMUM_SUPPORTED_CHOCO_VERSION = '0.9.9.0'
+
+ commands :chocolatey => PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command
+
+ def initialize(value={})
+ super(value)
+ @property_flush = {}
+ end
+
+ def properties
+ if @property_hash.empty?
+ @property_hash = query
+ end
+ @property_hash.dup
+ end
+
+ def query
+ self.class.features.each do |feature|
+ return feature.properties if @resource[:name][/\A\S*/].downcase == feature.name.downcase
+ end
+
+ return {}
+ end
+
+ def self.get_features
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+
+ choco_config = PuppetX::Chocolatey::ChocolateyCommon.choco_config_file
+ raise Puppet::ResourceError, "Config file not found for Chocolatey. Please make sure you have Chocolatey installed." if choco_config.nil?
+ raise Puppet::ResourceError, "An install was detected, but was unable to locate config file at #{choco_config}." unless PuppetX::Chocolatey::ChocolateyCommon.file_exists?(choco_config)
+
+ Puppet.debug("Gathering features from '#{choco_config}'.")
+ config = REXML::Document.new File.new(choco_config, 'r')
+
+ config.elements.to_a( '//feature' )
+ end
+
+ def self.get_feature(element)
+ feature = {}
+ return feature if element.nil?
+
+ feature[:name] = element.attributes['name'].downcase if element.attributes['name']
+ feature[:description] = element.attributes['description'].downcase if element.attributes['description']
+
+ enabled = false
+ enabled = element.attributes['enabled'].downcase == 'true' if element.attributes['enabled']
+
+ feature[:ensure] = :disabled
+ feature[:ensure] = :enabled if enabled
+
+ Puppet.debug("Loaded feature '#{feature.inspect}'.")
+
+ feature
+ end
+
+ def self.features
+ get_features.collect do |item|
+ feature = get_feature(item)
+ new(feature)
+ end
+ end
+
+ def self.instances
+ features
+ end
+
+ def self.prefetch(resources)
+ instances.each do |provider|
+ if (resource = resources[provider.name])
+ resource.provider = provider
+ end
+ end
+ end
+
+ def enable
+ @property_flush[:ensure] = :enabled
+ end
+
+ def exists?
+ @property_hash[:ensure] == :enabled
+ end
+
+ def disable
+ @property_flush[:ensure] = :disabled
+ end
+
+ def validate
+ choco_version = Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version)
+ if PuppetX::Chocolatey::ChocolateyCommon.file_exists?(PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command) && choco_version < Gem::Version.new(FEATURE_MINIMUM_SUPPORTED_CHOCO_VERSION)
+ raise Puppet::ResourceError, "Chocolatey version must be '#{FEATURE_MINIMUM_SUPPORTED_CHOCO_VERSION}' to manage configuration values with Puppet. Detected '#{choco_version}' as your version. Please upgrade Chocolatey to use this resource."
+ end
+ end
+
+ mk_resource_methods
+
+ def flush
+ args = []
+ args << 'feature'
+
+ command = 'enable'
+ command = 'disable' if @property_flush[:ensure] == :disabled
+
+ args << command
+ args << '--name' << resource[:name]
+
+ Puppet::Util::Execution.execute([command(:chocolatey), *args])
+
+ @property_hash.clear
+ @property_flush.clear
+
+ self.class.features
+ @property_hash = query
+ end
+
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateysource/windows.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateysource/windows.rb
new file mode 100644
index 000000000..784dfc511
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/chocolateysource/windows.rb
@@ -0,0 +1,197 @@
+require 'puppet/type'
+require 'pathname'
+require 'rexml/document'
+
+Puppet::Type.type(:chocolateysource).provide(:windows) do
+ confine :operatingsystem => :windows
+ defaultfor :operatingsystem => :windows
+
+ require Pathname.new(__FILE__).dirname + '../../../' + 'puppet_x/chocolatey/chocolatey_common'
+ include PuppetX::Chocolatey::ChocolateyCommon
+
+ MINIMUM_SUPPORTED_CHOCO_VERSION = '0.9.9.0'
+ MINIMUM_SUPPORTED_CHOCO_VERSION_PRIORITY = '0.9.9.9'
+
+ commands :chocolatey => PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command
+
+ def initialize(value={})
+ super(value)
+ @property_flush = {}
+ end
+
+ def properties
+ if @property_hash.empty?
+ @property_hash = query || { :ensure => ( :absent )}
+ @property_hash[:ensure] = :absent if @property_hash.empty?
+ end
+ @property_hash.dup
+ end
+
+ def query
+ self.class.sources.each do |source|
+ return source.properties if @resource[:name][/\A\S*/].downcase == source.name.downcase
+ end
+
+ return {}
+ end
+
+ def self.get_sources
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+
+ choco_config = PuppetX::Chocolatey::ChocolateyCommon.choco_config_file
+ raise Puppet::ResourceError, "Config file not found for Chocolatey. Please make sure you have Chocolatey installed." if choco_config.nil?
+ raise Puppet::ResourceError, "An install was detected, but was unable to locate config file at #{choco_config}." unless PuppetX::Chocolatey::ChocolateyCommon.file_exists?(choco_config)
+
+ Puppet.debug("Gathering sources from '#{choco_config}'.")
+ config = REXML::Document.new File.new(choco_config, 'r')
+
+ config.elements.to_a( '//source' )
+ end
+
+ def self.get_source(element)
+ source = {}
+ return source if element.nil?
+
+ source[:name] = element.attributes['id'].downcase if element.attributes['id']
+ source[:location] = element.attributes['value'].downcase if element.attributes['value']
+
+ disabled = false
+ disabled = element.attributes['disabled'].downcase == 'true' if element.attributes['disabled']
+ source[:ensure] = :present
+ source[:ensure] = :disabled if disabled
+
+ source[:priority] = 0
+ source[:priority] = element.attributes['priority'].downcase if element.attributes['priority']
+
+ source[:user] = ''
+ source[:user] = element.attributes['user'].downcase if element.attributes['user']
+
+ Puppet.debug("Loaded source '#{source.inspect}'.")
+
+ source
+ end
+
+ def self.sources
+ @sources ||= get_sources.collect do |item|
+ source = get_source(item)
+ new(source)
+ end
+ end
+
+ def self.refresh_sources
+ @sources = nil
+ self.sources
+ end
+
+ def self.instances
+ sources
+ end
+
+ def self.prefetch(resources)
+ instances.each do |provider|
+ if (resource = resources[provider.name])
+ resource.provider = provider
+ end
+ end
+ end
+
+ def create
+ @property_flush[:ensure] = :present
+ end
+
+ def exists?
+ @property_hash[:ensure] == :present
+ end
+
+ def disable
+ @property_flush[:ensure] = :disabled
+ end
+
+ def destroy
+ @property_flush[:ensure] = :absent
+ end
+
+ def validate
+ choco_version = Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version)
+ if PuppetX::Chocolatey::ChocolateyCommon.file_exists?(PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command) && choco_version < Gem::Version.new(MINIMUM_SUPPORTED_CHOCO_VERSION)
+ raise Puppet::ResourceError, "Chocolatey version must be '#{MINIMUM_SUPPORTED_CHOCO_VERSION}' to manage configuration values with Puppet. Detected '#{choco_version}' as your version. Please upgrade Chocolatey to use this resource."
+ end
+
+ if choco_version < Gem::Version.new(MINIMUM_SUPPORTED_CHOCO_VERSION_PRIORITY) && resource[:priority] && resource[:priority] != 0
+ Puppet.warning("Chocolatey is unable to manage priority for sources when version is less than #{MINIMUM_SUPPORTED_CHOCO_VERSION_PRIORITY}. The value you set will be ignored.")
+ end
+
+ # location is always filled in with puppet resource, but
+ # resource[:location] is always empty (because it has a different
+ # code path where validation occurs before all properties/params
+ # have been set), resulting in errors
+ # location is always :absent when a manifest runs this with a missing
+ # `location => value`
+ location_check = location
+ # location could be :absent, which mk_resource_method will set it to
+ # resource[:location] is nil when running puppet resource
+ # if you remove `location => value`
+ location_check = resource[:location] if location_check == :absent
+ if (resource[:ensure] == :present && (location_check.nil? || location_check.strip == ''))
+ raise ArgumentError, "A non-empty location must be specified when ensure => present."
+ end
+
+ if resource[:password] && resource[:password] != ''
+ Puppet.debug("The password is not ensurable, so Puppet is unable to change the value using chocolateysource resource. As a workaround, a password change can be in the form of an exec. Reference Chocolateysource[#{resource[:name]}]")
+ end
+ end
+
+ mk_resource_methods
+
+ def flush
+ args = []
+ args << 'source'
+
+ # look at the hash, then flush if present.
+ # If all else fails, looks at resource[:ensure]
+ property_ensure = @property_hash[:ensure]
+ property_ensure = @property_flush[:ensure] if @property_flush[:ensure]
+ property_ensure = resource[:ensure] if property_ensure.nil?
+
+ command = 'add'
+ command = 'remove' if property_ensure == :absent
+ command = 'disable' if property_ensure == :disabled
+
+ args << command
+ args << '--name' << resource[:name]
+
+ if command == 'add'
+ args << '--source' << resource[:location]
+
+ if resource[:user] && resource[:user] != ''
+ args << '--user' << resource[:user]
+ args << '--password' << resource[:password]
+ end
+
+ choco_gem_version = Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version)
+ if choco_gem_version >= Gem::Version.new(MINIMUM_SUPPORTED_CHOCO_VERSION_PRIORITY)
+ args << '--priority' << resource[:priority]
+ end
+ end
+
+ begin
+ Puppet::Util::Execution.execute([command(:chocolatey), *args])
+ rescue Puppet::ExecutionFailure
+ raise Puppet::Error, "An error occurred running choco. Unable to set Chocolatey source configuration for #{self.inspect}"
+ end
+
+ if property_ensure == :present
+ begin
+ Puppet::Util::Execution.execute([command(:chocolatey), 'source', 'enable', '--name', resource[:name]])
+ rescue Puppet::ExecutionFailure
+ raise Puppet::Error, "An error occurred running choco. Unable to set Chocolatey source configuration for #{self.inspect}"
+ end
+ end
+
+ @property_hash.clear
+ @property_flush.clear
+
+ self.class.refresh_sources
+ @property_hash = query
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/package/chocolatey.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/package/chocolatey.rb
new file mode 100644
index 000000000..d01ad0d5a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/provider/package/chocolatey.rb
@@ -0,0 +1,280 @@
+require 'puppet/provider/package'
+require 'pathname'
+require Pathname.new(__FILE__).dirname + '../../../' + 'puppet_x/chocolatey/chocolatey_install'
+
+Puppet::Type.type(:package).provide(:chocolatey, :parent => Puppet::Provider::Package) do
+
+ desc "Manages packages using Chocolatey (Windows package manager).
+
+ The syntax for Chocolatey using the puppet provider is a much
+ closer match to *nix package managers, bringing a more agnostic
+ approach to package management across platforms. Chocolatey packages
+ usually contain all of the logic to install software silently on a
+ Windows machine, much like RPM (yum) or DPKG (apt).
+
+ Installs can be as simple as
+
+ package {'git':
+ ensure => latest,
+ }
+
+ See the ReadMe for more information."
+
+ confine :operatingsystem => :windows
+ has_feature :installable
+ has_feature :uninstallable
+ has_feature :upgradeable
+ has_feature :versionable
+ has_feature :install_options
+ has_feature :uninstall_options
+ has_feature :holdable
+ #has_feature :package_settings
+
+ require Pathname.new(__FILE__).dirname + '../../../' + 'puppet_x/chocolatey/chocolatey_common'
+ include PuppetX::Chocolatey::ChocolateyCommon
+
+ commands :chocolatey => PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command
+
+ def initialize(value={})
+ super(value)
+ end
+
+ def print()
+ notice("The value is: '${name}'")
+ end
+
+ def self.is_compiled_choco?
+ Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version) >= Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon::FIRST_COMPILED_CHOCO_VERSION)
+ end
+
+ def is_compiled_choco?
+ self.class.is_compiled_choco?
+ end
+
+ def install
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+ choco_exe = is_compiled_choco?
+
+ # always unhold on install
+ unhold if choco_exe
+
+ args = []
+
+ # also will need to address -sidebyside or -m in the install args to allow
+ # multiple versions to be installed.
+ args << 'install'
+
+ should = @resource.should(:ensure)
+ case should
+ when true, false, Symbol
+ args << @resource[:name][/\A\S*/]
+ else
+ args.clear
+ if choco_exe
+ args << 'upgrade'
+ else
+ args << 'update'
+ end
+
+ # Add the package version
+ args << @resource[:name][/\A\S*/] << '-version' << @resource[:ensure]
+ end
+
+ if choco_exe
+ args << '-y'
+ end
+
+ if @resource[:source]
+ args << '-source' << @resource[:source]
+ end
+
+ args << @resource[:install_options]
+
+ if Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version) >= Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon::MINIMUM_SUPPORTED_CHOCO_VERSION_EXIT_CODES)
+ args << '--ignore-package-exit-codes'
+ end
+
+ chocolatey(*args)
+ end
+
+ def uninstall
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+ choco_exe = is_compiled_choco?
+
+ # always unhold on uninstall
+ unhold if choco_exe
+
+ args = 'uninstall', @resource[:name][/\A\S*/]
+
+ if choco_exe
+ args << '-fy'
+ end
+
+ choco_version = Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version)
+ if !choco_exe || choco_version >= Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon::MINIMUM_SUPPORTED_CHOCO_UNINSTALL_SOURCE)
+ if @resource[:source]
+ args << '-source' << @resource[:source]
+ end
+ end
+
+ args << @resource[:uninstall_options]
+
+ if Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version) >= Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon::MINIMUM_SUPPORTED_CHOCO_VERSION_EXIT_CODES)
+ args << '--ignore-package-exit-codes'
+ end
+
+ chocolatey(*args)
+ end
+
+ def update
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+ choco_exe = is_compiled_choco?
+
+ # always unhold on upgrade
+ unhold if choco_exe
+
+ if choco_exe
+ args = 'upgrade', @resource[:name][/\A\S*/], '-y'
+ else
+ args = 'update', @resource[:name][/\A\S*/]
+ end
+
+ if @resource[:source]
+ args << '-source' << @resource[:source]
+ end
+
+ args << @resource[:install_options]
+
+ if Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon.choco_version) >= Gem::Version.new(PuppetX::Chocolatey::ChocolateyCommon::MINIMUM_SUPPORTED_CHOCO_VERSION_EXIT_CODES)
+ args << '--ignore-package-exit-codes'
+ end
+
+ if self.query
+ chocolatey(*args)
+ else
+ self.install
+ end
+ end
+
+ # from puppet-dev mailing list
+ # Puppet will call the query method on the instance of the package
+ # provider resource when checking if the package is installed already or
+ # not.
+ # It's a determination for one specific package, the package modeled by
+ # the resource the method is called on.
+ # Query provides the information for the single package identified by @Resource[:name].
+ def query
+ self.class.instances.each do |package|
+ return package.properties if @resource[:name][/\A\S*/].downcase == package.name.downcase
+ end
+
+ return nil
+ end
+
+ def self.listcmd
+ args = []
+ args << 'list'
+ args << '-lo'
+ if is_compiled_choco?
+ args << '-r'
+ end
+
+ [command(:chocolatey), *args]
+ end
+
+ def self.instances
+ packages = []
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+ choco_exe = is_compiled_choco?
+ begin
+ pins = []
+ pin_output = nil unless choco_exe
+ #don't add -r yet, as there is an issue in 0.9.9.9/0.9.9.10 that returns full list plus pins
+ pin_output = Puppet::Util::Execution.execute([command(:chocolatey), 'pin', 'list']) if choco_exe
+ unless pin_output.nil?
+ pin_output.split("\n").each { |pin| pins << pin.split('|')[0] }
+ end
+
+ execpipe(listcmd) do |process|
+ process.each_line do |line|
+ line.chomp!
+ if line.empty? or line.match(/Reading environment variables.*/); next; end
+ raise Puppet::Error, "At least one source must be enabled." if line.match(/Unable to search for packages.*/)
+ if choco_exe
+ values = line.split('|')
+ else
+ values = line.split(' ')
+ end
+ values[1] = :held if pins.include? values[0]
+ packages << new({ :name => values[0].downcase, :ensure => values[1], :provider => self.name })
+ end
+ end
+ rescue Puppet::ExecutionFailure
+ return nil
+ end
+
+ packages
+ end
+
+ def latestcmd
+ choco_exe = is_compiled_choco?
+ if choco_exe
+ args = 'upgrade', '--noop', @resource[:name][/\A\S*/], '-r'
+ else
+ args = 'version', @resource[:name][/\A\S*/]
+ end
+
+ if @resource[:source]
+ args << '-source' << @resource[:source]
+ end
+
+ unless choco_exe
+ args << '| findstr /R "latest" | findstr /V "latestCompare"'
+ end
+
+ [command(:chocolatey), *args]
+ end
+
+ def latest
+ package_ver = ''
+ PuppetX::Chocolatey::ChocolateyCommon.set_env_chocolateyinstall
+ begin
+ execpipe(latestcmd) do |process|
+ process.each_line do |line|
+ line.chomp!
+ if line.empty?; next; end
+ if is_compiled_choco?
+ values = line.split('|')
+ package_ver = values[2]
+ else
+ # Example: ( latest : 2013.08.19.155043 )
+ values = line.split(':').collect(&:strip).delete_if(&:empty?)
+ package_ver = values[1]
+ end
+ end
+ end
+ rescue Puppet::ExecutionFailure
+ return nil
+ end
+
+ package_ver
+ end
+
+ def hold
+ raise ArgumentError, 'Only choco v0.9.9+ can use ensure => held' unless is_compiled_choco?
+
+ install
+
+ args = 'pin', 'add', '-n', @resource[:name][/\A\S*/]
+
+ chocolatey(*args)
+ end
+
+ def unhold
+ return unless is_compiled_choco?
+
+ Puppet::Util::Execution.execute([command(:chocolatey), 'pin','remove', '-n', @resource[:name][/\A\S*/]], :failonfail => false)
+ end
+
+
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateyconfig.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateyconfig.rb
new file mode 100644
index 000000000..ba4d4a65a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateyconfig.rb
@@ -0,0 +1,85 @@
+require 'puppet/type'
+require 'pathname'
+
+Puppet::Type.newtype(:chocolateyconfig) do
+
+ @doc = <<-'EOT'
+ Allows managing config settings for Chocolatey.
+ Configuration values provide settings for users
+ to configure aspects of Chocolatey and the way it
+ functions. Similar to features, except allow for user
+ configured values. Requires 0.9.10+. Learn more about
+ config at https://chocolatey.org/docs/commands-config
+ EOT
+
+ ensurable do
+ newvalue(:present) { provider.create }
+ newvalue(:absent) { provider.destroy }
+ defaultto :present
+
+ def retrieve
+ provider.properties[:ensure]
+ end
+
+ end
+
+ newparam(:name) do
+ desc "The name of the config setting. Used for uniqueness.
+ Puppet is not able to easily manage any values that
+ include Password in the key name in them as they
+ will be encrypted in the configuration file."
+
+ validate do |value|
+ if value.nil? or value.empty?
+ raise ArgumentError, "A non-empty name must be specified."
+ end
+ end
+
+ isnamevar
+
+ munge do |value|
+ value.downcase
+ end
+
+ def insync?(is)
+ is.downcase == should.downcase
+ end
+ end
+
+ newproperty(:value) do
+ desc "The value of the config setting. If the
+ name includes 'password', then the value is
+ not ensurable due to being encrypted in the
+ configuration file."
+
+ validate do |value|
+ if value.nil? or value.empty?
+ raise ArgumentError, "A non-empty value must be specified. To unset value, use ensure => absent"
+ end
+ end
+
+ def insync?(is)
+ if (resource[:name] =~ /password/i)
+ # If name contains password, it is
+ # always in sync if there is a value
+ return (is.nil? || is.empty?) == (should.nil? || should.empty?)
+ else
+ return is.downcase == should.downcase
+ end
+ end
+ end
+
+ validate do
+ if self[:ensure] != :absent
+ raise ArgumentError, "Unless ensure => absent, value is required." if self[:value].nil? || self[:value].empty?
+ end
+
+ if provider.respond_to?(:validate)
+ provider.validate
+ end
+ end
+
+ autorequire(:exec) do
+ ['install_chocolatey_official']
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateyfeature.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateyfeature.rb
new file mode 100644
index 000000000..da5ebcc7f
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateyfeature.rb
@@ -0,0 +1,57 @@
+require 'puppet/type'
+require 'pathname'
+
+Puppet::Type.newtype(:chocolateyfeature) do
+
+ @doc = <<-'EOT'
+ Allows managing features for Chocolatey. Features are
+ configuration that act as feature flippers to turn on or
+ off certain aspects of how Chocolatey works.
+ Learn more about features at
+ https://chocolatey.org/docs/commands-feature
+
+ EOT
+
+ newparam(:name) do
+ desc "The name of the feature. Used for uniqueness."
+
+ validate do |value|
+ if value.nil? or value.empty?
+ raise ArgumentError, "A non-empty name must be specified."
+ end
+ end
+
+ isnamevar
+
+ munge do |value|
+ value.downcase
+ end
+
+ def insync?(is)
+ is.downcase == should.downcase
+ end
+ end
+
+ ensurable do
+ newvalue(:enabled) { provider.enable }
+ newvalue(:disabled) { provider.disable }
+
+ def retrieve
+ provider.properties[:ensure]
+ end
+ end
+
+ validate do
+ if self[:ensure].nil? && provider.properties[:ensure].nil?
+ raise ArgumentError, "Invalid value for ensure. Valid values are enabled or disabled."
+ end
+
+ if provider.respond_to?(:validate)
+ provider.validate
+ end
+ end
+
+ autorequire(:exec) do
+ ['install_chocolatey_official']
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateysource.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateysource.rb
new file mode 100644
index 000000000..fcd4531b9
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet/type/chocolateysource.rb
@@ -0,0 +1,141 @@
+require 'puppet/type'
+require 'pathname'
+
+Puppet::Type.newtype(:chocolateysource) do
+
+ @doc = <<-'EOT'
+ Allows managing sources for Chocolatey. A source can be a
+ folder, a CIFS share, a NuGet Http OData feed, or a full
+ Package Gallery. Learn more about sources at
+ https://chocolatey.org/docs/how-to-host-feed
+
+ EOT
+
+ ensurable do
+ newvalue(:present) { provider.create }
+ newvalue(:disabled) { provider.disable }
+ newvalue(:absent) { provider.destroy }
+ defaultto :present
+
+ def retrieve
+ provider.properties[:ensure]
+ end
+
+ end
+
+ newparam(:name) do
+ desc "The name of the source. Used for uniqueness."
+
+ validate do |value|
+ if value.nil? or value.empty?
+ raise ArgumentError, "A non-empty name must be specified."
+ end
+ end
+
+ isnamevar
+
+ munge do |value|
+ value.downcase
+ end
+
+ def insync?(is)
+ is.downcase == should.downcase
+ end
+ end
+
+ newproperty(:location) do
+ desc "The location of the source repository. Can be a url pointing to
+ an OData feed (like chocolatey/chocolatey_server), a CIFS (UNC) share,
+ or a local folder. Required when `ensure => present` (the default for
+ `ensure`)."
+
+ validate do |value|
+ if value.nil? or value.empty?
+ raise ArgumentError, "A non-empty location must be specified."
+ end
+ end
+
+ def insync?(is)
+ is.downcase == should.downcase
+ end
+ end
+
+ newproperty(:user) do
+ desc "Optional user name for authenticated feeds.
+ Requires at least Chocolatey v0.9.9.0.
+ Defaults to `nil`. Specifying an empty value is the
+ same as setting the value to nil or not specifying
+ the property at all."
+
+ def insync?(is)
+ is.downcase == should.downcase
+ end
+
+ defaultto ''
+ end
+
+ newparam(:password) do
+ desc "Optional user password for authenticated feeds.
+ Not ensurable. Value is not able to be checked
+ with current value. If you need to update the password,
+ update another setting as well.
+ Requires at least Chocolatey v0.9.9.0.
+ Defaults to `nil`. Specifying an empty value is the
+ same as setting the value to nil or not specifying
+ the property at all."
+
+ defaultto ''
+ end
+
+ newproperty(:priority) do
+ desc "Optional priority for explicit feed order when
+ searching for packages across multiple feeds.
+ The lower the number the higher the priority.
+ Sources with a 0 priority are considered no priority
+ and are added after other sources with a priority
+ number.
+ Requires at least Chocolatey v0.9.9.9.
+ Defaults to 0."
+
+ validate do |value|
+ if value.nil?
+ raise ArgumentError, "A non-empty priority must be specified."
+ end
+ raise ArgumentError, "An integer is necessary for priority. Specify 0 or remove for no priority." unless resource.is_numeric?(value)
+ end
+
+ defaultto(0)
+ end
+
+ validate do
+ if (!self[:user].nil? && self[:user].strip != '' && (self[:password].nil? || self[:password] == '')) || ((self[:user].nil? || self[:user].strip == '') && !self[:password].nil? && self[:password] != '')
+ raise ArgumentError, "If specifying user/password, you must specify both values."
+ end
+
+ if provider.respond_to?(:validate)
+ provider.validate
+ end
+ end
+
+ autorequire(:exec) do
+ ['install_chocolatey_official']
+ end
+
+ def munge_boolean(value)
+ case value
+ when true, "true", :true
+ :true
+ when false, "false", :false
+ :false
+ else
+ fail("munge_boolean only takes booleans")
+ end
+ end
+
+ def is_numeric?(value)
+ # this is what stdlib does. Not sure if we want to emulate or not.
+ #numeric = %r{^-?(?:(?:[1-9]\d*)|0)$}
+ #if value.is_a? Integer or (value.is_a? String and value.match numeric)
+ Float(value) != nil rescue false
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_common.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_common.rb
new file mode 100644
index 000000000..ebebceee2
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_common.rb
@@ -0,0 +1,90 @@
+require 'pathname'
+require Pathname.new(__FILE__).dirname + 'chocolatey_version'
+require Pathname.new(__FILE__).dirname + 'chocolatey_install'
+
+module PuppetX
+ module Chocolatey
+ module ChocolateyCommon
+
+ ## determines if C# version of choco
+ FIRST_COMPILED_CHOCO_VERSION = '0.9.9.0'
+ MINIMUM_SUPPORTED_CHOCO_VERSION_EXIT_CODES = '0.9.10.0'
+ MINIMUM_SUPPORTED_CHOCO_UNINSTALL_SOURCE = '0.9.10.0'
+
+ def file_exists?(path)
+ File.exist?(path)
+ end
+ module_function :file_exists?
+
+ def chocolatey_command
+ if Puppet::Util::Platform.windows?
+ # When attempting to find the choco command executable, the following
+ # paths are checked:
+ # - Start with the install_path. If choco is found with environment
+ # variables through the registry or a check on the
+ # ChocolateyInstall env var (first install of Choco may only have
+ # this), then use that path.
+ # - Next look to the most commonly used install location (ProgramData)
+ # - Fall back to the older install location for older installations
+ # - If all else fails, attempt to find Chocolatey in the default place
+ # it installs
+ chocoInstallPath = PuppetX::Chocolatey::ChocolateyInstall.install_path
+
+ chocopath = (chocoInstallPath if (chocoInstallPath && file_exists?("#{chocoInstallPath}\\bin\\choco.exe"))) ||
+ ('C:\ProgramData\chocolatey' if file_exists?('C:\ProgramData\chocolatey\bin\choco.exe')) ||
+ ('C:\Chocolatey' if file_exists?('C:\Chocolatey\bin\choco.exe')) ||
+ "#{ENV['ALLUSERSPROFILE']}\\chocolatey"
+
+ chocopath += '\bin\choco.exe'
+ else
+ chocopath = 'choco.exe'
+ end
+
+ chocopath
+ end
+ module_function :chocolatey_command
+
+ def set_env_chocolateyinstall
+ ENV['ChocolateyInstall'] = PuppetX::Chocolatey::ChocolateyInstall.install_path
+ end
+ module_function :set_env_chocolateyinstall
+
+ def choco_version
+ @chocoversion ||= self.strip_beta_from_version(PuppetX::Chocolatey::ChocolateyVersion.version)
+ end
+ module_function :choco_version
+
+ def self.strip_beta_from_version(value)
+ return nil if value.nil?
+
+ value.split(/-/)[0]
+ end
+
+ def choco_config_file
+ chocoInstallPath = PuppetX::Chocolatey::ChocolateyInstall.install_path
+ choco_config = "#{chocoInstallPath}\\config\\chocolatey.config"
+
+ # choco may be installed, but a config file doesn't exist until the
+ # first run of choco - trigger that by checking the version
+ choco_run_ensure_config = choco_version
+
+ return choco_config if file_exists?(choco_config)
+
+ old_choco_config = "#{chocoInstallPath}\\chocolateyinstall\\chocolatey.config"
+
+ return old_choco_config if file_exists?(old_choco_config)
+
+ return nil
+ end
+ module_function :choco_config_file
+
+ # clears the cached values
+ def clear_cached_values
+ @chocoversion = nil
+ @compiled_choco = nil
+ end
+ module_function :clear_cached_values
+
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_install.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_install.rb
new file mode 100644
index 000000000..66c835af1
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_install.rb
@@ -0,0 +1,34 @@
+module PuppetX
+ module Chocolatey
+ class ChocolateyInstall
+
+ def self.install_path
+ value = nil
+
+ if Puppet::Util::Platform.windows?
+ require 'win32/registry'
+
+ begin
+ hive = Win32::Registry::HKEY_LOCAL_MACHINE
+ hive.open('SYSTEM\CurrentControlSet\Control\Session Manager\Environment', Win32::Registry::KEY_READ | 0x100) do |reg|
+ value = reg['ChocolateyInstall']
+ end
+ rescue Win32::Registry::Error => e
+ value = nil
+ end
+ end
+
+ # If machine level is not set, use process or user as the intended
+ # location where Chocolatey would be installed.
+ # Since it is technically possible that Chocolatey could exist on
+ # non-Windows installations, we don't want to confine this
+ # to just Windows.
+ if value.nil?
+ value = ENV['ChocolateyInstall']
+ end
+
+ value
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_version.rb b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_version.rb
new file mode 100644
index 000000000..5e3478484
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/lib/puppet_x/chocolatey/chocolatey_version.rb
@@ -0,0 +1,32 @@
+require 'pathname'
+require Pathname.new(__FILE__).dirname + 'chocolatey_install'
+
+module PuppetX
+ module Chocolatey
+ class ChocolateyVersion
+
+ OLD_CHOCO_MESSAGE = "Please run chocolatey /? or chocolatey help - chocolatey v"
+
+ def self.version
+ version = nil
+ choco_path = "#{PuppetX::Chocolatey::ChocolateyInstall.install_path}\\bin\\choco.exe"
+ if Puppet::Util::Platform.windows? && File.exist?(choco_path)
+ begin
+ # call `choco -v`
+ # - new choco will output a single value e.g. `0.9.9`
+ # - old choco is going to return the default output e.g. `Please run chocolatey /?`
+ version = Puppet::Util::Execution.execute("#{choco_path} -v").gsub(OLD_CHOCO_MESSAGE,'')
+ # - other messages, such as upgrade warnings or warnings about
+ # installing the licensed extension once the license is installed
+ # may show up when running this comamnd. Remove those as well
+ version = version.split(/\r\n|\n|\r/).last.strip unless version.nil?
+ rescue StandardError => e
+ version = '0'
+ end
+ end
+
+ version
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/manifests/config.pp b/modules/utilities/windows/repository_managers/chocolatey/manifests/config.pp
new file mode 100644
index 000000000..bd51307bc
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/manifests/config.pp
@@ -0,0 +1,34 @@
+# chocolatey::config - Private class used for configuration
+class chocolatey::config {
+ assert_private()
+
+ # this will require a second converge when choco is not
+ # installed the first time through. This is on purpose
+ # as we don't want to try to set these values for a
+ # version less than 0.9.9 and we don't know what the
+ # user may link to - it could be an older version of
+ # Chocolatey
+
+ $_choco_version = $chocolatey::chocolatey_version ? {
+ undef => '0',
+ default => $chocolatey::chocolatey_version
+ }
+
+# lint:ignore:80chars
+ if versioncmp($_choco_version, '0.9.9.0') >= 0 and versioncmp($_choco_version, '0.9.10.0') < 0 {
+ $_choco_exe_path = "${chocolatey::choco_install_location}\\bin\\choco.exe"
+
+ $_enable_autouninstaller = $chocolatey::enable_autouninstaller ? {
+ false => 'disable',
+ default => 'enable'
+ }
+
+ exec { "chocolatey_autouninstaller_${_enable_autouninstaller}":
+ path => $::path,
+ command => "${_choco_exe_path} feature -r ${_enable_autouninstaller} -n autoUninstaller",
+ unless => "cmd.exe /c ${_choco_exe_path} feature list -r | findstr /B /I /C:\"autoUninstaller - [${_enable_autouninstaller}d]\"",
+ environment => ["ChocolateyInstall=${::chocolatey::choco_install_location}"]
+ }
+ }
+# lint:endignore
+}
diff --git a/modules/utilities/windows/repository_managers/chocolatey/manifests/init.pp b/modules/utilities/windows/repository_managers/chocolatey/manifests/init.pp
new file mode 100644
index 000000000..d2729dc12
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/manifests/init.pp
@@ -0,0 +1,104 @@
+# chocolatey - Used for managing installation and configuration
+# of Chocolatey itself.
+#
+# @author Rob Reynolds, Rich Siegel, and puppet-chocolatey contributors
+#
+# @example Default - This will by default ensure Chocolatey is installed and ready for use.
+# include chocolatey
+#
+# @example Override default install location
+# class {'chocolatey':
+# choco_install_location => 'D:\secured\choco',
+# }
+#
+# @example Use an internal Chocolatey.nupkg for installation
+# class {'chocolatey':
+# chocolatey_download_url => 'https://internalurl/to/chocolatey.nupkg',
+# use_7zip => false,
+# choco_install_timeout_seconds => 2700,
+# }
+#
+# @example Use a file chocolatey.0.9.9.9.nupkg for installation
+# class {'chocolatey':
+# chocolatey_download_url => 'file:///c:/location/of/chocolatey.0.9.9.9.nupkg',
+# use_7zip => false,
+# choco_install_timeout_seconds => 2700,
+# }
+#
+# @example Log chocolatey bootstrap installer script output
+# class {'chocolatey':
+# log_output => true,
+# }
+#
+# @example Disable autouninstaller (use when less than 0.9.9.8)
+# class {'chocolatey':
+# enable_autouninstaller => false,
+# }
+#
+# @param [String] choco_install_location Where Chocolatey install should be
+# located. This needs to be an absolute path starting with a drive letter
+# e.g. `c:\`. Defaults to the currently detected install location based on
+# the `ChocolateyInstall` environment variable, falls back to
+# `'C:\ProgramData\chocolatey'`.
+# @param [Boolean] use_7zip Whether to use built-in shell or allow installer
+# to download 7zip to extract `chocolatey.nupkg` during installation.
+# Defaults to `false`.
+# @param [Integer] choco_install_timeout_seconds How long in seconds should
+# be allowed for the install of Chocolatey (including .NET Framework 4 if
+# necessary). Defaults to `1500` (25 minutes).
+# @param [String] chocolatey_download_url A url that will return
+# `chocolatey.nupkg`. This must be a url, but not necessarily an OData feed.
+# Any old url location will work. Defaults to
+# `'https://chocolatey.org/api/v2/package/chocolatey/'`.
+# @param [Boolean] enable_autouninstaller [Deprecated] - Should auto
+# uninstaller be turned on? Auto uninstaller is what allows Chocolatey to
+# automatically manage the uninstall of software from Programs and Features
+# without necessarily requiring a `chocolateyUninstall.ps1` file in the
+# package. Defaults to `true`. Setting is ignored in Chocolatey v0.9.10+.
+# @param [Boolean] log_output Log output from the installer. Defaults to
+# `false`.
+# @param [String] chocolatey_version chocolatey version, falls back to
+# `$::chocolateyversion`.
+class chocolatey (
+ $choco_install_location = $::chocolatey::params::install_location,
+ $use_7zip = $::chocolatey::params::use_7zip,
+ $choco_install_timeout_seconds = $::chocolatey::params::install_timeout_seconds,
+ $chocolatey_download_url = $::chocolatey::params::download_url,
+ $enable_autouninstaller = $::chocolatey::params::enable_autouninstaller,
+ $log_output = false,
+ $chocolatey_version = $::chocolatey::params::chocolatey_version
+) inherits ::chocolatey::params {
+
+
+validate_string($choco_install_location)
+# lint:ignore:140chars
+validate_re($choco_install_location, '^\w\:',
+"Please use a full path for choco_install_location starting with a local drive. Reference choco_install_location => '${choco_install_location}'."
+)
+# lint:endignore
+
+ validate_bool($use_7zip)
+ validate_integer($choco_install_timeout_seconds)
+
+ validate_string($chocolatey_download_url)
+# lint:ignore:140chars
+ validate_re($chocolatey_download_url,['^http\:\/\/','^https\:\/\/','file\:\/\/\/'],
+ "For chocolatey_download_url, if not using the default '${::chocolatey::params::download_url}', please use a Http/Https/File Url that downloads 'chocolatey.nupkg'."
+ )
+# lint:endignore
+
+ validate_bool($enable_autouninstaller)
+
+ if ((versioncmp($::clientversion, '3.4.0') >= 0) and (!defined('$::serverversion') or versioncmp($::serverversion, '3.4.0') >= 0)) {
+ class { '::chocolatey::install': } ->
+ class { '::chocolatey::config': }
+
+ contain '::chocolatey::install'
+ contain '::chocolatey::config'
+ } else {
+ anchor {'before_chocolatey':} ->
+ class { '::chocolatey::install': } ->
+ class { '::chocolatey::config': } ->
+ anchor {'after_chocolatey':}
+ }
+}
diff --git a/modules/utilities/windows/repository_managers/chocolatey/manifests/install.pp b/modules/utilities/windows/repository_managers/chocolatey/manifests/install.pp
new file mode 100644
index 000000000..9778bf3e0
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/manifests/install.pp
@@ -0,0 +1,27 @@
+# chocolatey::install - Private class used for install of Chocolatey
+class chocolatey::install {
+ assert_private()
+
+ $download_url = $::chocolatey::chocolatey_download_url
+ $unzip_type = $::chocolatey::use_7zip ? {
+ true => '7zip',
+ default => 'windows'
+ }
+
+ registry_value { 'ChocolateyInstall environment value':
+ ensure => present,
+ path => 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\ChocolateyInstall',
+ type => 'string',
+ data => $chocolatey::choco_install_location,
+ }
+
+ exec { 'install_chocolatey_official':
+ command => template('chocolatey/InstallChocolatey.ps1.erb'),
+ creates => "${::chocolatey::choco_install_location}\\bin\\choco.exe",
+ provider => powershell,
+ timeout => $::chocolatey::choco_install_timeout_seconds,
+ logoutput => $::chocolatey::log_output,
+ environment => ["ChocolateyInstall=${::chocolatey::choco_install_location}"],
+ # require => Registry_value['ChocolateyInstall environment value'],
+ }
+}
diff --git a/modules/utilities/windows/repository_managers/chocolatey/manifests/params.pp b/modules/utilities/windows/repository_managers/chocolatey/manifests/params.pp
new file mode 100644
index 000000000..5d6f91be8
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/manifests/params.pp
@@ -0,0 +1,9 @@
+# chocolatey::params - Default parameters
+class chocolatey::params {
+ $install_location = $::choco_install_path # default is C:\ProgramData\chocolatey
+ $download_url = 'https://chocolatey.org/api/v2/package/chocolatey/'
+ $use_7zip = false
+ $install_timeout_seconds = 1500
+ $enable_autouninstaller = true
+ $chocolatey_version = $::chocolateyversion
+}
diff --git a/modules/utilities/windows/repository_managers/chocolatey/metadata.json b/modules/utilities/windows/repository_managers/chocolatey/metadata.json
new file mode 100644
index 000000000..5701a6278
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/metadata.json
@@ -0,0 +1,53 @@
+{
+ "name": "puppetlabs-chocolatey",
+ "version": "2.0.1",
+ "author": "Puppet Inc",
+ "summary": "Chocolatey package provider for Puppet",
+ "license": "Apache-2.0",
+ "source": "https://github.com/puppetlabs/puppetlabs-chocolatey",
+ "project_page": "https://github.com/puppetlabs/puppetlabs-chocolatey",
+ "issues_url": "https://tickets.puppet.com/browse/MODULES",
+ "dependencies": [
+ {"name":"puppetlabs/stdlib","version_requirement":">= 4.6.0 < 5.0.0"},
+ {"name":"puppetlabs/powershell","version_requirement":">= 1.0.1 < 3.0.0"},
+ {"name":"puppetlabs/registry","version_requirement":">= 1.0.0 < 3.0.0"}
+ ],
+ "data_provider": null,
+ "description": "Chocolatey package provider for Puppet",
+ "tags": [
+ "microsoft",
+ "powershell",
+ ".NET Framework",
+ ".Net",
+ "dot_net",
+ "chocolatey",
+ "package",
+ "package manager",
+ "chocolatey for business",
+ "chocolatey professional"
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": ">= 3.0.0 < 2016.4.0"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": ">= 3.0.0 < 5.0.0"
+ }
+ ],
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "Windows",
+ "operatingsystemrelease": [
+ "Server 2008",
+ "Server 2008 R2",
+ "Server 2012",
+ "Server 2012 R2",
+ "7",
+ "8.1",
+ "10"
+ ]
+ }
+ ]
+}
diff --git a/modules/utilities/windows/repository_managers/chocolatey/secgen_metadata.xml b/modules/utilities/windows/repository_managers/chocolatey/secgen_metadata.xml
new file mode 100644
index 000000000..7484a39c0
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/secgen_metadata.xml
@@ -0,0 +1,17 @@
+
+
+
+ Chocolatey install
+ Jason Keighley
+ Apache v2
+ A Chocolatey installation
+
+ repository_managers
+ windows
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/classes/config_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/config_spec.rb
new file mode 100644
index 000000000..e85f69b07
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/config_spec.rb
@@ -0,0 +1,77 @@
+require 'spec_helper'
+
+RSpec.describe 'chocolatey' do
+ context 'contains config.pp' do
+ context 'with older choco installed' do
+ let(:facts) {
+ {
+ :chocolateyversion => '0.9.8.33',
+ :choco_install_path => 'C:\ProgramData\chocolatey',
+ }
+ }
+
+ [true, false].each do |param_value|
+ feature_enable = 'enable'
+ feature_enable = 'disable' if !param_value
+
+ context "enable_autouninstaller => #{param_value}" do
+ let(:params) {{ :enable_autouninstaller => param_value }}
+
+ it { is_expected.not_to contain_exec("chocolatey_autouninstaller_#{feature_enable}") }
+
+ it {
+ is_expected.not_to contain_exec("chocolatey_autouninstaller_#{feature_enable}").with_command("C:\\ProgramData\\chocolatey\\bin\\choco.exe feature -r #{feature_enable} -n autoUninstaller")
+ }
+ end
+ end
+ end
+
+ context 'without choco installed' do
+ let(:facts) {
+ {
+ :chocolateyversion => '0',
+ :choco_install_path => 'C:\ProgramData\chocolatey',
+ }
+ }
+
+ [true, false].each do |param_value|
+ feature_enable = 'enable'
+ feature_enable = 'disable' if !param_value
+
+ context "enable_autouninstaller => #{param_value}" do
+ let(:params) {{ :enable_autouninstaller => param_value }}
+
+ it { is_expected.not_to contain_exec("chocolatey_autouninstaller_#{feature_enable}") }
+
+ it {
+ is_expected.not_to contain_exec("chocolatey_autouninstaller_#{feature_enable}").with_command("C:\\ProgramData\\chocolatey\\bin\\choco.exe feature -r #{feature_enable} -n autoUninstaller")
+ }
+ end
+ end
+ end
+
+ context 'with choco.exe installed' do
+ let(:facts) {
+ {
+ :chocolateyversion => '0.9.9.8',
+ :choco_install_path => 'C:\ProgramData\chocolatey',
+ }
+ }
+
+ [true, false].each do |param_value|
+ feature_enable = 'enable'
+ feature_enable = 'disable' if !param_value
+
+ context "enable_autouninstaller => #{param_value}" do
+ let(:params) {{ :enable_autouninstaller => param_value }}
+
+ it { is_expected.to contain_exec("chocolatey_autouninstaller_#{feature_enable}") }
+
+ it {
+ is_expected.to contain_exec("chocolatey_autouninstaller_#{feature_enable}").with_command("C:\\ProgramData\\chocolatey\\bin\\choco.exe feature -r #{feature_enable} -n autoUninstaller")
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/classes/coverage_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/coverage_spec.rb
new file mode 100644
index 000000000..12513b83c
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/coverage_spec.rb
@@ -0,0 +1 @@
+at_exit { RSpec::Puppet::Coverage.report! }
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/classes/init_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/init_spec.rb
new file mode 100644
index 000000000..180903928
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/init_spec.rb
@@ -0,0 +1,200 @@
+require 'spec_helper'
+
+describe 'chocolatey' do
+ let(:facts) {
+ {
+ :chocolateyversion => '0.9.9.8',
+ :choco_install_path => 'C:\ProgramData\chocolatey',
+ }
+ }
+
+ [{}].each do |params|
+ context "#{params}" do
+ let(:params) { params }
+
+ it 'should compile successfully' do
+ catalogue
+ end
+
+ #it { is_expected.to compile }
+ #it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_class('chocolatey') }
+ it { is_expected.to contain_class('chocolatey::params') }
+ it { is_expected.to contain_class('chocolatey::install') }
+ it { is_expected.to contain_class('chocolatey::config') }
+ end
+ end
+
+ context "chocolatey_download_url =>" do
+ ['https://chocolatey.org/api/v2/package/chocolatey/','http://location','file:///c:/somwhere/chocolatey.nupkg'].each do |param_value|
+ context "#{param_value}" do
+ let (:params) {{
+ :chocolatey_download_url => param_value
+ }}
+
+ it 'should compile successfully' do
+ catalogue
+ end
+ end
+ end
+
+ if Puppet.version < '4.0.0'
+ invalid_url_values = ['\\\\ciflocation\\share','bob',"4",'',3]
+ not_a_string_values = [false]
+ else
+ invalid_url_values = ['\\\\ciflocation\\share','bob',"4",'']
+ not_a_string_values = [false, 3]
+ end
+
+ invalid_url_values.each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ :chocolatey_download_url => param_value
+ }}
+
+ let(:error_message) { /use a Http\/Https\/File Url that downloads/ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+
+ not_a_string_values.each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ :chocolatey_download_url => param_value
+ }}
+
+ let(:error_message) { /is not a string/ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+ end
+
+ context "choco_install_location =>" do
+ ['C:\\ProgramData\\chocolatey','D:\\somewhere'].each do |param_value|
+ context "#{param_value}" do
+ let (:params) {{
+ :choco_install_location => param_value
+ }}
+
+ it 'should compile successfully' do
+ catalogue
+ end
+ end
+ end
+
+ if Puppet.version < '4.0.0'
+ [false].each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ :choco_install_location => param_value
+ }}
+
+ let(:error_message) { /is not a string/ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+
+ #1 is actually a string before v4.
+ [1,'https://somewhere','\\\\overhere',''].each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ :choco_install_location => param_value
+ }}
+
+ let(:error_message) { /Please use a full path for choco_install_location/ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+ else
+ [1,false].each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ :choco_install_location => param_value
+ }}
+
+ let(:error_message) { /is not a string/ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+
+ ['https://somewhere','\\\\overhere',''].each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ :choco_install_location => param_value
+ }}
+
+ let(:error_message) { /Please use a full path for choco_install_location/ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+ end
+ end
+
+ context "choco_install_timeout_seconds =>" do
+ [1500,8000,"1",'30'].each do |param_value|
+ context "#{param_value}" do
+ let (:params) {{
+ :choco_install_timeout_seconds => param_value
+ }}
+
+ it 'should compile successfully' do
+ catalogue
+ end
+ end
+ end
+
+ ['string',false,''].each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ :choco_install_timeout_seconds => param_value
+ }}
+
+ let(:error_message) { /Expected first argument to be an Integer/ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+ end
+
+ ['use_7zip','enable_autouninstaller'].each do |boolean_param|
+ context "#{boolean_param} =>" do
+ [true, false].each do |param_value|
+ context "#{param_value}" do
+ let (:params) {{
+ boolean_param.to_sym => param_value
+ }}
+
+ it 'should compile successfully' do
+ catalogue
+ end
+ end
+ end
+
+ ['true','false','bob',3,"4",''].each do |param_value|
+ context "#{param_value} (invalid scenario)" do
+ let (:params) {{
+ boolean_param.to_sym => param_value
+ }}
+
+ let(:error_message) { /is not a boolean./ }
+ it {
+ expect { catalogue }.to raise_error(Puppet::Error, error_message)
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/classes/install_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/install_spec.rb
new file mode 100644
index 000000000..b1a14e6ef
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/classes/install_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+RSpec.describe 'chocolatey' do
+
+ let(:facts) {
+ {
+ :chocolateyversion => '0.9.9.8',
+ :choco_install_path => 'C:\ProgramData\chocolatey',
+ }
+ }
+
+ context 'contains install.pp' do
+ ['c:\local_folder', "C:\\ProgramData\\chocolatey"].each do |param_value|
+ context "choco_install_location => #{param_value}" do
+ let(:params) {{ :choco_install_location => param_value }}
+
+ it { is_expected.to contain_exec('install_chocolatey_official').with_creates("#{param_value}\\bin\\choco.exe") }
+ end
+ end
+
+
+ [1500, 35].each do |param_value|
+ context "choco_install_timeout_seconds => #{param_value}" do
+ let(:params) {{ :choco_install_timeout_seconds => param_value }}
+
+ it { is_expected.to contain_exec('install_chocolatey_official').with_timeout("#{param_value}") }
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/spec_helper.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/spec_helper.rb
new file mode 100644
index 000000000..72c407699
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/spec_helper.rb
@@ -0,0 +1,64 @@
+#require 'ruby-prof'
+#RubyProf.start
+
+IDEAL_CONSOLE_WIDTH = 72
+def horizontal_rule(width = 5)
+ '=' * [width, IDEAL_CONSOLE_WIDTH].min
+end
+
+require 'puppetlabs_spec_helper/module_spec_helper'
+
+# require dependencies
+gems = [
+ #'minitest/autorun', # http://docs.seattlerb.org/minitest/
+ #'minitest/unit', # https://github.com/freerange/mocha#bundler
+ 'mocha', # http://gofreerange.com/mocha/docs/Mocha/Configuration.html
+ 'puppet',
+]
+begin
+ gems.each {|gem| require gem}
+rescue => e
+ # http://goo.gl/r3nFG
+ # emphasize dependency failures in case a task spews lots of output
+ warn horizontal_rule(e.message.length)
+ warn e.class
+ warn e.message
+ warn horizontal_rule(e.message.length)
+ exit(1)
+end
+
+RSpec.configure do |c|
+ # set the environment variable before files are loaded, otherwise it is too late
+ ENV['ChocolateyInstall'] = 'c:\blah'
+
+ begin
+ Win32::Registry.any_instance.stubs(:[]).with('Bind')
+ Win32::Registry.any_instance.stubs(:[]).with('Domain')
+ Win32::Registry.any_instance.stubs(:[]).with('ChocolateyInstall').raises(Win32::Registry::Error.new(2), 'file not found yo')
+ rescue
+ # we don't care
+ end
+
+ # https://www.relishapp.com/rspec/rspec-core/v/2-12/docs/mock-framework-integration/mock-with-mocha!
+ c.mock_framework = :mocha
+ # see output for all failures
+ c.fail_fast = false
+ c.expect_with :rspec do |e|
+ e.syntax = [:should, :expect]
+ end
+ c.raise_errors_for_deprecations!
+
+ c.after :suite do
+ #result = RubyProf.stop
+ # Print a flat profile to text
+ #printer = RubyProf::FlatPrinter.new(result)
+ #printer.print(STDOUT)
+ end
+end
+
+# We need this because the RAL uses 'should' as a method. This
+# allows us the same behaviour but with a different method name.
+class Object
+ alias :must :should
+ alias :must_not :should_not
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/facter/choco_install_path_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/facter/choco_install_path_spec.rb
new file mode 100644
index 000000000..fb1869558
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/facter/choco_install_path_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+require 'facter'
+require 'puppet_x/chocolatey/chocolatey_install'
+
+describe 'choco_install_path fact' do
+ subject(:fact) { Facter.fact(:choco_install_path) }
+
+ before :each do
+ Facter.clear
+ Facter.clear_messages
+ end
+
+ context "on Windows", :if => Puppet::Util::Platform.windows? do
+ it "should return the output of PuppetX::Chocolatey::ChocolateyInstall.install_path" do
+ expected_value = 'C:\somewhere'
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns(expected_value)
+
+ subject.value.must == expected_value
+ end
+
+ it "should return the default path when PuppetX::Chocolatey::ChocolateyInstall.install_path is nil" do
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns(nil)
+
+ subject.value.must == 'C:\ProgramData\chocolatey'
+ end
+ end
+
+ after :each do
+ Facter.clear
+ Facter.clear_messages
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/facter/chocolateyversion_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/facter/chocolateyversion_spec.rb
new file mode 100644
index 000000000..c6f6f143a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/facter/chocolateyversion_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+require 'facter'
+require 'puppet_x/chocolatey/chocolatey_version'
+
+describe 'chocolateyversion fact' do
+ subject(:fact) { Facter.fact(:chocolateyversion) }
+
+ before :each do
+ Facter.clear
+ Facter.clear_messages
+ end
+
+ context "on Windows", :if => Puppet::Util::Platform.windows? do
+ it "should return the output of PuppetX::Chocolatey::ChocolateyVersion.version" do
+ expected_value = '1.2.3'
+ PuppetX::Chocolatey::ChocolateyVersion.expects(:version).returns(expected_value)
+
+ subject.value.must == expected_value
+ end
+
+ it "should return the default of 0 when PuppetX::Chocolatey::ChocolateyVersion.version is nil" do
+ PuppetX::Chocolatey::ChocolateyVersion.expects(:version).returns(nil)
+
+ subject.value.must == '0'
+ end
+ end
+
+ after :each do
+ Facter.clear
+ Facter.clear_messages
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateyconfig/windows_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateyconfig/windows_spec.rb
new file mode 100644
index 000000000..696181351
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateyconfig/windows_spec.rb
@@ -0,0 +1,268 @@
+require 'spec_helper'
+require 'stringio'
+require 'puppet/type/chocolateyconfig'
+require 'puppet/provider/chocolateyconfig/windows'
+require 'rexml/document'
+
+provider = Puppet::Type.type(:chocolateyconfig).provider(:windows)
+describe provider do
+ let (:name) { 'configItem' }
+ let (:resource) { Puppet::Type.type(:chocolateyconfig).new(:provider => :windows, :name => name, :value => "yes") }
+ let (:choco_config) { 'c:\choco.config' }
+ let (:choco_install_path) { 'c:\dude\bin\choco.exe' }
+ let (:choco_config_contents) { <<-'EOT'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EOT
+ }
+
+
+ let (:minimum_supported_version) {'0.9.10.0'}
+ let (:last_unsupported_version) {'0.9.9.12'}
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyInstall.stubs(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:choco_version).returns(minimum_supported_version)
+
+ @provider = provider.new(resource)
+ resource.provider = @provider
+
+ # Stub all file and config tests
+ provider.stubs(:healthcheck)
+ end
+
+ context "verify provider" do
+ it "should be an instance of Puppet::Type::Chocolateyconfig::ProviderWindows" do
+ @provider.must be_an_instance_of Puppet::Type::Chocolateyconfig::ProviderWindows
+ end
+
+ it "should have a create method" do
+ @provider.should respond_to(:create)
+ end
+
+ it "should have an exists? method" do
+ @provider.should respond_to(:exists?)
+ end
+
+ it "should have a destroy method" do
+ @provider.should respond_to(:destroy)
+ end
+
+ it "should have a properties method" do
+ @provider.should respond_to(:properties)
+ end
+
+ it "should have a query method" do
+ @provider.should respond_to(:query)
+ end
+ end
+
+ context "properties" do
+ context ":value" do
+ #it "should default to nil" do
+ # resource[:value].should be_nil
+ #end
+
+ it "should accept c:\\cache" do
+ resource[:value] = 'c:\cache'
+ end
+
+ it "should accept 2700" do
+ resource[:value] = '2700'
+ end
+
+ it "should accept 'value with spaces'" do
+ resource[:value] = 'value with spaces'
+ end
+ end
+ end
+
+ context "self.get_configs" do
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:set_env_chocolateyinstall)
+ end
+
+ it "should error when the config file location is null" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(nil)
+
+ expect {
+ provider.get_configs
+ }.to raise_error(Puppet::ResourceError, /Config file not found for Chocolatey/)
+ end
+
+ it "should error when the config file is not found" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(false)
+
+ expect {
+ provider.get_configs
+ }.to raise_error(Puppet::ResourceError, /was unable to locate config file at/)
+ end
+
+ context "when getting configs from the config file" do
+ configs = []
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(true)
+ File.expects(:new).with(choco_config,"r").returns choco_config_contents
+
+ configs = provider.get_configs
+ end
+
+ it "should match the count of configs in the config" do
+ configs.count.must eq 11
+
+ end
+
+ it "should contain xml elements" do
+ configs[0].must be_an_instance_of REXML::Element
+ end
+ end
+ end
+
+ context "self.get_config" do
+ let (:element) { REXML::Element.new('add') }
+ element_key = "cacheLocation"
+ element_value= "c:\\cache"
+ element_description = "Cache location if not TEMP folder."
+
+ before :each do
+ element.add_attributes( { "key" => element_key,
+ "value" => element_value,
+ "description" => element_description,
+ } )
+ end
+
+ it "should return nil config when element is nil" do
+ provider.get_config(nil).must be == {}
+ end
+
+ it "should convert an element to a config" do
+ config = provider.get_config(element)
+
+ config[:name].must eq element_key
+ config[:value].must eq element_value
+ config[:description].must eq element_description
+ config[:ensure].must eq :present
+ end
+ end
+
+ context ".validation" do
+ it "should not error when Chocolatey is not installed" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).returns(false).at_least(0)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_install_path).returns(false).at_least(0)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns('')
+
+ resource.provider.validate
+ end
+
+ it "should not error when the minimum version is met" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).returns(false).at_least(0)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_install_path).returns(true).at_least(0)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version)
+
+ resource.provider.validate
+ end
+
+ it "should error when the minimum version is not met" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).returns(true).at_least(0)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(last_unsupported_version)
+
+ expect {
+ resource.provider.validate
+ }.to raise_error(Puppet::ResourceError, /Chocolatey version must be '0.9.10.0' to manage configuration values. Detected '#{last_unsupported_version}'/)
+ end
+ end
+
+ context ".flush" do
+ resource_name = "yup"
+ resource_value = "this"
+ resource_ensure = :present
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:set_env_chocolateyinstall).at_most_once
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config).at_most_once
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(true).at_most_once
+ File.expects(:new).with(choco_config,"r").returns(choco_config_contents).at_most_once
+
+ resource[:name] = resource_name
+ resource[:value] = resource_value
+ resource[:ensure] = resource_ensure
+ end
+
+ it "should ensure a config setting is set" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'config', 'set',
+ '--name', resource_name,
+ '--value', resource_value
+ ])
+
+ resource.flush
+ end
+
+ it "should ensure a config setting is removed" do
+ resource.provider.destroy
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'config', 'unset',
+ '--name', resource_name
+ ])
+
+ resource.flush
+ end
+
+ it "should provide an error message when choco execution fails" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'config', 'set',
+ '--name', resource_name,
+ '--value', resource_value
+ ]).raises(Puppet::ExecutionFailure, "Nooooo")
+
+ expect { resource.flush }.to raise_error(Puppet::Error, /Unable to set Chocolateyconfig/)
+ end
+
+
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateyfeature/windows_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateyfeature/windows_spec.rb
new file mode 100644
index 000000000..c5ac9dd67
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateyfeature/windows_spec.rb
@@ -0,0 +1,170 @@
+require 'spec_helper'
+require 'stringio'
+require 'puppet/type/chocolateyfeature'
+require 'puppet/provider/chocolateyfeature/windows'
+require 'rexml/document'
+
+provider = Puppet::Type.type(:chocolateyfeature).provider(:windows)
+describe provider do
+ let (:name) { 'allowglobalconfirmation' }
+ let (:resource) { Puppet::Type.type(:chocolateyfeature).new(:provider => :windows, :name => name, :ensure => 'enabled' ) }
+ let (:choco_config) { 'c:\choco.config' }
+ let (:choco_install_path) { 'c:\dude\bin\choco.exe' }
+ let (:choco_config_contents) { <<-'EOT'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EOT
+ }
+
+ let (:minimum_supported_version) {'0.9.9.0'}
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyInstall.stubs(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:choco_version).returns(minimum_supported_version)
+
+ @provider = provider.new(resource)
+ resource.provider = @provider
+
+ # Stub all file and config tests
+ provider.stubs(:healthcheck)
+ end
+
+ context "verify provider" do
+ it "should be an instance of Puppet::Type::Chocolateyfeature::ProviderWindows" do
+
+ @provider.must be_an_instance_of Puppet::Type::Chocolateyfeature::ProviderWindows
+ end
+
+ it "should have a enable method" do
+ @provider.should respond_to(:enable)
+ end
+
+ it "should have an exists? method" do
+ @provider.should respond_to(:exists?)
+ end
+
+ it "should have a disable method" do
+ @provider.should respond_to(:disable)
+ end
+
+ it "should have a properties method" do
+ @provider.should respond_to(:properties)
+ end
+
+ it "should have a query method" do
+ @provider.should respond_to(:query)
+ end
+ end
+
+ context "self.get_features" do
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:set_env_chocolateyinstall)
+ end
+
+ it "should error when the config file location is null" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(nil)
+
+ expect {
+ provider.get_features
+ }.to raise_error(Puppet::ResourceError, /Config file not found for Chocolatey/)
+ end
+
+ it "should error when the config file is not found" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(false)
+
+ expect {
+ provider.get_features
+ }.to raise_error(Puppet::ResourceError, /was unable to locate config file at/)
+ end
+
+ context "when getting features from the config file" do
+ features = []
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(true)
+ File.expects(:new).with(choco_config,"r").returns choco_config_contents
+
+ features = provider.get_features
+ end
+
+ it "should match the count of features in the config" do
+ features.count.must eq 14
+
+ end
+
+ it "should contain xml elements" do
+ features[0].must be_an_instance_of REXML::Element
+ end
+ end
+ end
+
+ context "self.get_feature" do
+ let (:element) { REXML::Element.new('feature') }
+ element_name = "default"
+ element_enabled = 'true'
+
+ before :each do
+ element.add_attributes( { "name" => element_name, "enabled" => element_enabled, } )
+ end
+
+ it "should return nil feature when element is nil" do
+ provider.get_feature(nil).must be == {}
+ end
+
+ it "should convert an element to a feature" do
+ feature = provider.get_feature(element)
+
+ feature[:name].must eq element_name
+ feature[:ensure].must eq :enabled
+ end
+
+ it "when feature is disabled" do
+ element.delete_attribute('enabled')
+ element.add_attribute('enabled', 'false')
+
+ feature = provider.get_feature(element)
+ feature[:ensure].must eq :disabled
+ end
+ end
+
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateysource/windows_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateysource/windows_spec.rb
new file mode 100644
index 000000000..d27d3290d
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/chocolateysource/windows_spec.rb
@@ -0,0 +1,607 @@
+require 'spec_helper'
+require 'stringio'
+require 'puppet/type/chocolateysource'
+require 'puppet/provider/chocolateysource/windows'
+require 'rexml/document'
+
+provider = Puppet::Type.type(:chocolateysource).provider(:windows)
+describe provider do
+ let (:name) { 'sourcename' }
+ let (:location) { 'c:\packages' }
+ let (:resource) { Puppet::Type.type(:chocolateysource).new(:provider => :windows, :name => name, :location => location) }
+ let (:choco_config) { 'c:\choco.config' }
+ let (:choco_install_path) { 'c:\dude\bin\choco.exe' }
+ let (:choco_config_contents) { <<-'EOT'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ EOT
+ }
+
+ let (:newer_choco_version) {'0.9.10.0'}
+ let (:minimum_supported_version_priority) {'0.9.9.9'}
+ let (:last_unsupported_version_priority) {'0.9.9.8'}
+ let (:minimum_supported_version) {'0.9.9.0'}
+ let (:last_unsupported_version) {'0.9.8.33'}
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyInstall.stubs(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:choco_version).returns(minimum_supported_version)
+
+ @provider = provider.new(resource)
+ resource.provider = @provider
+
+ # Stub all file and config tests
+ provider.stubs(:healthcheck)
+ end
+
+ context "verify provider" do
+ it "should be an instance of Puppet::Type::Chocolateysource::ProviderWindows" do
+
+ @provider.must be_an_instance_of Puppet::Type::Chocolateysource::ProviderWindows
+ end
+
+ it "should have a create method" do
+ @provider.should respond_to(:create)
+ end
+
+ it "should have an exists? method" do
+ @provider.should respond_to(:exists?)
+ end
+
+ it "should have a disable method" do
+ @provider.should respond_to(:disable)
+ end
+
+ it "should have a destroy method" do
+ @provider.should respond_to(:destroy)
+ end
+
+ it "should have a properties method" do
+ @provider.should respond_to(:properties)
+ end
+
+ it "should have a query method" do
+ @provider.should respond_to(:query)
+ end
+ end
+
+ context "properties" do
+
+ context ":location" do
+ it "should accept c:\\packages" do
+ resource[:location] = 'c:\packages'
+ end
+
+ it "should accept http://somelocation/packages" do
+ resource[:location] = 'http://somelocation/packages'
+ end
+
+ it "should accept \\\\unc\\share\\packages" do
+ resource[:location] = '\\unc\share\packages'
+ end
+ end
+
+ context ":user" do
+ it "should accept 'bob'" do
+ resource[:user] = 'bob'
+ end
+
+ it "should accept 'domain\\bob'" do
+ resource[:user] = 'domain\bob'
+ end
+
+ it "should accept api keys like 'api123-456-243 d123'" do
+ resource[:user] = 'api123-456-243 d123'
+ end
+ end
+
+ context ":password" do
+ it "should accept 'bob'" do
+ resource[:password] = 'bob'
+ end
+
+ it "should accept api keys like 'api123-456-243 d123'" do
+ resource[:password] = 'api123-456-243 d123'
+ end
+ end
+ end
+
+ context "self.get_sources" do
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:set_env_chocolateyinstall)
+ end
+
+ it "should error when the config file location is null" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(nil)
+
+ expect {
+ provider.get_sources
+ }.to raise_error(Puppet::ResourceError, /Config file not found for Chocolatey/)
+ end
+
+ it "should error when the config file is not found" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(false)
+
+ expect {
+ provider.get_sources
+ }.to raise_error(Puppet::ResourceError, /was unable to locate config file at/)
+ end
+
+ context "when getting sources from the config file" do
+ sources = []
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(true)
+ File.expects(:new).with(choco_config,"r").returns choco_config_contents
+
+ sources = provider.get_sources
+ end
+
+ it "should match the count of sources in the config" do
+ sources.count.must eq 3
+
+ end
+
+ it "should contain xml elements" do
+ sources[0].must be_an_instance_of REXML::Element
+ end
+ end
+ end
+
+ context "self.get_source" do
+ let (:element) { REXML::Element.new('source') }
+ element_id = "default"
+ element_value= "c:\\packages"
+ element_disabled = "false"
+ element_priority = "10"
+ element_user = "thisguy"
+ element_password = "super/encrypted+value=="
+
+
+ before :each do
+ element.add_attributes( { "id" => element_id,
+ "value" => element_value,
+ "disabled" => element_disabled,
+ "priority" => element_priority,
+ "user" => element_user,
+ "password" => element_password
+ } )
+ end
+
+ it "should return nil source when element it nil" do
+ provider.get_source(nil).must be == {}
+ end
+
+ it "should convert an element to a source" do
+ source = provider.get_source(element)
+
+ source[:name].must eq element_id
+ source[:location].must eq element_value
+ source[:priority].must eq element_priority
+ source[:user].must eq element_user
+ source[:ensure].must eq :present
+ end
+
+ it "should convert a bare bones element to a source" do
+ element.delete_attribute('disabled')
+ element.delete_attribute('priority')
+ element.delete_attribute('user')
+ element.delete_attribute('password')
+
+ source = provider.get_source(element)
+
+ source[:name].must eq element_id
+ source[:location].must eq element_value
+ source[:ensure].must eq :present
+ end
+
+ it "when source is disabled" do
+ element.delete_attribute('disabled')
+ element.add_attribute('disabled', 'true')
+
+ source = provider.get_source(element)
+ source[:ensure].must eq :disabled
+ end
+ end
+
+ context ".validation" do
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).returns(true).at_least(0)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_install_path).returns(true).at_least(0)
+ end
+
+ it "should not warn when both user/password are empty" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version)
+ Puppet.expects(:warning).never
+ Puppet.expects(:debug).never
+
+ resource.provider.validate
+ end
+
+ it "should throw when choco version is less than the minimum supported version" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(last_unsupported_version)
+
+ expect {
+ resource.provider.validate
+ }.to raise_error(Puppet::Error, /Chocolatey version must be '0.9.9.0' to manage configuration values with Puppet/)
+ end
+
+ it "should write a debug message on password when password is not empty" do
+ resource[:user] = 'tim'
+ resource[:password] = 'tim'
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet.expects(:warning).never
+ Puppet.expects(:debug).with("The password is not ensurable, so Puppet is unable to change the value using chocolateysource resource. As a workaround, a password change can be in the form of an exec. Reference Chocolateysource[#{name}]")
+
+ resource.provider.validate
+ end
+
+ it "should not warn on user/password on newer choco versions" do
+ resource[:user] = 'tim'
+ resource[:password] = 'tim'
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet.expects(:warning).never
+
+ resource.provider.validate
+ end
+
+ it "should not warn on user/password when choco version is the minimum supported version" do
+ resource[:user] = 'tim'
+ resource[:password] = 'tim'
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version)
+ Puppet.expects(:warning).never
+
+ resource.provider.validate
+ end
+
+ it "should not warn if priority is not set" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet.expects(:warning).never
+
+ resource.provider.validate
+ end
+
+ it "should not warn if priority is not set on older unsupported versions" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(last_unsupported_version_priority)
+ Puppet.expects(:warning).never
+
+ resource.provider.validate
+ end
+
+ it "should not warn if priority is 0 on unsupported versions" do
+ resource[:priority] = 0
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(last_unsupported_version_priority)
+ Puppet.expects(:warning).never
+
+ resource.provider.validate
+ end
+
+ it "should not warn on priority when choco version is newer than the minimum supported version" do
+ resource[:priority] = 10
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet.expects(:warning).never
+
+ resource.provider.validate
+ end
+
+ it "should not warn on priority when choco version is the minimum supported version" do
+ resource[:priority] = 10
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version_priority)
+ Puppet.expects(:warning).never
+
+ resource.provider.validate
+ end
+
+ it "should warn on priority when choco version is less than the minimum supported version" do
+ resource[:priority] = 10
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(last_unsupported_version_priority)
+ Puppet.expects(:warning).with("Chocolatey is unable to manage priority for sources when version is less than 0.9.9.9. The value you set will be ignored.")
+
+ resource.provider.validate
+ end
+
+ it "should pass when ensure is not present and location is empty" do
+ no_location_resource = Puppet::Type.type(:chocolateysource).new(:name => 'source', :ensure => :disabled )
+ no_location_resource.provider = provider.new(no_location_resource)
+
+ no_location_resource.provider.validate
+ end
+
+ it "should fail when ensure => present and location is empty" do
+ expect {
+ no_location_resource = Puppet::Type.type(:chocolateysource).new(:name => 'source')
+ no_location_resource.provider = provider.new(no_location_resource)
+
+ no_location_resource.provider.validate
+ }.to raise_error(Exception, /non-empty location/)
+ # check for just an exception here
+ # In some versions of Puppet, this comes back as ArgumentError
+ # In other versions of Puppet, this comes back as Puppet::Error
+ end
+ end
+
+ context ".flush" do
+ resource_name = "yup"
+ resource_location = "loc"
+ resource_ensure = :present
+ resource_priority = 10
+ resource_user = "thatguy"
+ resource_password = "secrets!"
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:set_env_chocolateyinstall).at_most_once
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_config_file).returns(choco_config).at_most_once
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(choco_config).returns(true).at_most_once
+ File.expects(:new).with(choco_config,"r").returns(choco_config_contents).at_most_once
+
+ resource[:name] = resource_name
+ resource[:location] = resource_location
+ resource[:ensure] = resource_ensure
+ end
+
+ it "should ensure a source is present with minimal values set" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--priority', 0,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', 'yup'
+ ])
+
+ resource.flush
+ end
+
+ it "should ensure a source is present with all values set" do
+ resource[:priority] = resource_priority
+ resource[:user] = resource_user
+ resource[:password] = resource_password
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--user', resource_user,
+ '--password', resource_password,
+ '--priority', resource_priority,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name
+ ])
+
+ resource.flush
+ end
+
+ it "should set priority when present" do
+ resource[:priority] = resource_priority
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--priority', resource_priority,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name
+ ])
+
+ resource.flush
+ end
+
+ it "should set user and password when user is present" do
+ resource[:user] = resource_user
+ resource[:password] = resource_password
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--user', resource_user,
+ '--password', resource_password,
+ '--priority', 0,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name
+ ])
+
+ resource.flush
+ end
+
+ it "should set user and password when choco version is newer than the minimum supported version" do
+ resource[:user] = resource_user
+ resource[:password] = resource_password
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--user', resource_user,
+ '--password', resource_password,
+ '--priority', 0,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name
+ ])
+
+ resource.flush
+ end
+
+ it "should set user and password when choco version is the minimum supported version" do
+ resource[:user] = resource_user
+ resource[:password] = resource_password
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--user', resource_user,
+ '--password', resource_password,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name
+ ])
+
+ resource.flush
+ end
+
+ it "should set priority when choco version is newer than the minimum supported version" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--priority', 0,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name,
+ ])
+
+ resource.flush
+ end
+
+ it "should set priority when choco version is the minimum supported version" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_version_priority)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--priority', 0,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name,
+ ])
+
+ resource.flush
+ end
+
+ it "should not set priority when choco version is less than the minimum supported version" do
+ resource[:priority] = resource_priority
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(last_unsupported_version_priority)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', resource_name,
+ ])
+
+ resource.flush
+ end
+
+ it "should disable a source when ensure => disabled" do
+ resource[:ensure] = :disabled
+ resource[:name] = 'chocolatey'
+ resource.provider.disable
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'disable',
+ '--name', 'chocolatey'
+ ])
+
+ resource.flush
+ end
+
+ it "should remove a source when ensure => absent" do
+ resource[:ensure] = :absent
+ resource.provider.destroy
+
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).never
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'remove',
+ '--name', resource_name,
+ ])
+
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'enable',
+ '--name', 'yup'
+ ]).never
+
+ resource.flush
+ end
+
+ it "should provide an error message when choco execution fails" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(newer_choco_version)
+ Puppet::Util::Execution.expects(:execute).with([provider.command(:chocolatey),
+ 'source', 'add',
+ '--name', resource_name,
+ '--source', resource_location,
+ '--priority', 0,
+ ]).raises(Puppet::ExecutionFailure, "Nooooo")
+
+ expect { resource.flush }.to raise_error(Puppet::Error, /Unable to set Chocolatey source/)
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/package/chocolatey_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/package/chocolatey_spec.rb
new file mode 100644
index 000000000..e6fa57be1
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/provider/package/chocolatey_spec.rb
@@ -0,0 +1,514 @@
+require 'spec_helper'
+require 'stringio'
+require 'puppet/type/package'
+require 'puppet/provider/package/chocolatey'
+
+provider = Puppet::Type.type(:package).provider(:chocolatey)
+
+describe provider do
+ let (:resource) { Puppet::Type.type(:package).new(:provider => :chocolatey, :name => "chocolatey") }
+ let (:first_compiled_choco_version) {'0.9.9.0'}
+ let (:newer_choco_version) {'0.9.10.0'}
+ let (:last_posh_choco_version) {'0.9.8.33'}
+ let (:minimum_supported_choco_uninstall_source) {'0.9.10.0'}
+ let (:minimum_supported_choco_exit_codes) {'0.9.10.0'}
+ let (:choco_zero_ten_zero) {'0.10.0'}
+
+ before :each do
+ @provider = provider.new(resource)
+ resource.provider = @provider
+
+ # Stub all file and config tests
+ provider.stubs(:healthcheck)
+ Puppet::Util::Execution.stubs(:execute)
+ end
+
+ it "should be an instance of Puppet::Type::Package::ProviderChocolatey" do
+ @provider.must be_an_instance_of Puppet::Type::Package::ProviderChocolatey
+ end
+
+ it "should have an install method" do
+ @provider.should respond_to(:install)
+ end
+
+ it "should have a latest method" do
+ @provider.should respond_to(:uninstall)
+ end
+
+ it "should have an update method" do
+ @provider.should respond_to(:update)
+ end
+
+ it "should have a latest method" do
+ @provider.should respond_to(:latest)
+ end
+
+ context "parameter :source" do
+ it "should default to nil" do
+ resource[:source].should be_nil
+ end
+
+ it "should accept c:\\packages" do
+ resource[:source] = 'c:\packages'
+ end
+
+ it "should accept http://somelocation/packages" do
+ resource[:source] = 'http://somelocation/packages'
+ end
+
+ it "should accept \\\\unc\\share\\packages" do
+ resource[:source] = '\\unc\share\packages'
+ end
+ end
+
+ context "when installing" do
+ context "with compiled choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(true)
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:file_exists?).with('c:\dude\bin\choco.exe').returns(true)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(first_compiled_choco_version)
+ # unhold is called in installs on compiled choco
+ Puppet::Util::Execution.stubs(:execute)
+ end
+
+ it "should use install command without versioned package" do
+ resource[:ensure] = :present
+ @provider.expects(:chocolatey).with('install', 'chocolatey','-y', nil)
+
+ @provider.install
+ end
+
+ it "should call with ignore package exit codes when = 0.9.10" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_choco_exit_codes).at_least_once
+ resource[:ensure] = :present
+ @provider.expects(:chocolatey).with('install', 'chocolatey','-y', nil, '--ignore-package-exit-codes')
+
+ @provider.install
+ end
+
+ it "should call with ignore package exit codes when > 0.9.10" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(choco_zero_ten_zero).at_least_once
+ resource[:ensure] = :present
+ @provider.expects(:chocolatey).with('install', 'chocolatey','-y', nil, '--ignore-package-exit-codes')
+
+ @provider.install
+ end
+
+ it "should use upgrade command with versioned package" do
+ resource[:ensure] = '1.2.3'
+ @provider.expects(:chocolatey).with('upgrade', 'chocolatey', '-version', '1.2.3', '-y', nil)
+
+ @provider.install
+ end
+
+ it "should call install instead of upgrade if package name ends with .config" do
+ resource[:name] = "packages.config"
+ resource[:ensure] = :present
+ @provider.expects(:chocolatey).with('install', 'packages.config','-y', nil)
+
+ @provider.install
+ end
+
+ it "should use source if it is specified" do
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('install','chocolatey','-y', '-source', 'c:\packages', nil)
+
+ @provider.install
+ end
+ end
+
+ context "with posh choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(false)
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:file_exists?).with('c:\dude\bin\choco.exe').returns(true)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(last_posh_choco_version)
+ end
+
+ it "should use install command without versioned package" do
+ resource[:ensure] = :present
+ @provider.expects(:chocolatey).with('install', 'chocolatey', nil)
+
+ @provider.install
+ end
+
+ it "should use update command with versioned package" do
+ resource[:ensure] = '1.2.3'
+ @provider.expects(:chocolatey).with('update', 'chocolatey', '-version', '1.2.3', nil)
+
+ @provider.install
+ end
+
+ it "should use source if it is specified" do
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('install','chocolatey', '-source', 'c:\packages', nil)
+
+ @provider.install
+ end
+ end
+ end
+
+ context "when holding" do
+ context "with compiled choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(true)
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:file_exists?).with('c:\dude\bin\choco.exe').returns(true)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(first_compiled_choco_version)
+ # unhold is called in installs on compiled choco
+ Puppet::Util::Execution.stubs(:execute)
+ end
+
+ it "should use install command with held package" do
+ resource[:ensure] = :held
+ @provider.expects(:chocolatey).with('install', 'chocolatey','-y', nil)
+ @provider.expects(:chocolatey).with('pin', 'add', '-n', 'chocolatey')
+
+ @provider.hold
+ end
+ end
+
+ context "with posh choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(false)
+ end
+
+ it "should throw an argument error with held package" do
+ resource[:ensure] = :held
+
+ expect { @provider.hold }.to raise_error(ArgumentError, "Only choco v0.9.9+ can use ensure => held")
+ end
+ end
+ end
+
+ context "when uninstalling" do
+ context "with compiled choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(true)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(first_compiled_choco_version)
+ # unhold is called in installs on compiled choco
+ Puppet::Util::Execution.stubs(:execute)
+ end
+
+ it "should call the remove operation" do
+ @provider.expects(:chocolatey).with('uninstall', 'chocolatey','-fy', nil)
+
+ @provider.uninstall
+ end
+
+ it "should call with ignore package exit codes when = 0.9.10" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_choco_exit_codes).at_least_once
+ @provider.expects(:chocolatey).with('uninstall', 'chocolatey','-fy', nil, '--ignore-package-exit-codes')
+
+ @provider.uninstall
+ end
+
+ it "should call with ignore package exit codes when > 0.9.10" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(choco_zero_ten_zero).at_least_once
+ @provider.expects(:chocolatey).with('uninstall', 'chocolatey','-fy', nil, '--ignore-package-exit-codes')
+
+ @provider.uninstall
+ end
+
+ it "should use ignore source if it is specified and the version is less than 0.9.10" do
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('uninstall','chocolatey','-fy', nil)
+
+ @provider.uninstall
+ end
+
+ it "should use source if it is specified and the version is at least 0.9.10" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_choco_uninstall_source).at_least_once
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('uninstall','chocolatey', '-fy', '-source', 'c:\packages', nil, '--ignore-package-exit-codes')
+
+ @provider.uninstall
+ end
+
+ it "should use source if it is specified and the version is greater than 0.9.10" do
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(choco_zero_ten_zero).at_least_once
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('uninstall','chocolatey', '-fy', '-source', 'c:\packages', nil, '--ignore-package-exit-codes')
+
+ @provider.uninstall
+ end
+ end
+
+ context "with posh choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(false)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(last_posh_choco_version)
+ end
+
+ it "should call the remove operation" do
+ @provider.expects(:chocolatey).with('uninstall', 'chocolatey', nil)
+
+ @provider.uninstall
+ end
+
+ it "should use source if it is specified" do
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('uninstall','chocolatey', '-source', 'c:\packages', nil)
+
+ @provider.uninstall
+ end
+ end
+ end
+
+ context "when updating" do
+ context "with compiled choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(true)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(first_compiled_choco_version)
+ # unhold is called in installs on compiled choco
+ Puppet::Util::Execution.stubs(:execute)
+ end
+
+ it "should use `chocolatey upgrade` when ensure latest and package present" do
+ provider.stubs(:instances).returns [provider.new({
+ :ensure => "1.2.3",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ })]
+ @provider.expects(:chocolatey).with('upgrade', 'chocolatey', '-y', nil)
+
+ @provider.update
+ end
+
+ it "should call with ignore package exit codes when = 0.9.10" do
+ provider.stubs(:instances).returns [provider.new({
+ :ensure => "1.2.3",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ })]
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(minimum_supported_choco_exit_codes).at_least_once
+ resource[:ensure] = :present
+ @provider.expects(:chocolatey).with('upgrade', 'chocolatey','-y', nil, '--ignore-package-exit-codes')
+
+ @provider.update
+ end
+
+ it "should call with ignore package exit codes when > 0.9.10" do
+ provider.stubs(:instances).returns [provider.new({
+ :ensure => "1.2.3",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ })]
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:choco_version).returns(choco_zero_ten_zero).at_least_once
+ resource[:ensure] = :present
+ @provider.expects(:chocolatey).with('upgrade', 'chocolatey','-y', nil, '--ignore-package-exit-codes')
+
+ @provider.update
+ end
+
+
+ it "should use `chocolatey install` when ensure latest and package absent" do
+ provider.stubs(:instances).returns []
+ @provider.expects(:chocolatey).with('install', 'chocolatey', '-y', nil)
+
+ @provider.update
+ end
+
+ it "should use source if it is specified" do
+ provider.expects(:instances).returns [provider.new({
+ :ensure => "latest",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ })]
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('upgrade','chocolatey', '-y', '-source', 'c:\packages', nil)
+
+ @provider.update
+ end
+ end
+
+ context "with posh choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(false)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(last_posh_choco_version)
+ end
+
+ it "should use `chocolatey update` when ensure latest and package present" do
+ provider.stubs(:instances).returns [provider.new({
+ :ensure => "1.2.3",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ })]
+ @provider.expects(:chocolatey).with('update', 'chocolatey', nil)
+
+ @provider.update
+ end
+
+ it "should use `chocolatey install` when ensure latest and package absent" do
+ provider.stubs(:instances).returns []
+ @provider.expects(:chocolatey).with('install', 'chocolatey', nil)
+
+ @provider.update
+ end
+
+ it "should use source if it is specified" do
+ provider.expects(:instances).returns [provider.new({
+ :ensure => "latest",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ })]
+ resource[:source] = 'c:\packages'
+ @provider.expects(:chocolatey).with('update','chocolatey', '-source', 'c:\packages', nil)
+
+ @provider.update
+ end
+ end
+ end
+
+ context "when getting latest" do
+ context "with compiled choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(true)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(first_compiled_choco_version)
+ end
+
+ it "should use choco.exe arguments" do
+ # we don't care where choco is, we are concerned with the arguments that are passed to choco.
+ #
+ @provider.send(:latestcmd).drop(1).should == ['upgrade', '--noop', 'chocolatey','-r']
+ end
+
+ it "should use source if it is specified" do
+ resource[:source] = 'c:\packages'
+ @provider.send(:latestcmd).drop(1).should == ['upgrade', '--noop', 'chocolatey','-r', '-source', 'c:\packages']
+ #@provider.expects(:chocolatey).with('upgrade', '--noop', 'chocolatey','-r', '-source', 'c:\packages')
+
+ #@provider.latest
+ end
+ end
+
+ context "with posh choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(false)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(last_posh_choco_version)
+ end
+
+ it "should use posh arguments" do
+ @provider.send(:latestcmd).drop(1).should == ['version', 'chocolatey', '| findstr /R "latest" | findstr /V "latestCompare"']
+ end
+
+ it "should use source if it is specified" do
+ resource[:source] = 'c:\packages'
+ @provider.send(:latestcmd).drop(1).should == ['version', 'chocolatey', '-source', 'c:\packages', '| findstr /R "latest" | findstr /V "latestCompare"']
+ #@provider.expects(:chocolatey).with('version', 'chocolatey', '-source', 'c:\packages', '| findstr /R "latest" | findstr /V "latestCompare"')
+
+ #@provider.latest
+ end
+ end
+ end
+
+ context "query" do
+ it "should return a hash when chocolatey and the package are present" do
+ provider.expects(:instances).returns [provider.new({
+ :ensure => "1.2.5",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ })]
+
+ @provider.query.should == {
+ :ensure => "1.2.5",
+ :name => "chocolatey",
+ :provider => :chocolatey,
+ }
+ end
+
+ it "should return nil when the package is missing" do
+ provider.expects(:instances).returns []
+
+ @provider.query.should == nil
+ end
+ end
+
+ context "when fetching a package list" do
+ it "should invoke provider listcmd" do
+ provider.expects(:listcmd)
+
+ provider.instances
+ end
+
+ it "should query chocolatey" do
+ provider.expects(:execpipe).with() do |args|
+ args[1] =~ /list/
+ args[2] =~ /-lo/
+ end
+
+ provider.instances
+ end
+
+ context "self.instances" do
+ it "should return nil on error" do
+ provider.expects(:execpipe).raises(Puppet::ExecutionFailure.new("ERROR!"))
+
+ provider.instances.should be_nil
+ end
+
+ context "with compiled choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(true)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(first_compiled_choco_version)
+ end
+
+ it "should return installed packages with their versions" do
+ provider.expects(:execpipe).yields(StringIO.new(%Q(package1|1.23\n\package2|2.00\n)))
+
+ packages = (provider.instances)
+
+ packages.length.should == 2
+
+ packages[0].properties.should == {
+ :provider => :chocolatey,
+ :ensure => "1.23",
+ :name => 'package1'
+ }
+
+ packages[1].properties.should == {
+ :provider => :chocolatey,
+ :ensure => "2.00",
+ :name => 'package2'
+ }
+ end
+
+ it "should return nil on error" do
+ provider.expects(:execpipe).yields(StringIO.new(%Q(Unable to search for packages when there are no soures enabled for packages and none were passed as arguments.\n)))
+
+ expect {
+ provider.instances
+ }.to raise_error(Puppet::Error, /At least one source must be enabled./)
+ end
+ end
+
+ context "with posh choco client" do
+ before :each do
+ @provider.class.stubs(:is_compiled_choco?).returns(false)
+ PuppetX::Chocolatey::ChocolateyVersion.stubs(:version).returns(last_posh_choco_version)
+ end
+
+ it "should return installed packages with their versions" do
+ provider.expects(:execpipe).yields(StringIO.new(%Q(package1 1.23\n\package2 2.00\n)))
+
+ packages = (provider.instances)
+
+ packages.length.should == 2
+
+ packages[0].properties.should == {
+ :provider => :chocolatey,
+ :ensure => "1.23",
+ :name => 'package1'
+ }
+
+ packages[1].properties.should == {
+ :provider => :chocolatey,
+ :ensure => "2.00",
+ :name => 'package2'
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateyconfig_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateyconfig_spec.rb
new file mode 100644
index 000000000..241d891c5
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateyconfig_spec.rb
@@ -0,0 +1,103 @@
+require 'spec_helper'
+require 'puppet/type/chocolateyconfig'
+
+describe Puppet::Type.type(:chocolateyconfig) do
+ let(:resource) { Puppet::Type.type(:chocolateyconfig).new(:name => "config", :ensure => :absent) }
+ let(:provider) { Puppet::Provider.new(resource) }
+ let(:catalog) { Puppet::Resource::Catalog.new }
+ let (:minimum_supported_version) {'0.9.10.0'}
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:choco_version).returns(minimum_supported_version)
+
+ resource.provider = provider
+ end
+
+ it "should be an instance of Puppet::Type::Chocolateyconfig" do
+ resource.must be_an_instance_of Puppet::Type::Chocolateyconfig
+ end
+
+ it "parameter :name should be the name var" do
+ resource.parameters[:name].isnamevar?.should be_truthy
+ end
+
+ #string values
+ ['name','value'].each do |param|
+ context "parameter :#{param}" do
+ let (:param_symbol) { param.to_sym }
+
+ it "should not allow nil" do
+ expect {
+ resource[param_symbol] = nil
+ }.to raise_error(Puppet::Error, /Got nil value for #{param}/)
+ end
+
+ it "should not allow empty" do
+ expect {
+ resource[param_symbol] = ''
+ }.to raise_error(Puppet::Error, /A non-empty #{param} must/)
+ end
+
+ it "should accept any string value" do
+ resource[param_symbol] = 'value'
+ resource[param_symbol] = "c:/thisstring-location/value/somefile.txt"
+ resource[param_symbol] = "c:\\thisstring-location\\value\\somefile.txt"
+ end
+ end
+ end
+
+ context "param :ensure" do
+ it "should accept 'present'" do
+ resource[:ensure] = 'present'
+ end
+
+ it "should accept present" do
+ resource[:ensure] = :present
+ end
+
+ it "should accept absent" do
+ resource[:ensure] = :absent
+ end
+
+ it "should reject any other value" do
+ expect {
+ resource[:ensure] = :whenever
+ }.to raise_error(Puppet::Error, /Invalid value :whenever. Valid values are/)
+ end
+ end
+
+ it "should autorequire Exec[install_chocolatey_official] when in the catalog" do
+ exec = Puppet::Type.type(:exec).new(:name => "install_chocolatey_official", :path => "nope")
+ catalog.add_resource resource
+ catalog.add_resource exec
+
+ reqs = resource.autorequire
+ reqs.count.must == 1
+ reqs[0].source.must == exec
+ reqs[0].target.must == resource
+ end
+
+ context ".validate" do
+ it "should pass when ensure => absent with no value" do
+ resource[:ensure] = :absent
+
+ resource.validate
+ end
+
+ it "should pass when ensure => present with a value" do
+ resource[:ensure] = :present
+ resource[:value] = 'yo'
+
+ resource.validate
+ end
+
+ it "should fail when ensure => present with no value" do
+ resource[:ensure] = :present
+
+ expect {
+ resource.validate
+ }.to raise_error(ArgumentError, /Unless ensure => absent, value is required/)
+ end
+ end
+
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateyfeature_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateyfeature_spec.rb
new file mode 100644
index 000000000..caac211e3
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateyfeature_spec.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+require 'puppet/type/chocolateyfeature'
+
+describe Puppet::Type.type(:chocolateyfeature) do
+ let(:resource) { Puppet::Type.type(:chocolateyfeature).new(:name => "chocolateyfeature", :ensure => "enabled" ) }
+ let(:provider) { Puppet::Provider.new(resource) }
+ let(:catalog) { Puppet::Resource::Catalog.new }
+ let (:minimum_supported_version) {'0.9.9.0'}
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:choco_version).returns(minimum_supported_version)
+
+ resource.provider = provider
+ resource[:ensure] = 'enabled'
+ end
+
+ it "should be an instance of Puppet::Type::Chocolateyfeature" do
+ resource.must be_an_instance_of Puppet::Type::Chocolateyfeature
+ end
+
+ it "parameter :name should be the name var" do
+ resource.parameters[:name].isnamevar?.should be_truthy
+ end
+
+ context "parameter :name" do
+ let (:param_symbol) { :name }
+
+ it "should accept any string value" do
+ resource[param_symbol] = 'value'
+ resource[param_symbol] = "c:/thisstring-location/value/somefile.txt"
+ resource[param_symbol] = "c:\\thisstring-location\\value\\somefile.txt"
+ end
+ end
+
+ context "param :ensure" do
+ it "should accept 'enabled'" do
+ resource[:ensure] = 'enabled'
+ end
+
+ it "should accept enabled" do
+ resource[:ensure] = :enabled
+ end
+
+ it "should accept 'disabled'" do
+ resource[:ensure] = 'disabled'
+ end
+
+ it "should accept :disabled" do
+ resource[:ensure] = :disabled
+ end
+
+ it "should reject any other value" do
+ expect {
+ resource[:ensure] = :whenever
+ }.to raise_error(Puppet::Error, /Invalid value :whenever. Valid values are/)
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateysource_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateysource_spec.rb
new file mode 100644
index 000000000..8f38df958
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet/type/chocolateysource_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+require 'puppet/type/chocolateysource'
+
+describe Puppet::Type.type(:chocolateysource) do
+ let(:resource) { Puppet::Type.type(:chocolateysource).new(:name => 'source', :location => 'c:\packages') }
+ let(:provider) { Puppet::Provider.new(resource) }
+ let(:catalog) { Puppet::Resource::Catalog.new }
+ let (:minimum_supported_version) {'0.9.9.0'}
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:choco_version).returns(minimum_supported_version)
+
+ resource.provider = provider
+ end
+
+ it "should be an instance of Puppet::Type::Chocolateysource" do
+ resource.must be_an_instance_of Puppet::Type::Chocolateysource
+ end
+
+ it "parameter :name should be the name var" do
+ resource.parameters[:name].isnamevar?.should be_truthy
+ end
+
+ #string values
+ ['name','location','user','password'].each do |param|
+ context "parameter :#{param}" do
+ let (:param_symbol) { param.to_sym }
+
+ it "should accept any string value" do
+ resource[param_symbol] = 'value'
+ resource[param_symbol] = "c:/thisstring-location/value/somefile.txt"
+ resource[param_symbol] = "c:\\thisstring-location\\value\\somefile.txt"
+ end
+ end
+ end
+
+ #numeric values
+ ['priority'].each do |param|
+ context "parameter :#{param}" do
+ let (:param_symbol) { param.to_sym }
+
+ it "should accept any numeric value" do
+ resource[param_symbol] = 0
+ resource[param_symbol] = 10
+ end
+
+ it "should accept any string that represents a numeric value" do
+ resource[param_symbol] = '1'
+ resource[param_symbol] = '0'
+ end
+
+ it "should not accept other string values" do
+ expect {
+ resource[param_symbol] = 'value'
+ }.to raise_error(Puppet::Error, /An integer is necessary for #{param}/)
+ end
+
+ it "should not accept symbol values" do
+ expect {
+ resource[param_symbol] = :whenever
+ }.to raise_error(Puppet::Error, /An integer is necessary for #{param}/)
+ end
+ end
+ end
+
+ context "param :ensure" do
+ it "should accept 'present'" do
+ resource[:ensure] = 'present'
+ end
+
+ it "should accept present" do
+ resource[:ensure] = :present
+ end
+
+ it "should accept :disabled" do
+ resource[:ensure] = :disabled
+ end
+
+ it "should accept absent" do
+ resource[:ensure] = :absent
+ end
+
+ it "should reject any other value" do
+ expect {
+ resource[:ensure] = :whenever
+ }.to raise_error(Puppet::Error, /Invalid value :whenever. Valid values are/)
+ end
+ end
+
+ it "should autorequire Exec[install_chocolatey_official] when in the catalog" do
+ exec = Puppet::Type.type(:exec).new(:name => "install_chocolatey_official", :path => "nope")
+ catalog.add_resource resource
+ catalog.add_resource exec
+
+ reqs = resource.autorequire
+ reqs.count.must == 1
+ reqs[0].source.must == exec
+ reqs[0].target.must == resource
+ end
+
+ context ".validate" do
+ it "should pass when both user/password are empty" do
+ resource.validate
+ end
+
+ it "should pass when both user/password have a value" do
+ resource[:user] = 'tim'
+ resource[:password] = 'tim'
+
+ resource.validate
+ end
+
+ it "should fail when user has a value but password does not" do
+ resource[:user] = 'tim'
+
+ expect {
+ resource.validate
+ }.to raise_error(ArgumentError, /you must specify both values/)
+ end
+
+ it "should fail when password has a value but user does not" do
+ resource[:password] = 'tim'
+
+ expect {
+ resource.validate
+ }.to raise_error(ArgumentError, /you must specify both values/)
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_common_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_common_spec.rb
new file mode 100644
index 000000000..9e0aece4a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_common_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+require 'puppet_x/chocolatey/chocolatey_install'
+require 'puppet_x/chocolatey/chocolatey_common'
+
+describe 'Chocolatey Common' do
+
+ let (:first_compiled_choco_version) {'0.9.9.0'}
+ let (:newer_choco_version) {'0.9.10.0'}
+ let (:last_posh_choco_version) {'0.9.8.33'}
+
+ before :each do
+ PuppetX::Chocolatey::ChocolateyCommon.stubs(:set_env_chocolateyinstall)
+ end
+
+ context ".chocolatey_command" do
+ it "should find chocolatey install location based on PuppetX::Chocolatey::ChocolateyInstall", :if => Puppet.features.microsoft_windows? do
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with('c:\dude\bin\choco.exe').returns(true)
+
+ PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command.should == 'c:\dude\bin\choco.exe'
+ end
+
+ it "should find chocolatey install location based on default location", :if => Puppet.features.microsoft_windows? do
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns('c:\dude')
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with('c:\dude\bin\choco.exe').returns(false)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with('C:\ProgramData\chocolatey\bin\choco.exe').returns(false)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with('C:\Chocolatey\bin\choco.exe').returns(false)
+
+ PuppetX::Chocolatey::ChocolateyCommon.chocolatey_command.should == "#{ENV['ALLUSERSPROFILE']}\\chocolatey\\bin\\choco.exe"
+ end
+ end
+
+ context ".choco_version" do
+ it "should return PuppetX::Chocolatey::ChocolateyVersion.version" do
+ expected = '0.9.9.0.1'
+ PuppetX::Chocolatey::ChocolateyVersion.expects(:version).returns(expected)
+ PuppetX::Chocolatey::ChocolateyCommon.clear_cached_values
+
+ PuppetX::Chocolatey::ChocolateyCommon.choco_version.must eq expected
+ end
+ end
+
+ context ".choco_config_file" do
+ let (:choco_install_loc) { 'c:\dude' }
+
+ it "should return the normal config file location when found" do
+ expected = 'c:\dude\config\chocolatey.config'
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns(choco_install_loc)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(expected).returns(true)
+
+ PuppetX::Chocolatey::ChocolateyCommon.choco_config_file.must eq expected
+ end
+
+ it "should return the old config file location for older installs" do
+ expected = 'c:\dude\chocolateyinstall\chocolatey.config'
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns(choco_install_loc)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with('c:\dude\config\chocolatey.config').returns(false)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with(expected).returns(true)
+
+ PuppetX::Chocolatey::ChocolateyCommon.choco_config_file.must eq expected
+ end
+
+ it "should return nil when the config cannot be found" do
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns(choco_install_loc)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with('c:\dude\config\chocolatey.config').returns(false)
+ PuppetX::Chocolatey::ChocolateyCommon.expects(:file_exists?).with('c:\dude\chocolateyinstall\chocolatey.config').returns(false)
+
+ PuppetX::Chocolatey::ChocolateyCommon.choco_config_file.must be_nil
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_install_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_install_spec.rb
new file mode 100644
index 000000000..171438308
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_install_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+require 'puppet_x/chocolatey/chocolatey_install'
+
+describe 'Chocolatey Install Location' do
+
+ context 'on Windows', :if => Puppet::Util::Platform.windows? do
+
+ it "should return install path from registry if it exists" do
+ expected_value = 'C:\somewhere'
+ Win32::Registry.any_instance.expects(:[]).with('ChocolateyInstall').returns(expected_value)
+
+ PuppetX::Chocolatey::ChocolateyInstall.install_path.must == expected_value
+ end
+
+ it "should return the environment variable ChocolateyInstall if it exists" do
+ Win32::Registry.any_instance.expects(:[]).with('ChocolateyInstall').raises(Win32::Registry::Error.new(2), 'file not found yo')
+
+ # this is a placeholder, it is already set in spec_helper
+ ENV['ChocolateyInstall'] = 'c:\blah'
+
+ PuppetX::Chocolatey::ChocolateyInstall.install_path.must == 'c:\blah'
+ end
+
+ it "should return nil if the environment variable does not exist" do
+ Win32::Registry.any_instance.expects(:[]).with('ChocolateyInstall').raises(Win32::Registry::Error.new(2), 'file not found yo')
+ ENV['ChocolateyInstall'] = nil
+
+ PuppetX::Chocolatey::ChocolateyInstall.install_path.must be_nil
+ end
+ end
+
+ context 'on Linux', :if => Puppet.features.posix? do
+ it "should return the environment variable ChocolateyInstall if it exists" do
+ # this is a placeholder, it is already set in spec_helper
+ ENV['ChocolateyInstall'] = 'c:\blah'
+
+ PuppetX::Chocolatey::ChocolateyInstall.install_path.must == 'c:\blah'
+ end
+
+ it "should return nil if the ChocolateyInstall variable does not exist" do
+ ENV['ChocolateyInstall'] = nil
+
+ PuppetX::Chocolatey::ChocolateyInstall.install_path.must be_nil
+ end
+ end
+
+ after :each do
+ # setting the values back
+ ENV['ChocolateyInstall'] = 'c:\blah'
+ end
+
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_version_spec.rb b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_version_spec.rb
new file mode 100644
index 000000000..57e5618c7
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/spec/unit/puppet_x/chocolatey/chocolatey_version_spec.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+require 'puppet_x/chocolatey/chocolatey_version'
+
+describe 'Chocolatey Version' do
+
+ context 'on Windows', :if => Puppet::Util::Platform.windows? do
+
+ context "when Chocolatey is installed" do
+ before :each do
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns('c:\dude')
+ File.expects(:exist?).with('c:\dude\bin\choco.exe').returns(true)
+ end
+
+ it "should return the value from running choco -v" do
+ expected_value = '1.2.3'
+ Puppet::Util::Execution.expects(:execute).returns(expected_value)
+
+ PuppetX::Chocolatey::ChocolateyVersion.version.must == expected_value
+ end
+
+ it "should handle cleaning up spaces" do
+ expected_value = '1.2.3'
+ Puppet::Util::Execution.expects(:execute).returns(' ' + expected_value + ' ')
+
+ PuppetX::Chocolatey::ChocolateyVersion.version.must == expected_value
+ end
+
+ it "should handle older versions of choco" do
+ expected_value = '1.2.3'
+ Puppet::Util::Execution.expects(:execute).returns('Please run chocolatey /? or chocolatey help - chocolatey v' + expected_value)
+
+ PuppetX::Chocolatey::ChocolateyVersion.version.must == expected_value
+ end
+
+ it "should handle other messages that return with version call" do
+ expected_value = '1.2.3'
+ Puppet::Util::Execution.expects(:execute).returns("Error setting some value.\nPlease set this value yourself\r\nsound good?\r" + expected_value)
+
+ PuppetX::Chocolatey::ChocolateyVersion.version.must == expected_value
+ end
+
+ it "should handle a trailing line break" do
+ expected_value = '1.2.3'
+ Puppet::Util::Execution.expects(:execute).returns(expected_value + "\r\n")
+
+ PuppetX::Chocolatey::ChocolateyVersion.version.must == expected_value
+ end
+
+ it "should handle 0.9.8.33 of choco" do
+ expected_value = '1.2.3'
+ Puppet::Util::Execution.expects(:execute).returns('!!ATTENTION!!
+The next version of Chocolatey (v0.9.9) will require -y to perform
+ behaviors that change state without prompting for confirmation. Start
+ using it now in your automated scripts.
+
+ For details on the all new Chocolatey, visit http://bit.ly/new_choco
+Please run chocolatey /? or chocolatey help - chocolatey v' + expected_value)
+
+ PuppetX::Chocolatey::ChocolateyVersion.version.must == expected_value
+ end
+ end
+
+ context "When Chocolatey is not installed" do
+ before :each do
+ PuppetX::Chocolatey::ChocolateyInstall.expects(:install_path).returns(nil)
+ File.expects(:exist?).with('\bin\choco.exe').returns(false)
+ end
+
+ it "should return nil" do
+ PuppetX::Chocolatey::ChocolateyVersion.version.must be_nil
+ end
+ end
+
+ end
+
+ context 'on Linux', :if => Puppet.features.posix? do
+ it "should return nil on a non-windows system" do
+ PuppetX::Chocolatey::ChocolateyVersion.version.must be_nil
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/templates/InstallChocolatey.ps1.erb b/modules/utilities/windows/repository_managers/chocolatey/templates/InstallChocolatey.ps1.erb
new file mode 100644
index 000000000..5a2bff645
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/templates/InstallChocolatey.ps1.erb
@@ -0,0 +1,151 @@
+# ==============================================================================
+# Copyright 2011 - Present RealDimensions Software, LLC
+#
+# 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.
+# ==============================================================================
+
+$ErrorActionPreference = 'Stop'
+
+# For some reason try/catch wrapping only ensures
+# that none of this script runs at all
+# https://tickets.puppetlabs.com/browse/MODULES-2634
+#try {
+
+# variables
+$url = '<%= @download_url %>'
+$unzipMethod = '<%= @unzip_type %>'
+if ($env:TEMP -eq $null) {
+ $env:TEMP = Join-Path $env:SystemDrive 'temp'
+}
+$chocTempDir = Join-Path $env:TEMP "chocolatey"
+$tempDir = Join-Path $chocTempDir "chocInstall"
+if (![System.IO.Directory]::Exists($tempDir)) {[System.IO.Directory]::CreateDirectory($tempDir)}
+$file = Join-Path $tempDir "chocolatey.zip"
+$chocErrorLog = Join-Path $tempDir "chocError.log"
+
+# PowerShell v2/3 caches the output stream. Then it throws errors due
+# to the FileStream not being what is expected. Fixes "The OS handle's
+# position is not what FileStream expected. Do not use a handle
+# simultaneously in one FileStream and in Win32 code or another
+# FileStream."
+
+# This only works with the ConsoleHost (PowerShell InternalHost)
+function Fix-PowerShellOutputRedirectionBug {
+ try{
+ # http://www.leeholmes.com/blog/2008/07/30/workaround-the-os-handles-position-is-not-what-filestream-expected/ plus comments
+ $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
+ $objectRef = $host.GetType().GetField("externalHostRef", $bindingFlags).GetValue($host)
+ $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty"
+ $consoleHost = $objectRef.GetType().GetProperty("Value", $bindingFlags).GetValue($objectRef, @())
+ [void] $consoleHost.GetType().GetProperty("IsStandardOutputRedirected", $bindingFlags).GetValue($consoleHost, @())
+ $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField"
+ $field = $consoleHost.GetType().GetField("standardOutputWriter", $bindingFlags)
+ $field.SetValue($consoleHost, [Console]::Out)
+ [void] $consoleHost.GetType().GetProperty("IsStandardErrorRedirected", $bindingFlags).GetValue($consoleHost, @())
+ $field2 = $consoleHost.GetType().GetField("standardErrorWriter", $bindingFlags)
+ $field2.SetValue($consoleHost, [Console]::Error)
+ } catch {
+ Write-Output "Unable to apply redirection fix. Error: $_"
+ }
+}
+
+Fix-PowerShellOutputRedirectionBug
+
+# This should help when certain organizations have issues installing Chocolatey
+# Attempt to set highest encryption available for SecurityProtocol.
+# PowerShell will not set this by default (until maybe .NET 4.6.x). This
+# will typically produce a message for PowerShell v2 (just an info
+# message though)
+try {
+ # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48)
+ # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't
+ # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is
+ # installed (.NET 4.5 is an in-place upgrade).
+ [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48
+} catch {
+ Write-Output "Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to do one or more of the following: (1) upgrade to .NET Framework 4.5 and PowerShell v3 and/or (2) specify internal Chocolatey package location (see https://forge.puppet.com/puppetlabs/chocolatey#manage-chocolatey-installation)."
+}
+
+function Download-File {
+param (
+ [string]$url,
+ [string]$file
+ )
+ Write-Output "Downloading $url to $file"
+ $downloader = new-object System.Net.WebClient
+ $downloader.Proxy.Credentials=[System.Net.CredentialCache]::DefaultNetworkCredentials;
+ $downloader.DownloadFile($url, $file)
+}
+
+# download the package
+Download-File $url $file
+
+if ($unzipMethod -eq '7zip') {
+ # download 7zip
+ Write-Output "Download 7Zip commandline tool"
+ $7zaExe = Join-Path $tempDir '7za.exe'
+
+ Download-File 'https://chocolatey.org/7za.exe' "$7zaExe"
+
+ # unzip the package
+ Write-Output "Extracting $file to $tempDir..."
+ Start-Process "$7zaExe" -ArgumentList "x -o`"$tempDir`" -y `"$file`"" -Wait -NoNewWindow
+} else {
+ if ($PSVersionTable.PSVersion.Major -lt 5) {
+ $shellApplication = new-object -com shell.application
+ $zipPackage = $shellApplication.NameSpace($file)
+ $destinationFolder = $shellApplication.NameSpace($tempDir)
+ $destinationFolder.CopyHere($zipPackage.Items(),0x10)
+ } else {
+ Expand-Archive -Path "$file" -DestinationPath "$tempDir" -Force | Out-Null
+ }
+}
+
+# call chocolatey install
+Write-Output "Installing chocolatey on this machine"
+$toolsFolder = Join-Path $tempDir "tools"
+$chocInstallPS1 = Join-Path $toolsFolder "chocolateyInstall.ps1"
+
+if ($PSVersionTable.PSVersion.Major -gt 2) {
+ & $chocInstallPS1
+} else {
+ $output = Invoke-Expression $chocInstallPS1
+ $output
+ Write-Output "Any errors that occured during install or upgrade are logged here: $chocoErrorLog"
+ $error | out-file $chocErrorLog
+}
+
+Write-Output 'Ensuring chocolatey commands are on the path'
+$chocInstallVariableName = "ChocolateyInstall"
+$chocoPath = [Environment]::GetEnvironmentVariable($chocInstallVariableName, [System.EnvironmentVariableTarget]::User)
+if ($chocoPath -eq $null -or $chocoPath -eq '') {
+ $chocoPath = 'C:\ProgramData\Chocolatey'
+}
+
+$chocoBinPath = Join-Path $chocoPath 'bin'
+
+if ($($env:Path).ToLower().Contains($($chocoBinPath).ToLower()) -eq $false) {
+ $env:Path = [Environment]::GetEnvironmentVariable('Path',[System.EnvironmentVariableTarget]::Machine);
+}
+
+Write-Output 'Ensuring chocolatey.nupkg is in the lib folder'
+$chocoPkgDir = Join-Path $chocoPath 'lib\chocolatey'
+$nupkg = Join-Path $chocoPkgDir 'chocolatey.nupkg'
+if (![System.IO.Directory]::Exists($chocoPkgDir)) { [System.IO.Directory]::CreateDirectory($chocoPkgDir); }
+Copy-Item "$file" "$nupkg" -Force -ErrorAction SilentlyContinue
+
+#}
+#catch
+#{
+# Write-Host "$($_.Exception.Message)"
+# exit 1
+#}
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/00_pe_install.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/00_pe_install.rb
new file mode 100644
index 000000000..40bcd4bea
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/00_pe_install.rb
@@ -0,0 +1,20 @@
+require 'master_manipulator'
+test_name 'MODULES-3138 - C48 - Install Puppet Enterprise'
+
+# Check for a master before continuing
+if master == nil
+ fail_test("Master is not set, are you using a host configuration that has a master?")
+end
+
+# Init
+step 'Install PE'
+install_pe
+
+step 'Disable Node Classifier'
+disable_node_classifier(master)
+
+step 'Disable Environment Caching'
+disable_env_cache(master)
+
+step 'Restart Puppet Server'
+restart_puppet_server(master)
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/01_chocolatey_module.install.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/01_chocolatey_module.install.rb
new file mode 100644
index 000000000..efccc025b
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/01_chocolatey_module.install.rb
@@ -0,0 +1,25 @@
+test_name 'MODULES-3138 - C97814 - Install Pre-suite Acceptance Test'
+
+# Beaker option set if "BEAKER_FORGE_HOST" environment variable is present
+staging = { :module_name => 'puppetlabs-chocolatey' }
+if options[:forge_host]
+ # Check to see if module version is specified.
+ staging[:version] = ENV['MODULE_VERSION'] if ENV['MODULE_VERSION']
+ step 'Install Chocolatey Module from Forge'
+ install_dev_puppet_module_on(master, staging)
+else
+ step 'Install Chocolatey Module Dependencies'
+ %w(puppetlabs-stdlib puppetlabs-powershell badgerious/windows_env).each do |dep|
+ on(master, puppet("module install #{dep}"))
+ end
+end
+
+step 'Install Chocolatey Module'
+proj_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../'))
+local = { :module_name => 'chocolatey', :source => proj_root}
+
+# Check to see if module version is specified.
+staging[:version] = ENV['MODULE_VERSION'] if ENV['MODULE_VERSION']
+
+# in CI install from staging forge, otherwise from local
+install_dev_puppet_module_on(master, local)
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/02_chocolatey_application_install.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/02_chocolatey_application_install.rb
new file mode 100644
index 000000000..782d130ee
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/pre-suite/02_chocolatey_application_install.rb
@@ -0,0 +1,47 @@
+require 'master_manipulator'
+require 'chocolatey_helper'
+test_name 'MODULES-3043 - C97739 - Install Client on Virgin System'
+
+chocolatey_pp = < 'file:///C:/chocolatey.nupkg',
+ use_7zip => false,
+ }
+MANIFEST
+
+chocoVersion = /[0-9]+[\d'.']*/
+
+# Setup
+step 'Inject "site.pp" on Master'
+site_pp = create_site_pp(master, :manifest => chocolatey_pp)
+inject_site_pp(master, get_site_pp_path(master), site_pp)
+
+#Test
+confine_block(:to, :platform => 'windows') do
+
+ agents.each do |agent|
+ opts = {
+ :acceptable_exit_codes => [0, 2]
+ }
+
+ url = get_latest_chocholatey_download_url
+
+ step 'Download chocolatey nuget package' do
+ curl_on(agent, "#{url} > C:/chocolatey.nupkg")
+ end
+
+ step 'should apply chocolatey manifest and install choco.exe' do
+ on(agent, puppet('agent -t --environment production'), opts) do |result|
+ assert_no_match(/Error:/, result.stderr, 'Unexpected error was detected!')
+ end
+ end
+
+ step 'should have valid version of Chocolatey' do
+ on(agent, 'C:/ProgramData/chocolatey/bin/choco.exe -v', :acceptable_exit_codes => 0) do |result|
+ assert_match(chocoVersion, result.stdout, 'Expected: ' + chocoVersion.to_s + ' but got ' + result.stdout)
+ end
+ end
+
+ end
+
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/tests/hello.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/tests/hello.rb
new file mode 100644
index 000000000..682072efb
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/acceptance/tests/hello.rb
@@ -0,0 +1,7 @@
+test_name "Hello Test"
+
+step "Say Hello"
+
+hosts.each do |host|
+ on(host, "echo hello!")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/configs/.gitignore b/modules/utilities/windows/repository_managers/chocolatey/tests/configs/.gitignore
new file mode 100644
index 000000000..f67aa100f
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/configs/.gitignore
@@ -0,0 +1,3 @@
+# Ignore all configs in this directory, they will be created by beaker-hostgenerator
+*
+!.gitignore
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/lib/chocolatey_helper.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/lib/chocolatey_helper.rb
new file mode 100644
index 000000000..742de3fcd
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/lib/chocolatey_helper.rb
@@ -0,0 +1,50 @@
+require 'net/http'
+require 'uri'
+require 'nokogiri'
+
+$chocolatey_latest_info_url = "http://nexus.delivery.puppetlabs.net/service/local/nuget/choco-pipeline-tests/Packages()?$filter=((Id%20eq%20%27chocolatey%27)%20and%20(not%20IsPrerelease))%20and%20IsLatestVersion"
+
+# Extract the url for the latest Puppet hosted version of Chocolatey
+#
+# ==== Returns
+#
+# +string+ - url from the feed/content->src of the $chocolatey_latest_info_url
+#
+# ==== Raises
+#
+# URI::InvalidURIError
+#
+# ==== Examples
+#
+# url = get_latest_chocholatey_download_url;
+
+def get_latest_chocholatey_download_url()
+ uri = URI.parse($chocolatey_latest_info_url)
+
+ response = Net::HTTP.get_response(uri)
+ xml_str = Nokogiri::XML(response.body)
+
+ src_url = xml_str.css('//feed//content').attr('src')
+
+ return src_url
+end
+
+def config_file_location
+ 'c:\\ProgramData\\chocolatey\\config\\chocolatey.config'
+end
+
+def backup_config
+ step 'Backup default configuration file'
+ on(agents, "cmd.exe /c \"copy #{config_file_location} #{config_file_location}.bkp\"")
+end
+
+def reset_config
+ step 'Reset configuration file to default'
+ on(agents, "cmd.exe /c \"move #{config_file_location}.bkp #{config_file_location}\"")
+end
+
+def get_xml_value(xpath, file_text)
+ doc = Nokogiri::XML(file_text)
+
+ doc.xpath(xpath)
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/00_install_certs.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/00_install_certs.rb
new file mode 100644
index 000000000..bdcf8b99a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/00_install_certs.rb
@@ -0,0 +1,93 @@
+test_name "Install CA Certs"
+confine(:to, :platform => 'windows')
+
+GEOTRUST_GLOBAL_CA = <<-EOM
+-----BEGIN CERTIFICATE-----
+MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
+MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
+YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
+EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
+R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
+9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
+fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
+iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
+1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
+bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
+MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
+ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
+uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
+Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
+tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
+PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
+hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
+5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
+-----END CERTIFICATE-----
+EOM
+
+USERTRUST_NETWORK_CA = <<-EOM
+-----BEGIN CERTIFICATE-----
+MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
+lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
+Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
+dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
+SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
+A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
+MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
+d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
+cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
+0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
+M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
+MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
+oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
+DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
+oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
+VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
+dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
+bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
+BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
+//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
+CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
+CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
+3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
+KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
+-----END CERTIFICATE-----
+EOM
+
+EQUIFAX_CA = <<-EOM
+-----BEGIN CERTIFICATE-----
+MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
+UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
+dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
+MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
+dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
+AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
+BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
+cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
+AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
+MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
+aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
+ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
+IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
+MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
+A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
+7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
+1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
+-----END CERTIFICATE-----
+EOM
+
+hosts.each do |host|
+ step "Installing Geotrust CA cert"
+ create_remote_file(host, "geotrustglobal.pem", GEOTRUST_GLOBAL_CA)
+ on host, "chmod 644 geotrustglobal.pem"
+ on host, "cmd /c certutil -v -addstore Root `cygpath -w geotrustglobal.pem`"
+
+ step "Installing Usertrust Network CA cert"
+ create_remote_file(host, "usertrust-network.pem", USERTRUST_NETWORK_CA)
+ on host, "chmod 644 usertrust-network.pem"
+ on host, "cmd /c certutil -v -addstore Root `cygpath -w usertrust-network.pem`"
+
+ step "Installing Equifax CA cert"
+ create_remote_file(host, "equifax.pem", EQUIFAX_CA)
+ on host, "chmod 644 equifax.pem"
+ on host, "cmd /c certutil -v -addstore Root `cygpath -w equifax.pem`"
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/01_puppet_agent_install.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/01_puppet_agent_install.rb
new file mode 100644
index 000000000..774ff4541
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/01_puppet_agent_install.rb
@@ -0,0 +1,13 @@
+test_name 'Install Puppet Agent'
+
+confine(:to, :platform => 'windows')
+
+step 'Install Puppet Agent'
+if ENV['BEAKER_PUPPET_AGENT_VERSION']
+ install_puppet_agent_on(agents, :version => ENV['BEAKER_PUPPET_AGENT_VERSION'])
+else
+ install_puppet_agent_on(agents)
+end
+
+step 'Prevent Puppet Service from Running'
+on(agents, puppet('resource service puppet ensure=stopped enable=false'))
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/02_chocolatey_module_install.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/02_chocolatey_module_install.rb
new file mode 100644
index 000000000..ffe350b2e
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/02_chocolatey_module_install.rb
@@ -0,0 +1,27 @@
+test_name 'MODULES-3138 - C97813 - Install Pre-suite Reference Test'
+
+confine(:to, :platform => 'windows')
+
+# Init
+proj_root = File.expand_path(File.join(File.dirname(__FILE__), '../../../'))
+
+staging = { :module_name => 'puppetlabs-chocolatey' }
+local = { :module_name => 'chocolatey', :source => proj_root }
+
+# Beaker option set if "BEAKER_FORGE_HOST" environment variable is present
+agents.each do |agent|
+ if options[:forge_host]
+ # Check to see if module version is specified.
+ staging[:version] = ENV['MODULE_VERSION'] if ENV['MODULE_VERSION']
+ step 'Install Chocolatey Module from Forge'
+ install_dev_puppet_module_on(agent, staging)
+ else
+ step 'Install Chocolatey Module Dependencies'
+ %w(puppetlabs-stdlib puppetlabs-powershell badgerious/windows_env).each do |dep|
+ on(agent, puppet("module install #{dep}"))
+ end
+ step 'Install Chocolatey Module from Local Source'
+ # in CI install from staging forge, otherwise from local
+ install_dev_puppet_module_on(agent, local)
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/03_chocolatey_application_install.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/03_chocolatey_application_install.rb
new file mode 100644
index 000000000..9ca358d80
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/pre-suite/03_chocolatey_application_install.rb
@@ -0,0 +1,39 @@
+require 'chocolatey_helper'
+
+test_name 'MODULES-3043 - C97739 - Install Client on Virgin System'
+
+confine(:to, :platform => 'windows')
+
+chocolatey_pp = < 'file:///C:/chocolatey.nupkg',
+ use_7zip => false,
+ }
+MANIFEST
+
+
+chocoVersion = /[0-9]+[\d'.']*/
+
+agents.each do |agent|
+ opts = {
+ :acceptable_exit_codes => [0, 2]
+ }
+
+ url = get_latest_chocholatey_download_url;
+
+ step 'Download chocolatey nuget package' do
+ curl_on(agent, "#{url} > C:/chocolatey.nupkg")
+ end
+
+ step 'should apply chocolatey manifest and install choco.exe' do
+ apply_manifest_on(agent, chocolatey_pp, opts) do |result|
+ assert_no_match(/Error:/, result.stderr, 'Unexpected error was detected!')
+ end
+ end
+
+ step 'should have valid version of Chocolatey' do
+ on(agent, 'C:/ProgramData/chocolatey/bin/choco.exe -v', :acceptable_exit_codes => 0) do |result|
+ assert_match(chocoVersion, result.stdout, 'Expected: ' + chocoVersion.to_s + ' but got ' + result.stdout)
+ end
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/add_new_config_item.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/add_new_config_item.rb
new file mode 100644
index 000000000..a4446b4d9
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/add_new_config_item.rb
@@ -0,0 +1,29 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Add New Config Item'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'hello123':
+ ensure => present,
+ value => 'this guy',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/this guy/, get_xml_value("//config/add[@key='hello123']/@value", result.output).to_s, 'Value did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/add_value_to_existing_config.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/add_value_to_existing_config.rb
new file mode 100644
index 000000000..4e707d99c
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/add_value_to_existing_config.rb
@@ -0,0 +1,29 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Add a Value to an Existing Config Setting'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'proxy':
+ ensure => present,
+ value => 'https://somewhere',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/https\:\/\/somewhere/, get_xml_value("//config/add[@key='proxy']/@value", result.output).to_s, 'Value did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/change_config_value.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/change_config_value.rb
new file mode 100644
index 000000000..f08b34305
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/change_config_value.rb
@@ -0,0 +1,46 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Config Settings Change Config Value'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'proxyUser':
+ value => 'bob',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest to setup'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify setup'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/bob/, get_xml_value("//config/add[@key='proxyUser']/@value", result.output).to_s, 'Value did not match')
+ end
+end
+
+# arrange
+chocolatey_src_change = <<-PP
+ chocolateyconfig {'proxyuser':
+ value => 'tim',
+ }
+PP
+
+# act
+step 'Apply manifest to change config setting'
+apply_manifest(chocolatey_src_change, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/tim/, get_xml_value("//config/add[@key='proxyUser']/@value", result.output).to_s, 'Value did not change')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/ensure_config_value_with_password_in_name.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/ensure_config_value_with_password_in_name.rb
new file mode 100644
index 000000000..e9dbd294a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/ensure_config_value_with_password_in_name.rb
@@ -0,0 +1,50 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Ensure Config Value with Password In Name'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'proxypassword':
+ value => 'secrect',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+password = ''
+
+# act
+step 'Apply manifest to setup proxyPassword'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify setup'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ password = get_xml_value("//config/add[@key='proxyPassword']/@value", result.output).to_s
+ assert_match(/.+/, password, 'Value did not match')
+ end
+end
+
+# arrange
+chocolatey_src_change = <<-PP
+ chocolateyconfig {'proxypassword':
+ value => 'secrect2',
+ }
+PP
+
+# act
+step 'Apply manifest to attempt to change proxyPassword - should have no effect'
+apply_manifest(chocolatey_src_change, :catch_failures => true)
+
+step 'Verify results'
+# should have no effect
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(password, get_xml_value("//config/add[@key='proxyPassword']/@value", result.output).to_s, 'Value should not have changed')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/fail_to_appy_bad_manifest.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/fail_to_appy_bad_manifest.rb
new file mode 100644
index 000000000..a11080a10
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/fail_to_appy_bad_manifest.rb
@@ -0,0 +1,25 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Fail to Apply Bad Manifest'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'bob':
+ ensure => sad,
+ value => 'yes',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply Manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify Failure'
+ assert_match(/Error: Parameter ensure failed on Chocolateyconfig\[bob\]: Invalid value "sad"/, stderr, "stderr did not match expected")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/fail_to_set_present_without_value.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/fail_to_set_present_without_value.rb
new file mode 100644
index 000000000..6d65dc5fa
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/fail_to_set_present_without_value.rb
@@ -0,0 +1,24 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Fail to Set Present With No Value'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'bob':
+ ensure => present,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply Manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify Failure'
+ assert_match(/Error: Validation of Chocolateyconfig\[bob\] failed: Unless ensure => absent, value is required/, stderr, "stderr did not match expected")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/remove_config_value_with_password_in_name.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/remove_config_value_with_password_in_name.rb
new file mode 100644
index 000000000..84b1f35a6
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/remove_config_value_with_password_in_name.rb
@@ -0,0 +1,50 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Config Settings Remove Value with Password in Name'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'proxypassword':
+ value => 'secrect',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+password = ''
+
+# act
+step 'Apply manifest to setup proxyPassword'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify setup'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ password = get_xml_value("//config/add[@key='proxyPassword']/@value", result.output).to_s
+ assert_match(/.+/, password, 'Value did not match')
+ end
+end
+
+# arrange
+chocolatey_src_change = <<-PP
+ chocolateyconfig {'proxypassword':
+ ensure => absent,
+ }
+PP
+
+# act
+step 'Apply manifest to remove proxyPassword'
+apply_manifest(chocolatey_src_change, :catch_failures => true)
+
+step 'Verify results'
+# should have no effect
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_not_match(/.+/, get_xml_value("//config/add[@key='proxyPassword']/@value", result.output).to_s, 'Value should have been removed')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/remove_value_from_config.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/remove_value_from_config.rb
new file mode 100644
index 000000000..6bebb8d4a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyconfig/remove_value_from_config.rb
@@ -0,0 +1,28 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3035 - Remove Value From Config Setting'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyconfig {'commandExecutionTimeoutSeconds':
+ ensure => absent,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_not_match(/.+/, get_xml_value("//config/add[@key='commandExecutionTimeoutSeconds']/@value", result.output).to_s, 'Value did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/disable_disabled_feature.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/disable_disabled_feature.rb
new file mode 100644
index 000000000..80dbd9d7b
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/disable_disabled_feature.rb
@@ -0,0 +1,35 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3034 - Disable an Already Disabled Feature'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyfeature {'failOnAutoUninstaller':
+ ensure => disabled,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# verify prior
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/false/, get_xml_value("//features/feature[@name='failOnAutoUninstaller']/@enabled", result.output).to_s, 'Was not disabled by default, please adjust test to find another value.')
+ end
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/false/, get_xml_value("//features/feature[@name='failOnAutoUninstaller']/@enabled", result.output).to_s, 'Was not found disabled')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/disable_enabled_feature.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/disable_enabled_feature.rb
new file mode 100644
index 000000000..e2ae3e515
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/disable_enabled_feature.rb
@@ -0,0 +1,35 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3034 - Disable an Enabled Feature'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyfeature {'checksumFiles':
+ ensure => disabled,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+#verify prior
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/true/, get_xml_value("//features/feature[@name='checksumFiles']/@enabled", result.output).to_s, 'Was not enabled by default, please adjust test to find another value.')
+ end
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/false/, get_xml_value("//features/feature[@name='checksumFiles']/@enabled", result.output).to_s, 'Was not found disabled')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/enable_disabled_feature.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/enable_disabled_feature.rb
new file mode 100644
index 000000000..95c2730c4
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/enable_disabled_feature.rb
@@ -0,0 +1,35 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3034 - Enable a Disabled Feature'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyfeature {'failOnAutoUninstaller':
+ ensure => enabled,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# verify prior
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/false/, get_xml_value("//features/feature[@name='failOnAutoUninstaller']/@enabled", result.output).to_s, 'Was not disabled by default, please adjust test to find another value.')
+ end
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/true/, get_xml_value("//features/feature[@name='failOnAutoUninstaller']/@enabled", result.output).to_s, 'Was not found enabled')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/enable_enabled_feature.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/enable_enabled_feature.rb
new file mode 100644
index 000000000..9928ccc1b
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/enable_enabled_feature.rb
@@ -0,0 +1,35 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3034 - Enable Already Enabled Feature'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyfeature {'checksumFiles':
+ ensure => enabled,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+#verify prior
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/true/, get_xml_value("//features/feature[@name='checksumFiles']/@enabled", result.output).to_s, 'Was not enabled by default, please adjust test to find another value.')
+ end
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/true/, get_xml_value("//features/feature[@name='checksumFiles']/@enabled", result.output).to_s, 'Was not found enabled')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/fail_to_enable_nonexistent_feature.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/fail_to_enable_nonexistent_feature.rb
new file mode 100644
index 000000000..05367d19b
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/fail_to_enable_nonexistent_feature.rb
@@ -0,0 +1,24 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3034 - Enable non-existent feature'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyfeature {'idontexistfeature123123':
+ ensure => enabled,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify Failure'
+ assert_match(/returned 1: Feature 'idontexistfeature123123' not found/, stderr, "stderr did not match expected")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/fail_to_remove_feature.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/fail_to_remove_feature.rb
new file mode 100644
index 000000000..b7be83f30
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateyfeature/fail_to_remove_feature.rb
@@ -0,0 +1,25 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3034 - Attempt to remove feature'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateyfeature {'checksumFiles':
+ ensure => absent,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify Failure'
+ assert_match(/Error: Parameter ensure failed on Chocolateyfeature\[checksumFiles\]: Invalid value \"absent\"/, stderr, "stderr did not match expected")
+end
+
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateypackage/install_and_remove_good_package.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateypackage/install_and_remove_good_package.rb
new file mode 100644
index 000000000..654b3bfb5
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateypackage/install_and_remove_good_package.rb
@@ -0,0 +1,62 @@
+require 'chocolatey_helper'
+require 'beaker-windows'
+test_name 'MODULES-3037 - 97729 Install known good package via manifest and remove via manifest'
+confine(:to, :platform => 'windows')
+
+# arrange
+package_name = 'vlc'
+package_exe_path = %{C:\\'Program Files\\VideoLAN\\VLC\\vlc.exe'}
+software_uninstall_command = %{cmd.exe /C C:\\'Program Files\\VideoLAN\\VLC\\uninstall.exe' /S}
+
+chocolatey_package_manifest = <<-PP
+ package { "#{package_name}":
+ ensure => present,
+ provider => chocolatey,
+ source => 'http://nexus.delivery.puppetlabs.net/service/local/nuget/choco-pipeline-tests/'
+ }
+PP
+
+# teardown
+teardown do
+ on(agent, exec_ps_cmd("test-path #{package_exe_path}")) do |result|
+ if (result.output =~ /True/i)
+ retry_on(agent, exec_ps_cmd(software_uninstall_command))
+ end
+ end
+ #TODO: should we validate that the software was removed successfully here?
+end
+
+#validate
+step "should not have valid version of #{package_name}"
+on(agent, exec_ps_cmd("test-path #{package_exe_path}")) do |result|
+ assert_match(/False/i, result.output, "#{package_name} was present before application of manifest.")
+end
+
+
+#act
+step 'Apply manifest'
+apply_manifest(chocolatey_package_manifest, :catch_failures => true) do |result|
+ assert_match(/Notice\: \/Stage\[main\]\/Main\/Package\[#{package_name}\]\/ensure\: created/, result.stdout, "stdout did not report package creation of #{package_name}")
+end
+
+#validate
+step "should have valid version of #{package_name}"
+on(agent, exec_ps_cmd("test-path #{package_exe_path}")) do |result|
+ assert_match(/True/i, result.output, "#{package_name} was not present after application of manifest.")
+end
+
+#arrange
+chocolatey_package_manifest = <<-PP
+ package { "#{package_name}":
+ ensure => absent,
+ provider => chocolatey,
+ }
+PP
+
+#act
+step "Uninstall #{package_name} package via manifest"
+apply_manifest(chocolatey_package_manifest, :catch_failures => true) do |result|
+#validate
+ assert_match(/Stage\[main\]\/Main\/Package\[#{package_name}\]\/ensure\: removed/, result.stdout, "stdout did not report package removal of #{package_name}")
+end
+
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateypackage/install_and_remove_good_package_utf-8.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateypackage/install_and_remove_good_package_utf-8.rb
new file mode 100644
index 000000000..90d01f88e
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateypackage/install_and_remove_good_package_utf-8.rb
@@ -0,0 +1,65 @@
+# require 'chocolatey_helper'
+# require 'beaker-windows'
+# test_name 'MODULES-3037 - C97738 Install known good package with utf-8 via manifest and remove via manifest'
+# confine(:to, :platform => 'windows')
+#
+# # arrange
+# package_name = '竹ChocolateyGUIÖ'
+# package_exe_path = %{C:\\'Program Files (x86)\\ChocolateyGUI\\ChocolateyGUI.exe'}
+# software_uninstall_command = %{msiexec /x C:\\ProgramData\\chocolatey\\lib\\竹ChocolateyGUIÖ\\tools\\竹ChocolateyGUIÖ.msi /q}.force_encoding("ASCII-8BIT")
+#
+# chocolatey_package_manifest = <<-PP
+# package { "#{package_name}":
+# ensure => present,
+# provider => chocolatey,
+# source => 'http://nexus.delivery.puppetlabs.net/service/local/nuget/choco-pipeline-tests/'
+# }
+# PP
+#
+# # teardown
+# teardown do
+# on(agent, exec_ps_cmd("test-path #{package_exe_path}")) do |result|
+# if (result.output =~ /True/i)
+# on(agent, exec_ps_cmd(software_uninstall_command))
+# end
+# end
+# on(agent, exec_ps_cmd("test-path #{package_exe_path}")) do |result|
+# assert_match(/False/i, result.output, "#{package_name} was present after uninstall.")
+# end
+# end
+#
+# #validate
+# step "should not have valid version of #{package_name}"
+# on(agent, exec_ps_cmd("test-path #{package_exe_path}")) do |result|
+# assert_match(/False/i, result.output, "#{package_name} was present before application of manifest.")
+# end
+#
+#
+# #act
+# step 'Apply manifest'
+# apply_manifest(chocolatey_package_manifest, :catch_failures => true) do |result|
+# assert_match(/Notice\: \/Stage\[main\]\/Main\/Package\[#{package_name}\]\/ensure\: created/, result.stdout, "stdout did not report package creation of #{package_name}")
+# end
+#
+# #validate
+# step "should have valid version of #{package_name}"
+# on(agent, exec_ps_cmd("test-path #{package_exe_path}")) do |result|
+# assert_match(/True/i, result.output, "#{package_name} was not present after application of manifest.")
+# end
+#
+# #arrange
+# chocolatey_package_manifest = <<-PP
+# package { "#{package_name}":
+# ensure => absent,
+# provider => chocolatey,
+# }
+# PP
+#
+# #act
+# step "Uninstall #{package_name} package via manifest"
+# apply_manifest(chocolatey_package_manifest, :catch_failures => true) do |result|
+# #validate
+# expect_failure('Expected to fail because of MODULES-3541') do
+# assert_match(/Stage\[main\]\/Main\/Package\[#{package_name}\]\/ensure\: removed/, result.stdout, "stdout did not report package removal of #{package_name}")
+# end
+# end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_priority_to_existing_source.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_priority_to_existing_source.rb
new file mode 100644
index 000000000..98e53ae2e
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_priority_to_existing_source.rb
@@ -0,0 +1,30 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add Priority to an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ priority => 1,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/1/, get_xml_value("//sources/source[@id='chocolatey']/@priority", result.output).to_s, 'Priority did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_all_options.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_all_options.rb
new file mode 100644
index 000000000..b9abee607
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_all_options.rb
@@ -0,0 +1,36 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add Source With All Options'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'test':
+ ensure => present,
+ location => 'c:\\packages',
+ priority => 2,
+ user => 'bob',
+ password => 'yes',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/c:\\packages/, get_xml_value("//sources/source[@id='test']/@value", result.output).to_s, 'Location did not match')
+ assert_match(/2/, get_xml_value("//sources/source[@id='test']/@priority", result.output).to_s, 'Priority did not match')
+ assert_match(/bob/, get_xml_value("//sources/source[@id='test']/@user", result.output).to_s, 'User did not match')
+ assert_match(/.+/, get_xml_value("//sources/source[@id='test']/@password", result.output).to_s, 'Password was not saved')
+ assert_match(/false/, get_xml_value("//sources/source[@id='test']/@disabled", result.output).to_s, 'Disabled did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_minimal.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_minimal.rb
new file mode 100644
index 000000000..79f7e9df0
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_minimal.rb
@@ -0,0 +1,29 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add Source Minimal'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'test':
+ location => 'c:\\packages',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/c:\\packages/, get_xml_value("//sources/source[@id='test']/@value", result.output).to_s, 'Location did not match')
+ assert_match(/false/, get_xml_value("//sources/source[@id='test']/@disabled", result.output).to_s, 'Disabled did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_normal.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_normal.rb
new file mode 100644
index 000000000..b7361a8e1
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_source_normal.rb
@@ -0,0 +1,30 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add Source Happy Path'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'test':
+ ensure => present,
+ location => 'c:\\packages',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/c:\\packages/, get_xml_value("//sources/source[@id='test']/@value", result.output).to_s, 'Location did not match')
+ assert_match(/false/, get_xml_value("//sources/source[@id='test']/@disabled", result.output).to_s, 'Disabled did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_user_pass_to_existing_source.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_user_pass_to_existing_source.rb
new file mode 100644
index 000000000..7fddf95df
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/add_user_pass_to_existing_source.rb
@@ -0,0 +1,33 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add User/Password to an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ user => 'tim',
+ password => 'test',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/tim/, get_xml_value("//sources/source[@id='chocolatey']/@user", result.output).to_s, 'User did not match')
+ # we are not able to verify password other than if it has a value - it will be encrypted in a non-verifyable way
+ assert_match(/.+/, get_xml_value("//sources/source[@id='chocolatey']/@password", result.output).to_s, 'Password was not saved')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_existing_priority.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_existing_priority.rb
new file mode 100644
index 000000000..81e07a9e0
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_existing_priority.rb
@@ -0,0 +1,51 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Change Existing Priority'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ priority => 1,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest to set priority'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify priority setup was added'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/1/, get_xml_value("//sources/source[@id='chocolatey']/@priority", result.output).to_s, 'Priority setup did not match')
+ end
+end
+
+# arrange
+chocolatey_src_change = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ priority => 5,
+ }
+PP
+
+# act
+step 'Apply manifest to change priority'
+apply_manifest(chocolatey_src_change, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/5/, get_xml_value("//sources/source[@id='chocolatey']/@priority", result.output).to_s, 'Priority change did not match')
+ end
+end
+
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_existing_source_location.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_existing_source_location.rb
new file mode 100644
index 000000000..ca3171c7c
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_existing_source_location.rb
@@ -0,0 +1,30 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Change Source Location for an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'c:\\packages',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/c:\\packages/, get_xml_value("//sources/source[@id='chocolatey']/@value", result.output).to_s, 'Location did not match')
+ #todo should also verify there are no duplicates
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_user_pass.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_user_pass.rb
new file mode 100644
index 000000000..ab23923de
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/change_user_pass.rb
@@ -0,0 +1,54 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Change User/Password In an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ user => 'tim',
+ password => 'test',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest to setup user/password'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify user/password setup'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/tim/, get_xml_value("//sources/source[@id='chocolatey']/@user", result.output).to_s, 'User setup did not match')
+ # we are not able to verify password other than if it has a value - it will be encrypted in a non-verifyable way
+ assert_match(/.+/, get_xml_value("//sources/source[@id='chocolatey']/@password", result.output).to_s, 'Password was not saved')
+ end
+end
+
+# arrange
+chocolatey_src_change = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ user => 'bob',
+ password => 'newpass',
+ }
+PP
+
+# act
+step 'Apply manifest to change user/password'
+apply_manifest(chocolatey_src_change, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/bob/, get_xml_value("//sources/source[@id='chocolatey']/@user", result.output).to_s, 'User change did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/disable_existing_source.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/disable_existing_source.rb
new file mode 100644
index 000000000..1abda7a2a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/disable_existing_source.rb
@@ -0,0 +1,28 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Disable an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => disabled,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/true/, get_xml_value("//sources/source[@id='chocolatey']/@disabled", result.output).to_s, 'Disabled did not match')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/disable_existing_source_two_runs.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/disable_existing_source_two_runs.rb
new file mode 100644
index 000000000..c8618dc46
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/disable_existing_source_two_runs.rb
@@ -0,0 +1,43 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Disable an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => disabled,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify setup'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/true/, get_xml_value("//sources/source[@id='chocolatey']/@disabled", result.output).to_s, 'Disabled did not match')
+ end
+end
+
+# act
+step 'Apply manifest a second time'
+apply_manifest(chocolatey_src, :catch_failures => true) do |result|
+ assert_not_match(/Chocolateysource\[chocolatey\]\/user\: defined 'user' as ''/, result.stdout, "User was adjusted and should not have been")
+end
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/true/, get_xml_value("//sources/source[@id='chocolatey']/@disabled", result.output).to_s, 'Disabled did not match')
+
+ end
+end
+
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_apply_source_without_location.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_apply_source_without_location.rb
new file mode 100644
index 000000000..5a5043e8a
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_apply_source_without_location.rb
@@ -0,0 +1,24 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3430 - Add Source Sad Path: Fail to apply manifest without location'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify failure'
+ assert_match(/Error: Validation of Chocolateysource\[chocolatey\] failed: A non-empty location/, stderr, "stderr did not match expected")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_appy_bad_manifest.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_appy_bad_manifest.rb
new file mode 100644
index 000000000..85d15c873
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_appy_bad_manifest.rb
@@ -0,0 +1,25 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add Source Sad Path: Fail to apply bad manifest'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'test':
+ ensure => sad,
+ location => 'c:\\packages',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply Manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify Failure'
+ assert_match(/Error: Parameter ensure failed on Chocolateysource\[test\]: Invalid value "sad"/, stderr, "stderr did not match expected")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_set_password_without_user.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_set_password_without_user.rb
new file mode 100644
index 000000000..7648252fc
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_set_password_without_user.rb
@@ -0,0 +1,26 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add Source Sad Path: Set password with no user'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ password => 'test',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply Manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify Failure'
+ assert_match(/Error: Validation of Chocolateysource\[chocolatey\] failed: If specifying user\/password, you must specify both values/, stderr, "stderr did not match expected")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_set_user_without_password.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_set_user_without_password.rb
new file mode 100644
index 000000000..db18b0002
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/fail_to_set_user_without_password.rb
@@ -0,0 +1,26 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Add Source Sad Path: Set user with no password'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ user => 'tim',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply Manifest'
+apply_manifest(chocolatey_src, :expect_failures => true) do
+ step 'Verify Failure'
+ assert_match(/Error: Validation of Chocolateysource\[chocolatey\] failed: If specifying user\/password, you must specify both values/, stderr, "stderr did not match expected")
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_existing_source.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_existing_source.rb
new file mode 100644
index 000000000..78552bfde
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_existing_source.rb
@@ -0,0 +1,28 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Remove an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => absent,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_not_match(/chocolatey/, get_xml_value("//sources/source[@id='chocolatey']/@id", result.output).to_s, 'Source was not removed')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_priority_from_existing_source.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_priority_from_existing_source.rb
new file mode 100644
index 000000000..84359bb7d
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_priority_from_existing_source.rb
@@ -0,0 +1,50 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Remove Priority from an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ priority => 1,
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest to setup priority'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify priority setup'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/1/, get_xml_value("//sources/source[@id='chocolatey']/@priority", result.output).to_s, 'Priority did not match')
+ end
+end
+
+# arrange
+chocolatey_src_remove = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ }
+PP
+
+# act
+step 'Apply manifest to remove priority'
+apply_manifest(chocolatey_src_remove, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/0/, get_xml_value("//sources/source[@id='chocolatey']/@priority", result.output).to_s, 'Priority change did not match')
+ end
+end
+
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_user_pass_from_existing_source.rb b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_user_pass_from_existing_source.rb
new file mode 100644
index 000000000..9a2204ef6
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/reference/tests/chocolateysource/remove_user_pass_from_existing_source.rb
@@ -0,0 +1,53 @@
+require 'chocolatey_helper'
+test_name 'MODULES-3037 - Remove User/Password From an Existing Source'
+confine(:to, :platform => 'windows')
+
+backup_config
+
+# arrange
+chocolatey_src = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ user => 'tim',
+ password => 'test',
+ }
+PP
+
+# teardown
+teardown do
+ reset_config
+end
+
+# act
+step 'Apply manifest to setup user/password'
+apply_manifest(chocolatey_src, :catch_failures => true)
+
+step 'Verify user/password setup'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_match(/tim/, get_xml_value("//sources/source[@id='chocolatey']/@user", result.output).to_s, 'User setup did not match')
+ # we are not able to verify password other than if it has a value - it will be encrypted in a non-verifyable way
+ assert_match(/.+/, get_xml_value("//sources/source[@id='chocolatey']/@password", result.output).to_s, 'Password was not saved')
+ end
+end
+
+# arrange
+chocolatey_src_remove = <<-PP
+ chocolateysource {'chocolatey':
+ ensure => present,
+ location => 'https://chocolatey.org/api/v2',
+ }
+PP
+
+# act
+step 'Apply manifest to remove user/password'
+apply_manifest(chocolatey_src_remove, :catch_failures => true)
+
+step 'Verify results'
+agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{config_file_location}\"") do |result|
+ assert_not_match(/.+/, get_xml_value("//sources/source[@id='chocolatey']/@user", result.output).to_s, 'User was not removed')
+ assert_not_match(/.+/, get_xml_value("//sources/source[@id='chocolatey']/@password", result.output).to_s, 'Password was not removed')
+ end
+end
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/test_run_scripts/acceptance_tests.sh b/modules/utilities/windows/repository_managers/chocolatey/tests/test_run_scripts/acceptance_tests.sh
new file mode 100644
index 000000000..d1c11a4e7
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/test_run_scripts/acceptance_tests.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+set -e
+
+# Init
+SCRIPT_PATH=$(pwd)
+BASENAME_CMD="basename ${SCRIPT_PATH}"
+SCRIPT_BASE_PATH=`eval ${BASENAME_CMD}`
+declare -a ARGS
+
+# Argument Parsing
+if [ $# -eq 0 ]; then
+ ARGS[0]='windows-2012r2-64mda'
+ ARGS[1]='http://pe-releases.puppetlabs.lan/2016.1.1/'
+ ARGS[2]='forge'
+elif [[ $# -lt 3 || $# -gt 4 ]]; then
+ echo 'USAGE acceptance_tests.sh '
+ exit 1
+else
+ ARGS=("$@")
+fi
+
+# Figure out where we are in the directory hierarchy
+if [ $SCRIPT_BASE_PATH = "test_run_scripts" ]; then
+ cd ../../
+fi
+
+# Determine if the forge is needed for the test.
+if [ ${ARGS[2]} == 'forge' ]; then
+ echo 'Testing Module Using Forge Package'
+ export BEAKER_FORGE_HOST=api-module-staging.puppetlabs.com
+elif [ ${ARGS[2]} == 'local' ]; then
+ echo 'Testing Module Using Local Code'
+else
+ echo 'You must specify "forge" or "local" for test type!'
+ echo 'USAGE acceptance_tests.sh '
+ exit 1
+fi
+
+# Determine if a module version was specified.
+if [ -n "${ARGS[3]}" ]; then
+ echo "Using Module Version: ${ARGS[3]}"
+ export MODULE_VERSION=${ARGS[3]}
+elif [[ $# -eq 3 && ${ARGS[2]} == 'forge' ]]; then
+ echo 'WARNING: Running Acceptance Tests from Forge without Module Version!'
+fi
+
+# Sleep so the user has time to read script messages.
+sleep 2
+
+export pe_dist_dir=${ARGS[1]}
+export GEM_SOURCE=http://rubygems.delivery.puppetlabs.net
+
+bundle install --without build development test --path .bundle/gems
+
+bundle exec beaker \
+ --preserve-hosts onfail \
+ --config tests/configs/${ARGS[0]} \
+ --debug \
+ --tests tests/acceptance/tests \
+ --keyfile ~/.ssh/id_rsa-acceptance \
+ --pre-suite tests/acceptance/pre-suite \
+ --load-path tests/lib
+
+TEST_RESULT=$?
+
+rm -rf tmp
+
+exit $TEST_RESULT
diff --git a/modules/utilities/windows/repository_managers/chocolatey/tests/test_run_scripts/reference_tests.sh b/modules/utilities/windows/repository_managers/chocolatey/tests/test_run_scripts/reference_tests.sh
new file mode 100644
index 000000000..f48088ebd
--- /dev/null
+++ b/modules/utilities/windows/repository_managers/chocolatey/tests/test_run_scripts/reference_tests.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+
+set -e
+
+# Init
+SCRIPT_PATH=$(pwd)
+BASENAME_CMD="basename ${SCRIPT_PATH}"
+SCRIPT_BASE_PATH=`eval ${BASENAME_CMD}`
+declare -a ARGS
+
+# Argument Parsing
+if [ $# -eq 0 ]; then
+ ARGS[0]='windows-2012r2-64a'
+ ARGS[1]='1.4.1'
+ ARGS[2]='local'
+elif [[ $# -lt 3 || $# -gt 4 ]]; then
+ echo 'USAGE reference_tests.sh '
+ exit 1
+else
+ ARGS=("$@")
+fi
+
+# Figure out where we are in the directory hierarchy
+if [ $SCRIPT_BASE_PATH = "test_run_scripts" ]; then
+ cd ../../
+fi
+
+# Determine if the forge is needed for the test.
+if [ ${ARGS[2]} == 'forge' ]; then
+ echo 'Testing Module Using Forge Package'
+ export BEAKER_FORGE_HOST=api-module-staging.puppetlabs.com
+elif [ ${ARGS[2]} == 'local' ]; then
+ echo 'Testing Module Using Local Code'
+else
+ echo 'You must specify "forge" or "local" for test type!'
+ echo 'USAGE reference_tests.sh '
+ exit 1
+fi
+
+# Determine if a module version was specified.
+if [ -n "${ARGS[3]}" ]; then
+ echo "Using Module Version: ${ARGS[3]}"
+ export MODULE_VERSION=${ARGS[3]}
+elif [[ $# -eq 3 && ${ARGS[2]} == 'forge' ]]; then
+ echo 'WARNING: Running Reference Tests from Forge without Module Version!'
+fi
+
+# Sleep so the user has time to read script messages.
+sleep 2
+
+export BEAKER_PUPPET_AGENT_VERSION=${ARGS[1]}
+export GEM_SOURCE=http://rubygems.delivery.puppetlabs.net
+
+bundle install --without build development test --path .bundle/gems
+
+bundle exec beaker \
+ --preserve-hosts onfail \
+ --config tests/configs/${ARGS[0]} \
+ --debug \
+ --tests tests/reference/tests \
+ --keyfile ~/.ssh/id_rsa-acceptance \
+ --pre-suite tests/reference/pre-suite \
+ --load-path tests/lib \
+ --type aio
+
\ No newline at end of file
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/CHANGELOG.md b/modules/utilities/windows/shells/puppetlabs_powershell_local/CHANGELOG.md
new file mode 100644
index 000000000..66cc345b5
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/CHANGELOG.md
@@ -0,0 +1,98 @@
+##2016-11-17 - Supported Release 2.1.0
+###Summary
+
+Small release with bugs fixes and another speed improvement.
+
+###Bug Fixes
+- Support Windows 2016/WMF 5.1 using named pipes ([MODULES-3690](https://tickets.puppetlabs.com/browse/MODULES-3690))
+
+###Documentation updates
+- Document herestring ([DOC-2960](https://tickets.puppetlabs.com/browse/DOC-2960))
+
+##2016-10-05 - Supported Release 2.0.3
+###Summary
+
+Small release with bugs fixes and another speed improvement.
+
+###Bug Fixes
+- Miscellaneous fixes which improve reliability
+- Capture exit codes when executing external scripts ([MODULES-3399](https://tickets.puppetlabs.com/browse/MODULES-3399))
+- Add ability to set current working directory ([MODULES-3565](https://tickets.puppetlabs.com/browse/MODULES-3565))
+- Respect user specified timeout ([MODULES-3709](https://tickets.puppetlabs.com/browse/MODULES-3709))
+- Improve handling of user code exceptions ([MODULES-3443](https://tickets.puppetlabs.com/browse/MODULES-3443))
+- Output line and stacktrace of user code exception ([MODULES-3839](https://tickets.puppetlabs.com/browse/MODULES-3839))
+- Improve resilience to failure of PowerShell host ([MODULES-3875](https://tickets.puppetlabs.com/browse/MODULES-3875))
+- Fix race condition in threading with PowerShell host ([MODULES-3144](https://tickets.puppetlabs.com/browse/MODULES-3144))
+- Modify tests to detect differences in PowerShell error text ([MODULES-3442](https://tickets.puppetlabs.com/browse/MODULES-3442))
+
+###Documentation updates
+- Document how to handle exit codes ([MODULES-3588](https://tickets.puppetlabs.com/browse/MODULES-3588))
+
+##2016-07-12 - Supported Release 2.0.2
+###Summary
+
+Small release with bugs fixes and another speed improvement.
+
+###Features
+- Noticable speed increase by reducing the time start a PowerShell command ([MODULES-3406](https://tickets.puppetlabs.com/browse/MODULES-3406))
+
+###Bug Fixes
+- Fixed minor bugs in tests ([MODULES-3347](https://tickets.puppetlabs.com/browse/MODULES-3347))
+- Added tests for try/catch ([MODULES-2634](https://tickets.puppetlabs.com/browse/MODULES-2634))
+- Fixed bug with older ruby (1.8)
+
+##2016-05-24 - Supported Release 2.0.1
+###Bug Fixes
+
+- Updated the powershell manager in this module in order to not conflict with the Powershell Manager in the Puppet DSC module
+
+##2016-05-17 - Supported Release 2.0.0
+###Summary
+
+Major release with performance improvements
+
+Removed support for Windows Server 2003
+
+###Features
+- Major performance improvement by sharing a single powershell session instead of creating a new powershell session per command
+- Security improvement as scripts are not stored on the filesystem temporarily
+
+###Bug Fixes
+- Updated test suites with later versions
+- Documentation cleanup
+
+##2015-12-08 - Supported Release 1.0.6
+###Summary
+
+Small release for support of newer PE versions.
+
+##2015-07-28 - Supported Release 1.0.5
+###Summary
+
+Add metadata for Puppet 4 and PE 2015.2.0
+
+###Bug Fixes
+- Minor testing bug fixes
+- Readme cleanup
+
+##2014-11-04 - Supported Release 1.0.4
+###Summary
+
+Fix Issues URL
+Add Future Parser testing support
+
+##2014-08-25 - Supported Release 1.0.3
+###Summary
+
+This release updates the tests to verify that powershell continues to function on x64-native ruby.
+
+##2014-07-15 - Supported Release 1.0.2
+###Summary
+
+This release merely updates metadata.json so the module can be uninstalled and
+upgraded via the puppet module command.
+
+##2014-07-09 - Release 1.0.1
+###Summary
+
+Fix issue with metadata and PE version requirement
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/CONTRIBUTING.md b/modules/utilities/windows/shells/puppetlabs_powershell_local/CONTRIBUTING.md
new file mode 100644
index 000000000..dd2b5c4ff
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/CONTRIBUTING.md
@@ -0,0 +1,219 @@
+Checklist (and a short version for the impatient)
+=================================================
+
+ * Commits:
+
+ - Make commits of logical units.
+
+ - Check for unnecessary whitespace with "git diff --check" before
+ committing.
+
+ - Commit using Unix line endings (check the settings around "crlf" in
+ git-config(1)).
+
+ - Do not check in commented out code or unneeded files.
+
+ - The first line of the commit message should be a short
+ description (50 characters is the soft limit, excluding ticket
+ number(s)), and should skip the full stop.
+
+ - Associate the issue in the message. The first line should include
+ the issue number in the form "(#XXXX) Rest of message".
+
+ - The body should provide a meaningful commit message, which:
+
+ - uses the imperative, present tense: "change", not "changed" or
+ "changes".
+
+ - includes motivation for the change, and contrasts its
+ implementation with the previous behavior.
+
+ - Make sure that you have tests for the bug you are fixing, or
+ feature you are adding.
+
+ - Make sure the test suites passes after your commit:
+ `bundle exec rspec spec/acceptance` More information on [testing](#Testing) below
+
+ - When introducing a new feature, make sure it is properly
+ documented in the README.md
+
+ * Submission:
+
+ * Pre-requisites:
+
+ - Make sure you have a [GitHub account](https://github.com/join)
+
+ - [Create a ticket](https://tickets.puppet.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppet.com/browse/) you are patching for.
+
+ * Preferred method:
+
+ - Fork the repository on GitHub.
+
+ - Push your changes to a topic branch in your fork of the
+ repository. (the format ticket/1234-short_description_of_change is
+ usually preferred for this project).
+
+ - Submit a pull request to the repository in the puppetlabs
+ organization.
+
+The long version
+================
+
+ 1. Make separate commits for logically separate changes.
+
+ Please break your commits down into logically consistent units
+ which include new or changed tests relevant to the rest of the
+ change. The goal of doing this is to make the diff easier to
+ read for whoever is reviewing your code. In general, the easier
+ your diff is to read, the more likely someone will be happy to
+ review it and get it into the code base.
+
+ If you are going to refactor a piece of code, please do so as a
+ separate commit from your feature or bug fix changes.
+
+ We also really appreciate changes that include tests to make
+ sure the bug is not re-introduced, and that the feature is not
+ accidentally broken.
+
+ Describe the technical detail of the change(s). If your
+ description starts to get too long, that is a good sign that you
+ probably need to split up your commit into more finely grained
+ pieces.
+
+ Commits which plainly describe the things which help
+ reviewers check the patch and future developers understand the
+ code are much more likely to be merged in with a minimum of
+ bike-shedding or requested changes. Ideally, the commit message
+ would include information, and be in a form suitable for
+ inclusion in the release notes for the version of Puppet that
+ includes them.
+
+ Please also check that you are not introducing any trailing
+ whitespace or other "whitespace errors". You can do this by
+ running "git diff --check" on your changes before you commit.
+
+ 2. Sending your patches
+
+ To submit your changes via a GitHub pull request, we _highly_
+ recommend that you have them on a topic branch, instead of
+ directly on "master".
+ It makes things much easier to keep track of, especially if
+ you decide to work on another thing before your first change
+ is merged in.
+
+ GitHub has some pretty good
+ [general documentation](http://help.github.com/) on using
+ their site. They also have documentation on
+ [creating pull requests](http://help.github.com/send-pull-requests/).
+
+ In general, after pushing your topic branch up to your
+ repository on GitHub, you can switch to the branch in the
+ GitHub UI and click "Pull Request" towards the top of the page
+ in order to open a pull request.
+
+
+ 3. Update the related GitHub issue.
+
+ If there is a GitHub issue associated with the change you
+ submitted, then you should update the ticket to include the
+ location of your branch, along with any other commentary you
+ may wish to make.
+
+Testing
+=======
+
+Getting Started
+---------------
+
+Our puppet modules provide [`Gemfile`](./Gemfile)s which can tell a ruby
+package manager such as [bundler](http://bundler.io/) what Ruby packages,
+or Gems, are required to build, develop, and test this software.
+
+Please make sure you have [bundler installed](http://bundler.io/#getting-started)
+on your system, then use it to install all dependencies needed for this project,
+by running
+
+```shell
+% bundle install
+Fetching gem metadata from https://rubygems.org/........
+Fetching gem metadata from https://rubygems.org/..
+Using rake (10.1.0)
+Using builder (3.2.2)
+-- 8><-- many more --><8 --
+Using rspec-system-puppet (2.2.0)
+Using serverspec (0.6.3)
+Using rspec-system-serverspec (1.0.0)
+Using bundler (1.3.5)
+Your bundle is complete!
+Use `bundle show [gemname]` to see where a bundled gem is installed.
+```
+
+NOTE some systems may require you to run this command with sudo.
+
+If you already have those gems installed, make sure they are up-to-date:
+
+```shell
+% bundle update
+```
+
+With all dependencies in place and up-to-date we can now run the tests:
+
+```shell
+% rake spec
+```
+
+This will execute all the [rspec tests](http://rspec-puppet.com/) tests
+under [spec/defines](./spec/defines), [spec/classes](./spec/classes),
+and so on. rspec tests may have the same kind of dependencies as the
+module they are testing. While the module defines in its [Modulefile](./Modulefile),
+rspec tests define them in [.fixtures.yml](./fixtures.yml).
+
+Some puppet modules also come with [beaker](https://github.com/puppetlabs/beaker)
+tests. These tests spin up a virtual machine under
+[VirtualBox](https://www.virtualbox.org/)) with, controlling it with
+[Vagrant](http://www.vagrantup.com/) to actually simulate scripted test
+scenarios. In order to run these, you will need both of those tools
+installed on your system.
+
+You can run them by issuing the following command
+
+```shell
+% rake spec_clean
+% rspec spec/acceptance
+```
+
+This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml),
+install puppet, copy this module and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb)
+and then run all the tests under [spec/acceptance](./spec/acceptance).
+
+Writing Tests
+-------------
+
+XXX getting started writing tests.
+
+If you have commit access to the repository
+===========================================
+
+Even if you have commit access to the repository, you will still need to
+go through the process above, and have someone else review and merge
+in your changes. The rule is that all changes must be reviewed by a
+developer on the project (that did not write the code) to ensure that
+all changes go through a code review process.
+
+Having someone other than the author of the topic branch recorded as
+performing the merge is the record that they performed the code
+review.
+
+
+Additional Resources
+====================
+
+* [Getting additional help](http://puppet.com/community/get-help)
+
+* [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing)
+
+* [General GitHub documentation](http://help.github.com/)
+
+* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
+
+
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/Gemfile b/modules/utilities/windows/shells/puppetlabs_powershell_local/Gemfile
new file mode 100644
index 000000000..4d61083ae
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/Gemfile
@@ -0,0 +1,153 @@
+source ENV['GEM_SOURCE'] || "https://rubygems.org"
+
+# Determines what type of gem is requested based on place_or_version.
+def gem_type(place_or_version)
+ if place_or_version =~ /^git:/
+ :git
+ elsif place_or_version =~ /^file:/
+ :file
+ else
+ :gem
+ end
+end
+
+# Find a location or specific version for a gem. place_or_version can be a
+# version, which is most often used. It can also be git, which is specified as
+# `git://somewhere.git#branch`. You can also use a file source location, which
+# is specified as `file://some/location/on/disk`.
+def location_for(place_or_version, fake_version = nil)
+ if place_or_version =~ /^(git[:@][^#]*)#(.*)/
+ [fake_version, { :git => $1, :branch => $2, :require => false }].compact
+ elsif place_or_version =~ /^file:\/\/(.*)/
+ ['>= 0', { :path => File.expand_path($1), :require => false }]
+ else
+ [place_or_version, { :require => false }]
+ end
+end
+
+# The following gems are not included by default as they require DevKit on Windows.
+# You should probably include them in a Gemfile.local or a ~/.gemfile
+#gem 'pry' #this may already be included in the gemfile
+#gem 'pry-stack_explorer', :require => false
+#if RUBY_VERSION =~ /^2/
+# gem 'pry-byebug'
+#else
+# gem 'pry-debugger'
+#end
+
+group :development do
+ gem 'rake', :require => false
+ gem 'rspec', '~>3.0', :require => false
+ gem 'puppet-lint', :require => false
+ gem 'puppetlabs_spec_helper', '~>0.10.3', :require => false
+ gem 'puppet_facts', :require => false
+ gem 'mocha', '~>0.10.5', :require => false
+ gem 'pry', :require => false
+end
+
+group :system_tests do
+ gem 'beaker-rspec', *location_for(ENV['BEAKER_RSPEC_VERSION'] || '~> 5.1')
+ gem 'beaker', *location_for(ENV['BEAKER_VERSION'] || '~> 2.20')
+ gem 'beaker-puppet_install_helper', :require => false
+end
+
+# The recommendation is for PROJECT_GEM_VERSION, although there are older ways
+# of referencing these. Add them all for compatibility reasons. We'll remove
+# later when no issues are known. We'll prefer them in the right order.
+puppetversion = ENV['PUPPET_GEM_VERSION'] || ENV['GEM_PUPPET_VERSION'] || ENV['PUPPET_LOCATION'] || '>= 0'
+gem 'puppet', *location_for(puppetversion)
+
+# json_pure 2.0.2 added a requirement on ruby >= 2. We pin to json_pure 2.0.1
+# if using ruby 1.x
+gem 'json_pure', '<=2.0.1', :require => false if RUBY_VERSION =~ /^1\./
+
+# Only explicitly specify Facter/Hiera if a version has been specified.
+# Otherwise it can lead to strange bundler behavior. If you are seeing weird
+# gem resolution behavior, try setting `DEBUG_RESOLVER` environment variable
+# to `1` and then run bundle install.
+facterversion = ENV['FACTER_GEM_VERSION'] || ENV['GEM_FACTER_VERSION'] || ENV['FACTER_LOCATION']
+gem "facter", *location_for(facterversion) if facterversion
+hieraversion = ENV['HIERA_GEM_VERSION'] || ENV['GEM_HIERA_VERSION'] || ENV['HIERA_LOCATION']
+gem "hiera", *location_for(hieraversion) if hieraversion
+
+# For Windows dependencies, these could be required based on the version of
+# Puppet you are requiring. Anything greater than v3.5.0 is going to have
+# Windows-specific dependencies dictated by the gem itself. The other scenario
+# is when you are faking out Puppet to use a local file path / git path.
+explicitly_require_windows_gems = false
+puppet_gem_location = gem_type(puppetversion)
+# This is not a perfect answer to the version check
+if puppet_gem_location != :gem || puppetversion < '3.5.0'
+ if Gem::Platform.local.os == 'mingw32'
+ explicitly_require_windows_gems = true
+ end
+
+ if puppet_gem_location == :gem
+ # If facterversion hasn't been specified and we are
+ # looking for a Puppet Gem version less than 3.5.0, we
+ # need to ensure we get a good Facter for specs.
+ gem "facter",">= 1.6.11","<= 1.7.5",:require => false unless facterversion
+ # If hieraversion hasn't been specified and we are
+ # looking for a Puppet Gem version less than 3.5.0, we
+ # need to ensure we get a good Hiera for specs.
+ gem "hiera",">= 1.0.0","<= 1.3.0",:require => false unless hieraversion
+ end
+end
+
+if explicitly_require_windows_gems
+ # This also means Puppet Gem less than 3.5.0 - this has been tested back
+ # to 3.0.0. Any further back is likely not supported.
+ if puppet_gem_location == :gem
+ gem "ffi", "1.9.0", :require => false
+ gem "win32-eventlog", "0.5.3","<= 0.6.5", :require => false
+ gem "win32-process", "0.6.5","<= 0.7.5", :require => false
+ gem "win32-security", "~> 0.1.2","<= 0.2.5", :require => false
+ gem "win32-service", "0.7.2","<= 0.8.8", :require => false
+ gem "minitar", "0.5.4", :require => false
+ else
+ gem "ffi", "~> 1.9.0", :require => false
+ gem "win32-eventlog", "~> 0.5","<= 0.6.5", :require => false
+ gem "win32-process", "~> 0.6","<= 0.7.5", :require => false
+ gem "win32-security", "~> 0.1","<= 0.2.5", :require => false
+ gem "win32-service", "~> 0.7","<= 0.8.8", :require => false
+ gem "minitar", "~> 0.5.4", :require => false
+ end
+
+ gem "win32-dir", "~> 0.3","<= 0.4.9", :require => false
+ gem "win32console", "1.3.2", :require => false if RUBY_VERSION =~ /^1\./
+
+ # Puppet less than 3.7.0 requires these.
+ # Puppet 3.5.0+ will control the actual requirements.
+ # These are listed in formats that work with all versions of
+ # Puppet from 3.0.0 to 3.6.x. After that, these were no longer used.
+ # We do not want to allow newer versions than what came out after
+ # 3.6.x to be used as they constitute some risk in breaking older
+ # functionality. So we set these to exact versions.
+ gem "sys-admin", "1.5.6", :require => false
+ gem "win32-api", "1.4.8", :require => false
+ gem "win32-taskscheduler", "0.2.2", :require => false
+ gem "windows-api", "0.4.3", :require => false
+ gem "windows-pr", "1.2.3", :require => false
+else
+ if Gem::Platform.local.os == 'mingw32'
+ # If we're using a Puppet gem on windows, which handles its own win32-xxx gem dependencies (Pup 3.5.0 and above), set maximum versions
+ # Required due to PUP-6445
+ gem "win32-dir", "<= 0.4.9", :require => false
+ gem "win32-eventlog", "<= 0.6.5", :require => false
+ gem "win32-process", "<= 0.7.5", :require => false
+ gem "win32-security", "<= 0.2.5", :require => false
+ gem "win32-service", "<= 0.8.8", :require => false
+ end
+end
+
+# Evaluate Gemfile.local if it exists
+if File.exists? "#{__FILE__}.local"
+ eval(File.read("#{__FILE__}.local"), binding)
+end
+
+# Evaluate ~/.gemfile if it exists
+if File.exists?(File.join(Dir.home, '.gemfile'))
+ eval(File.read(File.join(Dir.home, '.gemfile')), binding)
+end
+
+# vim:ft=ruby
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/LICENSE b/modules/utilities/windows/shells/puppetlabs_powershell_local/LICENSE
new file mode 100644
index 000000000..7a4a3ea24
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
\ No newline at end of file
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/MAINTAINERS.md b/modules/utilities/windows/shells/puppetlabs_powershell_local/MAINTAINERS.md
new file mode 100644
index 000000000..19a6f1a0f
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/MAINTAINERS.md
@@ -0,0 +1,6 @@
+## Maintenance
+
+Maintainers:
+ - Puppet Windows Team `windows |at| puppet |dot| com`
+
+Tickets: https://tickets.puppet.com/browse/MODULES. Make sure to set component to `powershell`.
\ No newline at end of file
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/NOTICE b/modules/utilities/windows/shells/puppetlabs_powershell_local/NOTICE
new file mode 100644
index 000000000..c2112aa68
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/NOTICE
@@ -0,0 +1,16 @@
+Puppet Module - puppetlabs-powershell
+
+Copyright 2013 Josh Cooper, original author
+Copyright 2013 - 2016 Puppet, Inc.
+
+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.
\ No newline at end of file
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/README.md b/modules/utilities/windows/shells/puppetlabs_powershell_local/README.md
new file mode 100644
index 000000000..851e8289c
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/README.md
@@ -0,0 +1,213 @@
+# powershell
+
+#### Table of Contents
+
+1. [Overview](#overview)
+2. [Module Description - What the module does and why it is useful](#module-description)
+3. [Setup - The basics of getting started with powershell](#setup)
+ * [Setup requirements](#setup-requirements)
+ * [Beginning with powershell](#beginning-with-powershell)
+4. [Usage - Configuration options and additional functionality](#usage)
+ * [External files and exit codes](#external-files-and-exit-codes)
+ * [Console Error Output](#console-error-output)
+5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
+5. [Limitations - OS compatibility, etc.](#limitations)
+6. [Development - Guide for contributing to the module](#development)
+
+## Overview
+
+This module adds a new exec provider capable of executing PowerShell commands.
+
+## Module Description
+
+Puppet provides a built-in `exec` type that is capable of executing commands. This module adds a `powershell` provider to the `exec` type, which enables `exec` parameters, listed below. This module is particularly helpful if you need to run PowerShell commands but don't know the details about how PowerShell is executed, because you can run PowerShell commands in Puppet without the module.
+
+## Setup
+
+### Requirements
+
+This module requires PowerShell to be installed and the `powershell.exe` to be available in the system PATH.
+
+### Beginning with powershell
+
+The powershell module adapts the Puppet [exec](http://docs.puppet.com/references/stable/type.html#exec) resource to run PowerShell commands. To get started, install the module and declare 'powershell' in `provider` with the applicable command.
+
+~~~ puppet
+exec { 'RESOURCENAME':
+ command => '$(SOMECOMMAND)',
+ provider => powershell,
+}
+~~~
+
+## Usage
+
+When using `exec` resources with the `powershell` provider, the `command` parameter must be single-quoted to prevent Puppet from interpolating `$(..)`.
+
+For instance, to rename the Guest account:
+
+~~~ puppet
+exec { 'rename-guest':
+ command => '$(Get-WMIObject Win32_UserAccount -Filter "Name=\'guest\'").Rename("new-guest")',
+ unless => 'if (Get-WmiObject Win32_UserAccount -Filter "Name=\'guest\'") { exit 1 }',
+ provider => powershell,
+}
+~~~
+
+Note that the example uses the `unless` parameter to make the resource idempotent. The `command` is only executed if the Guest account does not exist, as indicated by `unless` returning 0.
+
+**Note:** PowerShell variables (such as `$_`) must be escaped in Puppet manifests either using backslashes or single quotes.
+
+Alternately, you can put the PowerShell code for the `command`, `onlyif`, and `unless` parameters into separate files, and then invoke the file function in the resource. You could also use templates and the `template()` function if the PowerShell scripts need access to variables from Puppet.
+
+~~~ puppet
+exec { 'rename-guest':
+ command => file('guest/rename-guest.ps1'),
+ onlyif => file('guest/guest-exists.ps1'),
+ provider => powershell,
+ logoutput => true,
+}
+~~~
+
+Each file is a PowerShell script that should be in the module's `files/` folder.
+
+For example, here is the script at: `guest/files/rename-guest.ps1`
+
+~~~ powershell
+$obj = $(Get-WMIObject Win32_UserAccount -Filter "Name='Guest'")
+$obj.Rename("OtherGuest")
+~~~
+
+This has the added benefit of not requiring escaping '$' in the PowerShell code. Note that the files must have DOS linefeeds or they will not work as expected. One tool for converting UNIX linefeeds to DOS linefeeds is [unix2dos](http://freecode.com/projects/dos2unix).
+
+### External files and exit codes
+
+If you are calling external files, such as other PowerShell scripts or executables, be aware that the last executed script's exitcode is used by Puppet to determine whether the command was successful.
+
+For example, if the file `C:\fail.ps1` contains the following PowerShell script:
+
+~~~ powershell
+& cmd /c EXIT 5
+& cmd /c EXIT 1
+~~~
+
+and we use the following Puppet manifest:
+
+~~~ puppet
+exec { 'test':
+ command => '& C:\fail.ps1',
+ provider => powershell,
+}
+~~~
+
+Then the `exec['test']` resource will always fail, because the last exit code from the external file `C:\fail.ps1` is `1`. This behavior might have unintended consequences if you combine multiple external files.
+
+To stop this behavior, ensure that you use explicit `Exit` statements in your PowerShell scripts. For example, we changed the Puppet manifest from the above to:
+
+~~~ puppet
+exec { 'test':
+ command => '& C:\fail.ps1; Exit 0',
+ provider => powershell,
+}
+~~~
+
+This will always succeed because the `Exit 0` statement overrides the exit code from the `C:\fail.ps1` script.
+
+### Console Error Output
+
+The PowerShell module internally captures output sent to the .NET `[System.Console]::Error` stream like:
+
+~~~ puppet
+exec { 'test':
+ command => '[System.Console]::Error.WriteLine("foo")',
+ provider => powershell,
+}
+~~~
+
+However, to produce output from a script, use the `Write-` prefixed cmdlets such as `Write-Output`, `Write-Debug` and `Write-Error`.
+
+## Reference
+
+#### Provider
+
+* powershell: Adapts the Puppet `exec` resource to run PowerShell commands.
+
+#### Parameters
+
+All parameters are optional.
+
+##### `creates`
+
+Specifies the file to look for before running the command. The command runs only if the file doesn't exist. **Note: This parameter does not create a file, it only looks for one.** Valid options: A string of the path to the file. Default: Undefined.
+
+##### `cwd`
+
+Sets the directory from which to run the command. Valid options: A string of the directory path. Default: Undefined.
+
+##### `command`
+
+Specifies the actual PowerShell command to execute. Must either be fully qualified or a search path for the command must be provided. Valid options: String. Default: Undefined.
+
+##### `environment`
+
+Sets additional environment variables to set for a command. Valid options: String, or an array of multiple options. Default: Undefined.
+
+##### `logoutput`
+
+Defines whether to log command output in addition to logging the exit code. If you specify 'on_failure', it only logs the output when the command has an exit code that does not match any value specified by the `returns` attribute. Valid options: true, false, and 'on_failure'. Default: 'on_failure'.
+
+##### `onlyif`
+
+Runs the exec only if the command returns 0. Valid options: String. Default: Undefined.
+
+##### `path`
+
+Specifies the search path used for command execution. Valid options: String of the path, an array, or a semicolon-separated list. Default: Undefined.
+
+##### `refresh`
+
+Refreshes the command. Valid options: String. Default: Undefined.
+
+##### `refreshonly`
+
+Refreshes the command only when a dependent object is changed. Used with `subscribe` and `notify` [metaparameters](http://docs.puppet.com/references/latest/metaparameter.html). Valid options: true, false. Default: false.
+
+##### `returns`
+
+Lists the expected return code(s). If the executed command returns something else, an error is returned. Valid options: An array of acceptable return codes or a single value. Default: 0.
+
+##### `timeout`
+
+Sets the maximum time in seconds that the command should take. Valid options: Number or string representation of a number. Default: 300.
+
+##### `tries`
+
+Determines the number of times execution of the command should be attempted. Valid options: Number or a string representation of a number. Default: '1'.
+
+##### `try_sleep`
+
+Specifies the time to sleep in seconds between `tries`. Valid options: Number or a string representation of a number. Default: Undefined.
+
+##### `unless`
+
+Runs the `exec`, unless the command returns 0. Valid options: String. Default: Undefined.
+
+## Limitations
+
+* Only supported on Windows Server 2008 and above, and Windows 7 and above.
+
+* Only supported on Powershell 2.0 and above.
+
+* When using here-strings in inline or templated scripts executed by this module, you must use the double-quote style syntax that begins with `@"` and ends with `"@`. The single-quote syntax that begins with `@'` and ends with `'@` is not supported.
+
+ Note that any external .ps1 script file loaded or executed with the call operator `&` is not subject to this limitation and can contain any style here-string. For instance, the script file external-code.ps1 can contain any style of here-string:
+
+ ```
+ exec { 'external-code':
+ command => '& C:\external-code.ps1',
+ provider => powershell,
+ }
+ ```
+
+## Development
+
+Puppet modules on the Puppet Forge are open projects, and community contributions are essential for keeping them great. We can’t access the huge number of platforms and myriad hardware, software, and deployment configurations that Puppet is intended to serve. We want to keep it as easy as possible to contribute changes so that our modules work in your environment. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. For more information, see our [module contribution guide.](https://docs.puppet.com/forge/contributing.html)
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/Rakefile b/modules/utilities/windows/shells/puppetlabs_powershell_local/Rakefile
new file mode 100644
index 000000000..dd489f127
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/Rakefile
@@ -0,0 +1,28 @@
+require 'puppetlabs_spec_helper/rake_tasks'
+require 'rspec/core/rake_task'
+
+task :default => :unit
+
+desc "Unit tests"
+RSpec::Core::RakeTask.new(:unit) do |t,args|
+ t.pattern = 'spec/unit'
+ t.rspec_opts = '--color'
+ t.verbose = true
+end
+
+desc "Beaker namespace"
+RSpec::Core::RakeTask.new('beaker:rspec:test:pe',:host) do |t,args|
+ args.with_defaults({:host => 'default'})
+ ENV['BEAKER_set'] = args[:host]
+ t.pattern = 'spec/acceptance'
+ t.rspec_opts = '--color'
+ t.verbose = true
+end
+
+RSpec::Core::RakeTask.new('beaker:rspec:test:git',:host) do |t,args|
+ args.with_defaults({:host => 'default'})
+ ENV['BEAKER_set'] = args[:host]
+ t.pattern = 'spec/acceptance'
+ t.rspec_opts = '--color'
+ t.verbose = true
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/appveyor.yml b/modules/utilities/windows/shells/puppetlabs_powershell_local/appveyor.yml
new file mode 100644
index 000000000..cb3370781
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/appveyor.yml
@@ -0,0 +1,44 @@
+version: 1.1.x.{build}
+skip_commits:
+ message: /^\(?doc\)?.*/
+clone_depth: 10
+init:
+- SET
+- 'mkdir C:\ProgramData\PuppetLabs\code && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\facter && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\hiera && exit 0'
+- 'mkdir C:\ProgramData\PuppetLabs\puppet\var && exit 0'
+environment:
+ matrix:
+ - PUPPET_GEM_VERSION: ~> 3.0
+ RUBY_VER: 193
+ - PUPPET_GEM_VERSION: ~> 3.0
+ RUBY_VER: 200-x64
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 21
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 21-x64
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 23
+ - PUPPET_GEM_VERSION: ~> 4.0
+ RUBY_VER: 23-x64
+ - PUPPET_GEM_VERSION: 3.0.0
+ RUBY_VER: 193
+matrix:
+ fast_finish: true
+install:
+- SET PATH=C:\Ruby%RUBY_VER%\bin;%PATH%
+- bundle install --jobs 4 --retry 2 --without system_tests
+- type Gemfile.lock
+build: off
+test_script:
+- bundle exec puppet -V
+- ruby -v
+- bundle exec rspec spec/unit spec/integration -fd -b
+notifications:
+- provider: Email
+ to:
+ - nobody@nowhere.com
+ on_build_success: false
+ on_build_failure: false
+ on_build_status_changed: false
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/checksums.json b/modules/utilities/windows/shells/puppetlabs_powershell_local/checksums.json
new file mode 100644
index 000000000..be90cd965
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/checksums.json
@@ -0,0 +1,34 @@
+{
+ "CHANGELOG.md": "631181084f8f3cb5803f2f7d7d7bb721",
+ "CONTRIBUTING.md": "2398672bb3e7c2a5ec11a9cea0f809e9",
+ "Gemfile": "9773686d3171abe4a41875e17244518b",
+ "LICENSE": "175792518e4ac015ab6696d16c4f607e",
+ "MAINTAINERS.md": "73b03f2d924a8c904b8ba2aa8034a756",
+ "NOTICE": "89aabfb0d9b2b991537fe6529d55e8f8",
+ "README.md": "56a6af068faf00916a87cada55a36c49",
+ "Rakefile": "b7fa47f72583e517f019ab58f48853f2",
+ "appveyor.yml": "b4ef9f7c0f183338230ef171465c9622",
+ "lib/puppet/provider/exec/powershell.rb": "0c7788b88dcdd69156f5ee91e9617f05",
+ "lib/puppet_x/puppetlabs/powershell/compatible_powershell_version.rb": "d85ac07f9b23964804f400737c846fb8",
+ "lib/puppet_x/puppetlabs/powershell/powershell_manager.rb": "2811c13a1954d3cf8eb456c4fa9c80ef",
+ "lib/puppet_x/puppetlabs/powershell/powershell_version.rb": "0c8298e41ff0216f1f6ad32d2a5ce010",
+ "lib/puppet_x/templates/init_ps.ps1": "823c389299afb88bfbf56f6a8247bef0",
+ "metadata.json": "c5c4893b73c5c98ba9a1b9ab5857317c",
+ "spec/acceptance/exec_powershell_spec.rb": "8a8fa3a2c2435ebf349bedc5be4f004c",
+ "spec/acceptance/files/param_script.ps1": "942dc0c5aaf3880732b610bdae165cba",
+ "spec/acceptance/files/services.ps1": "6281d2a9de037d17514ba1618bc8d63f",
+ "spec/acceptance/nodesets/windows-2003-i386.yml": "ee44e67756ecd0c07e08ae8ec91684cd",
+ "spec/acceptance/nodesets/windows-2003-x86_64.yml": "33f48b5729235a6764880f18ddfdf976",
+ "spec/acceptance/nodesets/windows-2008-x86_64.yml": "78c97561cc383cde9e18d612aeafa335",
+ "spec/acceptance/nodesets/windows-2008r2-x86_64.yml": "bf563a036e054f0f50cc271d6cd819e6",
+ "spec/acceptance/nodesets/windows-2012-x86_64.yml": "678b95ea73e959c79d8723f35f66bc54",
+ "spec/acceptance/nodesets/windows-2012r2-x86_64.yml": "f1f44c34a702307974dd1cff2909f402",
+ "spec/exit-27.ps1": "97c8ee0412556a78aa7ebd42aaa00435",
+ "spec/integration/puppet_x/puppetlabs/powershell_manager_spec.rb": "37e01fcae539ccc83dfe6efbcebc304b",
+ "spec/spec.opts": "a600ded995d948e393fbe2320ba8e51c",
+ "spec/spec_helper.rb": "c8dba9cd09f111851024bbe1574b1228",
+ "spec/spec_helper_acceptance.rb": "8869e99d1b25a73cb075ecf663d68a37",
+ "spec/unit/provider/exec/powershell_spec.rb": "0225775557ea001e978570fb930bd45b",
+ "spec/unit/puppet_x/puppetlabs/powershell/compatible_powershell_version_spec.rb": "023da68379904c6d424193343d227c1d",
+ "spec/unit/puppet_x/puppetlabs/powershell/powershell_version_spec.rb": "4e60382cc58e0466388d71299d9e8d0a"
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet/provider/exec/powershell.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet/provider/exec/powershell.rb
new file mode 100644
index 000000000..e2f13d9f0
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet/provider/exec/powershell.rb
@@ -0,0 +1,139 @@
+require 'puppet/provider/exec'
+require File.join(File.dirname(__FILE__), '../../../puppet_x/puppetlabs/powershell/compatible_powershell_version')
+require File.join(File.dirname(__FILE__), '../../../puppet_x/puppetlabs/powershell/powershell_manager')
+
+Puppet::Type.type(:exec).provide :powershell, :parent => Puppet::Provider::Exec do
+ confine :operatingsystem => :windows
+
+ commands :powershell =>
+ if File.exists?("#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe")
+ "#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe"
+ elsif File.exists?("#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe")
+ "#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe"
+ else
+ 'powershell.exe'
+ end
+
+ desc <<-EOT
+ Executes Powershell commands. One of the `onlyif`, `unless`, or `creates`
+ parameters should be specified to ensure the command is idempotent.
+
+ Example:
+ # Rename the Guest account
+ exec { 'rename-guest':
+ command => '$(Get-WMIObject Win32_UserAccount -Filter "Name=\'guest\'").Rename("new-guest")',
+ unless => 'if (Get-WmiObject Win32_UserAccount -Filter "Name=\'guest\'") { exit 1 }',
+ provider => powershell,
+ }
+ EOT
+
+ POWERSHELL_UPGRADE_MSG = <<-UPGRADE
+ Currently, the PowerShell module has reduced v1 functionality on this agent
+ due to one or more of the following conditions:
+
+ - Puppet 3.x (non-x64 version)
+
+ Puppet 3.x uses a Ruby version that requires a library to support a colored
+ console. Unfortunately this library prevents the PowerShell module from
+ using a shared PowerShell process to dramatically improve the performance of
+ resource application.
+
+ - PowerShell v2 with .NET Framework 2.0
+
+ PowerShell v2 works with both .NET Framework 2.0 and .NET Framework 3.5.
+ To be able to use the enhancements, we require at least .NET Framework 3.5.
+ Typically you will only see this on a base Windows Server 2008 (and R2)
+ install.
+
+ To enable these improvements, it is suggested to upgrade to any x64 version of
+ Puppet (including 3.x), or to a Puppet version newer than 3.x and ensure you
+ have at least .NET Framework 3.5 installed.
+ UPGRADE
+
+ def self.upgrade_message
+ Puppet.warning POWERSHELL_UPGRADE_MSG if !@upgrade_warning_issued
+ @upgrade_warning_issued = true
+ end
+
+ def self.powershell_args
+ ps_args = ['-NoProfile', '-NonInteractive', '-NoLogo', '-ExecutionPolicy', 'Bypass']
+ ps_args << '-Command' if !PuppetX::PowerShell::PowerShellManager.supported?
+
+ ps_args
+ end
+
+ def ps_manager
+ debug_output = Puppet::Util::Log.level == :debug
+ manager_args = "#{command(:powershell)} #{self.class.powershell_args().join(' ')}"
+ PuppetX::PowerShell::PowerShellManager.instance(manager_args, debug_output)
+ end
+
+ def run(command, check = false)
+ if !PuppetX::PowerShell::PowerShellManager.supported?
+ self.class.upgrade_message
+ write_script(command) do |native_path|
+ # Ideally, we could keep a handle open on the temp file in this
+ # process (to prevent TOCTOU attacks), and execute powershell
+ # with -File . But powershell complains that it can't open
+ # the file for exclusive access. If we close the handle, then an
+ # attacker could modify the file before we invoke powershell. So
+ # we redirect powershell's stdin to read from the file. Current
+ # versions of Windows use per-user temp directories with strong
+ # permissions, but I'd rather not make (poor) assumptions.
+ return super("cmd.exe /c \"\"#{native_path(command(:powershell))}\" #{legacy_args} -Command - < \"#{native_path}\"\"", check)
+ end
+ else
+ working_dir = resource[:cwd]
+ if (!working_dir.nil?)
+ self.fail "Working directory '#{working_dir}' does not exist" unless File.directory?(working_dir)
+ end
+ timeout_ms = resource[:timeout].nil? ? nil : resource[:timeout] * 1000
+
+ result = ps_manager.execute(command,timeout_ms,working_dir)
+
+ stdout = result[:stdout]
+ native_out = result[:native_stdout]
+ stderr = result[:stderr]
+ exit_code = result[:exitcode]
+
+ unless stderr.nil?
+ stderr.each { |e| Puppet.debug "STDERR: #{e.chop}" unless e.empty? }
+ end
+
+ Puppet.debug "STDERR: #{result[:errormessage]}" unless result[:errormessage].nil?
+
+ output = Puppet::Util::Execution::ProcessOutput.new(stdout.to_s + native_out.to_s, exit_code)
+
+ return output, output
+ end
+ end
+
+ def checkexe(command)
+ end
+
+ def validatecmd(command)
+ true
+ end
+
+ private
+ def write_script(content, &block)
+ Tempfile.open(['puppet-powershell', '.ps1']) do |file|
+ file.puts(content)
+ file.puts()
+ file.flush
+ yield native_path(file.path)
+ end
+ end
+
+ def native_path(path)
+ if Puppet::Util::Platform.windows?
+ path.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
+ else
+ path
+ end
+ end
+
+ def legacy_args
+ '-NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass'
+ end
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/compatible_powershell_version.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/compatible_powershell_version.rb
new file mode 100644
index 000000000..b97b06a2f
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/compatible_powershell_version.rb
@@ -0,0 +1,51 @@
+require File.join(File.dirname(__FILE__), 'powershell_version')
+
+module PuppetX
+ module PuppetLabs
+ module PowerShell
+ class CompatiblePowerShellVersion
+ def self.compatible_version?
+ value = false
+
+ powershell_version = PuppetX::PuppetLabs::PowerShell::PowerShellVersion.version
+
+ return false if powershell_version.nil?
+
+ # PowerShell v1 - definitely not good to go. Really the entire module
+ # may not even work but I digress
+ return false if Gem::Version.new(powershell_version) < Gem::Version.new(2)
+
+ # PowerShell v3+, we are good to go b/c .NET 4+
+ # https://msdn.microsoft.com/en-us/powershell/scripting/setup/windows-powershell-system-requirements
+ # Look at Microsoft .NET Framwork Requirements section.
+ if Gem::Version.new(powershell_version) >= Gem::Version.new(3)
+ return true
+ end
+
+ # If we are using PowerShell v2, we need to see what the latest
+ # version of .NET is that we have
+ # https://msdn.microsoft.com/en-us/library/hh925568.aspx
+ if Puppet::Util::Platform.windows?
+ require 'win32/registry'
+
+ begin
+ # At this point in the check, PowerShell is using .NET Framework
+ # 2.x family, so we only need to verify v3.5 key exists.
+ # If we were verifying all compatible types we would look for
+ # any of these keys: v3.5, v4.0, v4
+ hive = Win32::Registry::HKEY_LOCAL_MACHINE
+ # redirection doesn't actually matter here - disable it anyway
+ hive.open('SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', Win32::Registry::KEY_READ | 0x100) do |reg|
+ value = true
+ end
+ rescue Win32::Registry::Error => e
+ value = false
+ end
+ end
+
+ value
+ end
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/powershell_manager.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/powershell_manager.rb
new file mode 100644
index 000000000..38ab51487
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/powershell_manager.rb
@@ -0,0 +1,328 @@
+require 'rexml/document'
+require 'securerandom'
+require 'open3'
+require 'base64'
+require File.join(File.dirname(__FILE__), 'compatible_powershell_version')
+
+module PuppetX
+ module PowerShell
+ class PowerShellManager
+ @@instances = {}
+
+ def self.instance(cmd, debug = false)
+ key = cmd + debug.to_s
+ manager = @@instances[key]
+
+ if manager.nil? || !manager.alive?
+ # ignore any errors trying to tear down this unusable instance
+ manager.exit if manager rescue nil
+ @@instances[key] = PowerShellManager.new(cmd, debug)
+ end
+
+ @@instances[key]
+ end
+
+ def self.win32console_enabled?
+ @win32console_enabled ||= defined?(Win32) &&
+ defined?(Win32::Console) &&
+ Win32::Console.class == Class
+ end
+
+ def self.compatible_version_of_powershell?
+ @compatible_powershell_version ||= PuppetX::PuppetLabs::PowerShell::CompatiblePowerShellVersion.compatible_version?
+ end
+
+ def self.supported?
+ Puppet::Util::Platform.windows? &&
+ compatible_version_of_powershell? &&
+ !win32console_enabled?
+ end
+
+ def initialize(cmd, debug)
+ @usable = true
+
+ named_pipe_name = "#{SecureRandom.uuid}PuppetPsHost"
+
+ ps_args = ['-File', self.class.init_path, "\"#{named_pipe_name}\""]
+ ps_args << '"-EmitDebugOutput"' if debug
+ # @stderr should never be written to as PowerShell host redirects output
+ stdin, @stdout, @stderr, @ps_process = Open3.popen3("#{cmd} #{ps_args.join(' ')}")
+ stdin.close
+
+ Puppet.debug "#{Time.now} #{cmd} is running as pid: #{@ps_process[:pid]}"
+
+ pipe_path = "\\\\.\\pipe\\#{named_pipe_name}"
+ # wait for the pipe server to signal ready, and fail if no response in 10 seconds
+
+ # wait up to 10 seconds in 0.2 second intervals to be able to open the pipe
+ 50.times do
+ begin
+ # pipe is opened in binary mode and must always
+ @pipe = File.open(pipe_path, 'r+b')
+ break
+ rescue
+ sleep 0.2
+ end
+ end
+
+ fail "Failure waiting for PowerShell process #{@ps_process[:pid]} to start pipe server" if @pipe.nil?
+
+ Puppet.debug "#{Time.now} PowerShell initialization complete for pid: #{@ps_process[:pid]}"
+
+ at_exit { exit }
+ end
+
+ def alive?
+ # powershell process running
+ @ps_process.alive? &&
+ # explicitly set during a read / write failure, like broken pipe EPIPE
+ @usable &&
+ # an explicit failure state might not have been hit, but IO may be closed
+ self.class.is_stream_valid?(@pipe) &&
+ self.class.is_stream_valid?(@stdout) &&
+ self.class.is_stream_valid?(@stderr)
+ end
+
+ def execute(powershell_code, timeout_ms = nil, working_dir = nil)
+ code = make_ps_code(powershell_code, timeout_ms, working_dir)
+
+
+ # err is drained stderr pipe (not captured by redirection inside PS)
+ # or during a failure, a Ruby callstack array
+ out, native_stdout, err = exec_read_result(code)
+
+ # an error was caught during execution that has invalidated any results
+ return { :exitcode => -1, :stderr => err } if !@usable && out.nil?
+
+ out[:exitcode] = out[:exitcode].to_i if !out[:exitcode].nil?
+ # if err contains data it must be "real" stderr output
+ # which should be appended to what PS has already captured
+ out[:stderr] = out[:stderr].nil? ? [] : [out[:stderr]]
+ out[:stderr] += err if !err.nil?
+ out[:native_stdout] = native_stdout
+
+ out
+ end
+
+ def exit
+ @usable = false
+
+ Puppet.debug "PowerShellManager exiting..."
+ # pipe may still be open, but if stdout / stderr are dead PS process is in trouble
+ # and will block forever on a write to the pipe
+ # its safer to close pipe on Ruby side, which gracefully shuts down PS side
+ @pipe.close if !@pipe.closed?
+ @stdout.close if !@stdout.closed?
+ @stderr.close if !@stderr.closed?
+
+ # wait up to 2 seconds for the watcher thread to fully exit
+ @ps_process.join(2)
+ end
+
+ def self.init_path
+ # a PowerShell -File compatible path to bootstrap the instance
+ path = File.expand_path('../../../templates', __FILE__)
+ path = File.join(path, 'init_ps.ps1').gsub('/', '\\')
+ "\"#{path}\""
+ end
+
+ def make_ps_code(powershell_code, timeout_ms = nil, working_dir = nil)
+ begin
+ timeout_ms = Integer(timeout_ms)
+ # Lower bound protection. The polling resolution is only 50ms
+ if (timeout_ms < 50) then timeout_ms = 50 end
+ rescue
+ timeout_ms = 300 * 1000
+ end
+ # PS side expects Invoke-PowerShellUserCode is always the return value here
+ <<-CODE
+$params = @{
+ Code = @'
+#{powershell_code}
+'@
+ TimeoutMilliseconds = #{timeout_ms}
+ WorkingDirectory = "#{working_dir}"
+}
+
+Invoke-PowerShellUserCode @params
+ CODE
+ end
+
+ private
+
+ def self.is_readable?(stream, timeout = 0.5)
+ raise Errno::EPIPE if !is_stream_valid?(stream)
+ read_ready = IO.select([stream], [], [], timeout)
+ read_ready && stream == read_ready[0][0]
+ end
+
+ # when a stream has been closed by handle, but Ruby still has a file
+ # descriptor for it, it can be tricky to determine that it's actually dead
+ # the .fileno will still return an int, and calling get_osfhandle against
+ # it returns what the CRT thinks is a valid Windows HANDLE value, but
+ # that may no longer exist
+ def self.is_stream_valid?(stream)
+ # when a stream is closed, its obviously invalid, but Ruby doesn't always know
+ !stream.closed? &&
+ # so calling stat will yield an EBADF when underlying OS handle is bad
+ # as this resolves to a HANDLE and then calls the Windows API
+ !stream.stat.nil?
+ # any exceptions mean the stream is dead
+ rescue
+ false
+ end
+
+ # copied directly from Puppet 3.7+ to support Puppet 3.5+
+ def self.wide_string(str)
+ # ruby (< 2.1) does not respect multibyte terminators, so it is possible
+ # for a string to contain a single trailing null byte, followed by garbage
+ # causing buffer overruns.
+ #
+ # See http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?revision=41920&view=revision
+ newstr = str + "\0".encode(str.encoding)
+ newstr.encode!('UTF-16LE')
+ end
+
+ # mutates the given bytes, removing the length prefixed vaule
+ def self.read_length_prefixed_string(bytes)
+ # 32-bit integer in Little Endian format
+ length = bytes.slice!(0, 4).unpack('V').first
+ return nil if length == 0
+ bytes.slice!(0, length).force_encoding(Encoding::UTF_8)
+ end
+
+ # bytes is a binary string containing a list of length-prefixed
+ # key / value pairs (of UTF-8 encoded strings)
+ # this method mutates the incoming value
+ def self.ps_output_to_hash(bytes)
+ hash = {}
+ while !bytes.empty?
+ hash[read_length_prefixed_string(bytes).to_sym] = read_length_prefixed_string(bytes)
+ end
+
+ hash
+ end
+
+ # 1 byte command identifier
+ # 0 - Exit
+ # 1 - Execute
+ def pipe_command(command)
+ case command
+ when :exit
+ "\x00"
+ when :execute
+ "\x01"
+ end
+ end
+
+ # Data format is:
+ # 4 bytes - Little Endian encoded 32-bit integer length of string
+ # Intel CPUs are little endian, hence the .NET Framework typically is
+ # variable length - UTF8 encoded string bytes
+ def pipe_data(data)
+ msg = data.encode(Encoding::UTF_8)
+ # https://ruby-doc.org/core-1.9.3/Array.html#method-i-pack
+ [msg.bytes.length].pack('V') + msg.force_encoding(Encoding::BINARY)
+ end
+
+ def write_pipe(input)
+ # for compat with Ruby 2.1 and lower, its important to use syswrite and not write
+ # otherwise the pipe breaks after writing 1024 bytes
+ written = @pipe.syswrite(input)
+ @pipe.flush()
+
+ if written != input.length
+ msg = "Only wrote #{written} out of #{input.length} expected bytes to PowerShell pipe"
+ raise Errno::EPIPE.new(msg)
+ end
+ end
+
+ def read_from_pipe(pipe, timeout = 0.1, &block)
+ if self.class.is_readable?(pipe, timeout)
+ l = pipe.readpartial(4096)
+ Puppet.debug "#{Time.now} PIPE> #{l}"
+ # since readpartial may return a nil at EOF, skip returning that value
+ yield l if !l.nil?
+ end
+
+ nil
+ end
+
+ def drain_pipe_until_signaled(pipe, signal)
+ output = []
+
+ read_from_pipe(pipe) { |s| output << s } until !signal.locked?
+
+ # there's ultimately a bit of a race here
+ # read one more time after signal is received
+ read_from_pipe(pipe, 0) { |s| output << s } until !self.class.is_readable?(pipe)
+
+ # string has been binary up to this point, so force UTF-8 now
+ output == [] ?
+ [] :
+ [output.join('').force_encoding(Encoding::UTF_8)]
+ end
+
+ def read_streams
+ pipe_done_reading = Mutex.new
+ pipe_done_reading.lock
+ start_time = Time.now
+
+ stdout_reader = Thread.new { drain_pipe_until_signaled(@stdout, pipe_done_reading) }
+ stderr_reader = Thread.new { drain_pipe_until_signaled(@stderr, pipe_done_reading) }
+ pipe_reader = Thread.new(@pipe) do |pipe|
+ # read a Little Endian 32-bit integer for length of response
+ expected_response_length = pipe.sysread(4).unpack('V').first
+ return nil if expected_response_length == 0
+
+ # reads the expected bytes as a binary string or fails
+ pipe.sysread(expected_response_length)
+ end
+
+ Puppet.debug "Waited #{Time.now - start_time} total seconds."
+
+ # block until sysread has completed or errors
+ begin
+ output = pipe_reader.value
+ output = self.class.ps_output_to_hash(output) if !output.nil?
+ ensure
+ # signal stdout / stderr readers via mutex
+ # so that Ruby doesn't crash waiting on an invalid event
+ pipe_done_reading.unlock
+ end
+
+ # given redirection on PowerShell side, this should always be empty
+ stdout = stdout_reader.value
+
+ [
+ output,
+ stdout == [] ? nil : stdout.join(''), # native stdout
+ stderr_reader.value # native stderr
+ ]
+ ensure
+ # failsafe if the prior unlock was never reached / Mutex wasn't unlocked
+ pipe_done_reading.unlock if pipe_done_reading.locked?
+ # wait for all non-nil threads to see mutex unlocked and finish
+ [pipe_reader, stdout_reader, stderr_reader].compact.each(&:join)
+ end
+
+ def exec_read_result(powershell_code)
+ write_pipe(pipe_command(:execute))
+ write_pipe(pipe_data(powershell_code))
+ read_streams()
+ # if any pipes are broken, the manager is totally hosed
+ # bad file descriptors mean closed stream handles
+ # EOFError is a closed pipe (could be as a result of tearing down process)
+ rescue Errno::EPIPE, Errno::EBADF, EOFError => e
+ @usable = false
+ return nil, nil, [e.inspect, e.backtrace].flatten
+ # catch closed stream errors specifically
+ rescue IOError => ioerror
+ raise if !ioerror.message.start_with?('closed stream')
+ @usable = false
+ return nil, nil, [ioerror.inspect, ioerror.backtrace].flatten
+ end
+
+ end
+ end
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/powershell_version.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/powershell_version.rb
new file mode 100644
index 000000000..7fe9f7b66
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/puppetlabs/powershell/powershell_version.rb
@@ -0,0 +1,53 @@
+module PuppetX
+ module PuppetLabs
+ module PowerShell
+ class PowerShellVersion
+ end
+ end
+ end
+end
+
+if Puppet::Util::Platform.windows?
+ require 'win32/registry'
+ module PuppetX
+ module PuppetLabs
+ module PowerShell
+ class PowerShellVersion
+ ACCESS_TYPE = Win32::Registry::KEY_READ | 0x100
+ HKLM = Win32::Registry::HKEY_LOCAL_MACHINE
+ PS_ONE_REG_PATH = 'SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine'
+ PS_THREE_REG_PATH = 'SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine'
+ REG_KEY = 'PowerShellVersion'
+
+ def self.version
+ powershell_three_version || powershell_one_version
+ end
+
+ def self.powershell_one_version
+ version = nil
+ begin
+ HKLM.open(PS_ONE_REG_PATH, ACCESS_TYPE) do |reg|
+ version = reg[REG_KEY]
+ end
+ rescue
+ version = nil
+ end
+ version
+ end
+
+ def self.powershell_three_version
+ version = nil
+ begin
+ HKLM.open(PS_THREE_REG_PATH, ACCESS_TYPE) do |reg|
+ version = reg[REG_KEY]
+ end
+ rescue
+ version = nil
+ end
+ version
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/templates/init_ps.ps1 b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/templates/init_ps.ps1
new file mode 100644
index 000000000..5a54284a5
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/lib/puppet_x/templates/init_ps.ps1
@@ -0,0 +1,787 @@
+[CmdletBinding()]
+param (
+ [Parameter(Mandatory = $true)]
+ [String]
+ $NamedPipeName,
+
+ [Parameter(Mandatory = $false)]
+ [Switch]
+ $EmitDebugOutput = $False,
+
+ [Parameter(Mandatory = $false)]
+ [System.Text.Encoding]
+ $Encoding = [System.Text.Encoding]::UTF8
+)
+
+$script:EmitDebugOutput = $EmitDebugOutput
+# Necessary for [System.Console]::Error.WriteLine to roundtrip with UTF-8
+[System.Console]::OutputEncoding = $Encoding
+
+$hostSource = @"
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Globalization;
+using System.IO;
+using System.Management.Automation;
+using System.Management.Automation.Host;
+using System.Security;
+using System.Text;
+using System.Threading;
+
+namespace Puppet
+{
+ public class PuppetPSHostRawUserInterface : PSHostRawUserInterface
+ {
+ public PuppetPSHostRawUserInterface()
+ {
+ buffersize = new Size(120, 120);
+ backgroundcolor = ConsoleColor.Black;
+ foregroundcolor = ConsoleColor.White;
+ cursorposition = new Coordinates(0, 0);
+ cursorsize = 1;
+ }
+
+ private ConsoleColor backgroundcolor;
+ public override ConsoleColor BackgroundColor
+ {
+ get { return backgroundcolor; }
+ set { backgroundcolor = value; }
+ }
+
+ private Size buffersize;
+ public override Size BufferSize
+ {
+ get { return buffersize; }
+ set { buffersize = value; }
+ }
+
+ private Coordinates cursorposition;
+ public override Coordinates CursorPosition
+ {
+ get { return cursorposition; }
+ set { cursorposition = value; }
+ }
+
+ private int cursorsize;
+ public override int CursorSize
+ {
+ get { return cursorsize; }
+ set { cursorsize = value; }
+ }
+
+ private ConsoleColor foregroundcolor;
+ public override ConsoleColor ForegroundColor
+ {
+ get { return foregroundcolor; }
+ set { foregroundcolor = value; }
+ }
+
+ private Coordinates windowposition;
+ public override Coordinates WindowPosition
+ {
+ get { return windowposition; }
+ set { windowposition = value; }
+ }
+
+ private Size windowsize;
+ public override Size WindowSize
+ {
+ get { return windowsize; }
+ set { windowsize = value; }
+ }
+
+ private string windowtitle;
+ public override string WindowTitle
+ {
+ get { return windowtitle; }
+ set { windowtitle = value; }
+ }
+
+ public override bool KeyAvailable
+ {
+ get { return false; }
+ }
+
+ public override Size MaxPhysicalWindowSize
+ {
+ get { return new Size(165, 66); }
+ }
+
+ public override Size MaxWindowSize
+ {
+ get { return new Size(165, 66); }
+ }
+
+ public override void FlushInputBuffer()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override BufferCell[,] GetBufferContents(Rectangle rectangle)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override KeyInfo ReadKey(ReadKeyOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void ScrollBufferContents(Rectangle source, Coordinates destination, Rectangle clip, BufferCell fill)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetBufferContents(Rectangle rectangle, BufferCell fill)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class PuppetPSHostUserInterface : PSHostUserInterface
+ {
+ private PuppetPSHostRawUserInterface _rawui;
+ private StringBuilder _sb;
+ private StringWriter _errWriter;
+ private StringWriter _outWriter;
+
+ public PuppetPSHostUserInterface()
+ {
+ _sb = new StringBuilder();
+ _errWriter = new StringWriter(new StringBuilder());
+ // NOTE: StringWriter / StringBuilder are not technically thread-safe
+ // but PowerShell Write-XXX cmdlets and System.Console.Out.WriteXXX
+ // should not be executed concurrently within PowerShell, so should be safe
+ _outWriter = new StringWriter(_sb);
+ }
+
+ public override PSHostRawUserInterface RawUI
+ {
+ get
+ {
+ if ( _rawui == null){
+ _rawui = new PuppetPSHostRawUserInterface();
+ }
+ return _rawui;
+ }
+ }
+
+ public void ResetConsoleStreams()
+ {
+ System.Console.SetError(_errWriter);
+ System.Console.SetOut(_outWriter);
+ }
+
+ public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
+ {
+ _sb.Append(value);
+ }
+
+ public override void Write(string value)
+ {
+ _sb.Append(value);
+ }
+
+ public override void WriteDebugLine(string message)
+ {
+ _sb.AppendLine("DEBUG: " + message);
+ }
+
+ public override void WriteErrorLine(string value)
+ {
+ _sb.AppendLine(value);
+ }
+
+ public override void WriteLine(string value)
+ {
+ _sb.AppendLine(value);
+ }
+
+ public override void WriteVerboseLine(string message)
+ {
+ _sb.AppendLine("VERBOSE: " + message);
+ }
+
+ public override void WriteWarningLine(string message)
+ {
+ _sb.AppendLine("WARNING: " + message);
+ }
+
+ public override void WriteProgress(long sourceId, ProgressRecord record)
+ {
+ }
+
+ public string Output
+ {
+ get
+ {
+ _outWriter.Flush();
+ string text = _outWriter.GetStringBuilder().ToString();
+ _outWriter.GetStringBuilder().Length = 0; // Only .NET 4+ has .Clear()
+ return text;
+ }
+ }
+
+ public string StdErr
+ {
+ get
+ {
+ _errWriter.Flush();
+ string text = _errWriter.GetStringBuilder().ToString();
+ _errWriter.GetStringBuilder().Length = 0; // Only .NET 4+ has .Clear()
+ return text;
+ }
+ }
+
+ public override Dictionary Prompt(string caption, string message, Collection descriptions)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override string ReadLine()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override SecureString ReadLineAsSecureString()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class PuppetPSHost : PSHost
+ {
+ private Guid _hostId = Guid.NewGuid();
+ private bool shouldExit;
+ private int exitCode;
+
+ private readonly PuppetPSHostUserInterface _ui = new PuppetPSHostUserInterface();
+
+ public PuppetPSHost () {}
+
+ public bool ShouldExit { get { return this.shouldExit; } }
+ public int ExitCode { get { return this.exitCode; } }
+ public void ResetExitStatus()
+ {
+ this.exitCode = 0;
+ this.shouldExit = false;
+ }
+ public void ResetConsoleStreams()
+ {
+ _ui.ResetConsoleStreams();
+ }
+
+ public override Guid InstanceId { get { return _hostId; } }
+ public override string Name { get { return "PuppetPSHost"; } }
+ public override Version Version { get { return new Version(1, 1); } }
+ public override PSHostUserInterface UI
+ {
+ get { return _ui; }
+ }
+ public override CultureInfo CurrentCulture
+ {
+ get { return Thread.CurrentThread.CurrentCulture; }
+ }
+ public override CultureInfo CurrentUICulture
+ {
+ get { return Thread.CurrentThread.CurrentUICulture; }
+ }
+
+ public override void EnterNestedPrompt() { throw new NotImplementedException(); }
+ public override void ExitNestedPrompt() { throw new NotImplementedException(); }
+ public override void NotifyBeginApplication() { return; }
+ public override void NotifyEndApplication() { return; }
+
+ public override void SetShouldExit(int exitCode)
+ {
+ this.shouldExit = true;
+ this.exitCode = exitCode;
+ }
+ }
+}
+"@
+
+Add-Type -TypeDefinition $hostSource -Language CSharp
+$global:DefaultWorkingDirectory = (Get-Location -PSProvider FileSystem).Path
+
+#this is a string so we can import into our dynamic PS instance
+$global:ourFunctions = @'
+function Get-ProcessEnvironmentVariables
+{
+ $processVars = [Environment]::GetEnvironmentVariables('Process').Keys |
+ % -Begin { $h = @{} } -Process { $h.$_ = (Get-Item Env:\$_).Value } -End { $h }
+
+ # eliminate Machine / User vars so that we have only process vars
+ 'Machine', 'User' |
+ % { [Environment]::GetEnvironmentVariables($_).GetEnumerator() } |
+ ? { $processVars.ContainsKey($_.Name) -and ($processVars[$_.Name] -eq $_.Value) } |
+ % { $processVars.Remove($_.Name) }
+
+ $processVars.GetEnumerator() | Sort-Object Name
+}
+
+function Reset-ProcessEnvironmentVariables
+{
+ param($processVars)
+
+ # query Machine vars from registry, ensuring expansion EXCEPT for PATH
+ $vars = [Environment]::GetEnvironmentVariables('Machine').GetEnumerator() |
+ % -Begin { $h = @{} } -Process { $v = if ($_.Name -eq 'Path') { $_.Value } else { [Environment]::GetEnvironmentVariable($_.Name, 'Machine') }; $h."$($_.Name)" = $v } -End { $h }
+
+ # query User vars from registry, ensuring expansion EXCEPT for PATH
+ [Environment]::GetEnvironmentVariables('User').GetEnumerator() | % {
+ if ($_.Name -eq 'Path') { $vars[$_.Name] += ';' + $_.Value }
+ else
+ {
+ $value = [Environment]::GetEnvironmentVariable($_.Name, 'User')
+ $vars[$_.Name] = $value
+ }
+ }
+
+ $processVars.GetEnumerator() | % { $vars[$_.Name] = $_.Value }
+
+ Remove-Item -Path Env:\* -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Recurse
+
+ $vars.GetEnumerator() | % { Set-Item -Path "Env:\$($_.Name)" -Value $_.Value }
+}
+
+function Reset-ProcessPowerShellVariables
+{
+ param($psVariables)
+ $psVariables | %{
+ $tempVar = $_
+ if(-not(Get-Variable -Name $_.Name -ErrorAction SilentlyContinue)){
+ New-Variable -Name $_.Name -Value $_.Value -Description $_.Description -Option $_.Options -Visibility $_.Visibility
+ }
+ }
+}
+'@
+
+function Invoke-PowerShellUserCode
+{
+ [CmdletBinding()]
+ param(
+ [String]
+ $Code,
+
+ [Int]
+ $TimeoutMilliseconds,
+
+ [String]
+ $WorkingDirectory
+ )
+
+ if ($global:runspace -eq $null){
+ # CreateDefault2 requires PS3
+ if ([System.Management.Automation.Runspaces.InitialSessionState].GetMethod('CreateDefault2')){
+ $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2()
+ }else{
+ $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
+ }
+
+ $global:puppetPSHost = New-Object Puppet.PuppetPSHost
+ $global:runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace($global:puppetPSHost, $sessionState)
+ $global:runspace.Open()
+ }
+
+ try
+ {
+ $ps = $null
+ $global:puppetPSHost.ResetExitStatus()
+ $global:puppetPSHost.ResetConsoleStreams()
+
+ if ($PSVersionTable.PSVersion -ge [Version]'3.0') {
+ $global:runspace.ResetRunspaceState()
+ }
+
+ $ps = [System.Management.Automation.PowerShell]::Create()
+ $ps.Runspace = $global:runspace
+ [Void]$ps.AddScript($global:ourFunctions)
+ $ps.Invoke()
+
+ if ([string]::IsNullOrEmpty($WorkingDirectory)) {
+ [Void]$ps.Runspace.SessionStateProxy.Path.SetLocation($global:DefaultWorkingDirectory)
+ } else {
+ if (-not (Test-Path -Path $WorkingDirectory)) { Throw "Working directory `"$WorkingDirectory`" does not exist" }
+ [Void]$ps.Runspace.SessionStateProxy.Path.SetLocation($WorkingDirectory)
+ }
+
+ if(!$global:environmentVariables){
+ $ps.Commands.Clear()
+ $global:environmentVariables = $ps.AddCommand('Get-ProcessEnvironmentVariables').Invoke()
+ }
+
+ if($PSVersionTable.PSVersion -le [Version]'2.0'){
+ if(!$global:psVariables){
+ $global:psVariables = $ps.AddScript('Get-Variable').Invoke()
+ }
+
+ $ps.Commands.Clear()
+ [void]$ps.AddScript('Get-Variable -Scope Global | Remove-Variable -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue')
+ $ps.Invoke()
+
+ $ps.Commands.Clear()
+ [void]$ps.AddCommand('Reset-ProcessPowerShellVariables').AddParameter('psVariables', $global:psVariables)
+ $ps.Invoke()
+ }
+
+ $ps.Commands.Clear()
+ [Void]$ps.AddCommand('Reset-ProcessEnvironmentVariables').AddParameter('processVars', $global:environmentVariables)
+ $ps.Invoke()
+
+ # we clear the commands before each new command
+ # to avoid command pollution
+ $ps.Commands.Clear()
+ [Void]$ps.AddScript($Code)
+
+ # out-default and MergeMyResults takes all output streams
+ # and writes it to the PSHost we create
+ # this needs to be the last thing executed
+ [void]$ps.AddCommand("out-default");
+
+ # if the call operator & established an exit code, exit with it
+ [Void]$ps.AddScript('if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }')
+
+ if($PSVersionTable.PSVersion -le [Version]'2.0'){
+ $ps.Commands.Commands[0].MergeMyResults([System.Management.Automation.Runspaces.PipelineResultTypes]::Error, [System.Management.Automation.Runspaces.PipelineResultTypes]::Output);
+ }else{
+ $ps.Commands.Commands[0].MergeMyResults([System.Management.Automation.Runspaces.PipelineResultTypes]::All, [System.Management.Automation.Runspaces.PipelineResultTypes]::Output);
+ }
+ $asyncResult = $ps.BeginInvoke()
+
+ if (!$asyncResult.AsyncWaitHandle.WaitOne($TimeoutMilliseconds)){
+ throw "Catastrophic failure: PowerShell module timeout ($TimeoutMilliseconds ms) exceeded while executing"
+ }
+
+ try
+ {
+ $ps.EndInvoke($asyncResult)
+ } catch [System.Management.Automation.IncompleteParseException] {
+ # https://msdn.microsoft.com/en-us/library/system.management.automation.incompleteparseexception%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
+ throw $_.Exception.Message
+ } catch {
+ if ($_.Exception.InnerException -ne $null)
+ {
+ throw $_.Exception.InnerException
+ } else {
+ throw $_.Exception
+ }
+ }
+
+ [Puppet.PuppetPSHostUserInterface]$ui = $global:puppetPSHost.UI
+ return @{
+ exitcode = $global:puppetPSHost.Exitcode;
+ stdout = $ui.Output;
+ stderr = $ui.StdErr;
+ errormessage = $null;
+ }
+ }
+ catch
+ {
+ try
+ {
+ if ($global:runspace) { $global:runspace.Dispose() }
+ }
+ finally
+ {
+ $global:runspace = $null
+ }
+ if(($global:puppetPSHost -ne $null) -and $global:puppetPSHost.ExitCode){
+ $ec = $global:puppetPSHost.ExitCode
+ }else{
+ # This is technically not true at this point as we do not
+ # know what exitcode we should return as an unexpected exception
+ # happened and the user did not set an exitcode. Our best guess
+ # is to return 1 so that we ensure Puppet reports this run as an error.
+ $ec = 1
+ }
+
+ if ($_.Exception.ErrorRecord.InvocationInfo -ne $null)
+ {
+ $output = $_.Exception.Message + "`n`r" + $_.Exception.ErrorRecord.InvocationInfo.PositionMessage
+ } else {
+ $output = $_.Exception.Message | Out-String
+ }
+
+ # make an attempt to read StdErr as it may contain info about failures
+ try { $err = $global:puppetPSHost.UI.StdErr } catch { $err = $null }
+ return @{
+ exitcode = $ec;
+ stdout = $null;
+ stderr = $err;
+ errormessage = $output;
+ }
+ }
+ finally
+ {
+ if ($ps -ne $null) { [Void]$ps.Dispose() }
+ }
+}
+
+function Write-SystemDebugMessage
+{
+ [CmdletBinding()]
+ param(
+ [Parameter(Mandatory = $true)]
+ [String]
+ $Message
+ )
+
+ if ($script:EmitDebugOutput -or ($DebugPreference -ne 'SilentlyContinue'))
+ {
+ [System.Diagnostics.Debug]::WriteLine($Message)
+ }
+}
+
+function Signal-Event
+{
+ [CmdletBinding()]
+ param(
+ [String]
+ $EventName
+ )
+
+ $event = [System.Threading.EventWaitHandle]::OpenExisting($EventName)
+
+ [Void]$event.Set()
+ [Void]$event.Close()
+ if ($PSVersionTable.CLRVersion.Major -ge 3) {
+ [Void]$event.Dispose()
+ }
+
+ Write-SystemDebugMessage -Message "Signaled event $EventName"
+}
+
+function ConvertTo-LittleEndianBytes
+{
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [Int32]
+ $Value
+ )
+
+ $bytes = [BitConverter]::GetBytes($Value)
+ if (![BitConverter]::IsLittleEndian) { [Array]::Reverse($bytes) }
+
+ return $bytes
+}
+
+function ConvertTo-ByteArray
+{
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [Hashtable]
+ $Hash,
+
+ [Parameter(Mandatory = $true)]
+ [System.Text.Encoding]
+ $Encoding
+ )
+
+ # Initialize empty byte array that can be appended to
+ $result = [Byte[]]@()
+ # and add length / name / length / value from Hashtable
+ $Hash.GetEnumerator() |
+ % {
+ $name = $Encoding.GetBytes($_.Name)
+ $result += (ConvertTo-LittleEndianBytes $name.Length) + $name
+
+ $value = @()
+ if ($_.Value -ne $null) { $value = $Encoding.GetBytes($_.Value.ToString()) }
+ $result += (ConvertTo-LittleEndianBytes $value.Length) + $value
+ }
+
+ return $result
+}
+
+function Write-StreamResponse
+{
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.IO.Pipes.PipeStream]
+ $Stream,
+
+ [Parameter(Mandatory = $true)]
+ [Byte[]]
+ $Bytes
+ )
+
+ $length = ConvertTo-LittleEndianBytes -Value $Bytes.Length
+ $Stream.Write($length, 0, 4)
+ $Stream.Flush()
+
+ Write-SystemDebugMessage -Message "Wrote Int32 $($bytes.Length) as Byte[] $length to Stream"
+
+ $Stream.Write($bytes, 0, $bytes.Length)
+ $Stream.Flush()
+
+ Write-SystemDebugMessage -Message "Wrote $($bytes.Length) bytes of data to Stream"
+}
+
+function Read-Int32FromStream
+{
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.IO.Pipes.PipeStream]
+ $Stream
+ )
+
+ $length = New-Object Byte[] 4
+ # Read blocks until all 4 bytes available
+ $Stream.Read($length, 0, 4) | Out-Null
+ # value is sent in Little Endian, but if the CPU is not, in-place reverse the array
+ if (![BitConverter]::IsLittleEndian) { [Array]::Reverse($length) }
+ $value = [BitConverter]::ToInt32($length, 0)
+
+ Write-SystemDebugMessage -Message "Read Byte[] $length from stream as Int32 $value"
+
+ return $value
+}
+
+# Message format is:
+# 1 byte - command identifier
+# 0 - Exit
+# 1 - Execute
+# -1 - Exit - automatically returned when ReadByte encounters a closed pipe
+# [optional] 4 bytes - Little Endian encoded 32-bit code block length for execute
+# Intel CPUs are little endian, hence the .NET Framework typically is
+# [optional] variable length - code block
+function ConvertTo-PipeCommand
+{
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [System.IO.Pipes.PipeStream]
+ $Stream,
+
+ [Parameter(Mandatory = $true)]
+ [System.Text.Encoding]
+ $Encoding,
+
+ [Parameter(Mandatory = $false)]
+ [Int32]
+ $BufferChunkSize = 4096
+ )
+
+ # command identifier is a single value - ReadByte blocks until byte is ready / pipe closes
+ $command = $Stream.ReadByte()
+
+ Write-SystemDebugMessage -Message "Command id $command read from pipe"
+
+ switch ($command)
+ {
+ # Exit
+ # ReadByte returns a -1 when the pipe is closed on the other end
+ { @(0, -1) -contains $_ } { return @{ Command = 'Exit' }}
+
+ # Execute
+ 1 { $parsed = @{ Command = 'Execute' } }
+
+ default { throw "Catastrophic failure: Unexpected Command $command received" }
+ }
+
+ # read size of incoming byte buffer
+ $parsed.Length = Read-Int32FromStream -Stream $Stream
+ Write-SystemDebugMessage -Message "Expecting $($parsed.Length) raw bytes of $($Encoding.EncodingName) characters"
+
+ # Read blocks until all bytes are read or EOF / broken pipe hit - tested with 5MB and worked fine
+ $parsed.RawData = New-Object Byte[] $parsed.Length
+ $read = $Stream.Read($parsed.RawData, 0, $parsed.Length)
+ if ($read -lt $parsed.Length)
+ {
+ throw "Catastrophic failure: Expected $($parsed.Length) raw bytes, only received $read"
+ }
+
+ # turn the raw bytes into the expected encoded string!
+ $parsed.Code = $Encoding.GetString($parsed.RawData)
+
+ return $parsed
+}
+
+function Start-PipeServer
+{
+ [CmdletBinding()]
+ param (
+ [Parameter(Mandatory = $true)]
+ [String]
+ $CommandChannelPipeName,
+
+ [Parameter(Mandatory = $true)]
+ [System.Text.Encoding]
+ $Encoding
+ )
+
+ # this does not require versioning in the payload as client / server are tightly coupled
+ $server = New-Object System.IO.Pipes.NamedPipeServerStream($CommandChannelPipeName,
+ [System.IO.Pipes.PipeDirection]::InOut)
+
+ try
+ {
+ # block until Ruby process connects
+ $server.WaitForConnection()
+
+ Write-SystemDebugMessage -Message "Incoming Connection to $CommandChannelPipeName Received - Expecting Strings as $($Encoding.EncodingName)"
+
+ # Infinite Loop to process commands until EXIT received
+ $running = $true
+ while ($running)
+ {
+ # throws if an unxpected command id is read from pipe
+ $response = ConvertTo-PipeCommand -Stream $server -Encoding $Encoding
+
+ Write-SystemDebugMessage -Message "Received $($response.Command) command from client"
+
+ switch ($response.Command)
+ {
+ 'Execute' {
+ Write-SystemDebugMessage -Message "[Execute] Invoking user code:`n`n $($response.Code)"
+
+ # assuming that the Ruby code always calls Invoked-PowerShellUserCode,
+ # result should already be returned as a hash
+ $result = Invoke-Expression $response.Code
+
+ $bytes = ConvertTo-ByteArray -Hash $result -Encoding $Encoding
+
+ Write-StreamResponse -Stream $server -Bytes $bytes
+ }
+ 'Exit' { $running = $false }
+ }
+ }
+ }
+ catch [Exception]
+ {
+ Write-SystemDebugMessage -Message "PowerShell Pipe Server Failed!`n`n$_"
+ throw
+ }
+ finally
+ {
+ if ($server -ne $null) { $server.Dispose() }
+ }
+}
+
+Start-PipeServer -CommandChannelPipeName $NamedPipeName -Encoding $Encoding
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/manifests/.no_puppet b/modules/utilities/windows/shells/puppetlabs_powershell_local/manifests/.no_puppet
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/metadata.json b/modules/utilities/windows/shells/puppetlabs_powershell_local/metadata.json
new file mode 100644
index 000000000..80b14ec03
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/metadata.json
@@ -0,0 +1,38 @@
+{
+ "name": "puppetlabs-powershell",
+ "version": "2.1.0",
+ "author": "Puppet Inc",
+ "summary": "Adds a new exec provider for executing PowerShell commands.",
+ "license": "Apache-2.0",
+ "source": "https://github.com/puppetlabs/puppetlabs-powershell",
+ "project_page": "https://github.com/puppetlabs/puppetlabs-powershell",
+ "issues_url": "https://tickets.puppet.com/browse/MODULES/component/12015",
+ "dependencies": [
+
+ ],
+ "data_provider": null,
+ "operatingsystem_support": [
+ {
+ "operatingsystem": "Windows",
+ "operatingsystemrelease": [
+ "Server 2008",
+ "Server 2008 R2",
+ "Server 2012",
+ "Server 2012 R2",
+ "7",
+ "8",
+ "10"
+ ]
+ }
+ ],
+ "requirements": [
+ {
+ "name": "pe",
+ "version_requirement": ">= 3.0.0 < 2016.4.0"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": ">= 3.0.0 < 5.0.0"
+ }
+ ]
+}
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/puppetlabs_powershell_local.pp b/modules/utilities/windows/shells/puppetlabs_powershell_local/puppetlabs_powershell_local.pp
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/secgen_metadata.xml b/modules/utilities/windows/shells/puppetlabs_powershell_local/secgen_metadata.xml
new file mode 100644
index 000000000..f329b9c06
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/secgen_metadata.xml
@@ -0,0 +1,18 @@
+
+
+
+ Powershell install local
+ Jason Keighley
+ Puppetlabs
+ Apache v2
+ A local version of the powershell shell provisioner
+
+ shells
+ windows
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/exec_powershell_spec.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/exec_powershell_spec.rb
new file mode 100644
index 000000000..5df116ebe
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/exec_powershell_spec.rb
@@ -0,0 +1,425 @@
+require 'spec_helper_acceptance'
+
+describe 'powershell provider:' do #, :unless => UNSUPPORTED_PLATFORMS.include?(fact('osfamily')) do
+ windows_agents = agents.select { |a| a.platform =~ /windows/ }
+
+ shared_examples 'should fail' do |manifest, error_check|
+ it 'should throw an error' do
+ expect { apply_manifest_on(windows_agents, manifest, :catch_failures => true, :future_parser => FUTURE_PARSER) }.to raise_error(error_check)
+ end
+ end
+
+ shared_examples 'apply success' do |manifest|
+ it 'should succeed' do
+ apply_manifest_on(windows_agents, manifest, :catch_failures => true, :future_parser => FUTURE_PARSER)
+ end
+ end
+
+ describe 'should run successfully' do
+
+ p1 = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'Get-Process > c:/process.txt',
+ unless => 'if(!(test-path "c:/process.txt")){exit 1}',
+ provider => powershell,
+ }
+ MANIFEST
+
+ it 'should not error on first run' do
+ # Run it twice and test for idempotency
+ apply_manifest_on(windows_agents, p1, :catch_failures => true, :future_parser => FUTURE_PARSER)
+ end
+
+ it 'should be idempotent' do
+ apply_manifest_on(windows_agents, p1, :catch_failures => true, :future_parser => FUTURE_PARSER, :acceptable_exit_codes => [0])
+ end
+
+ end
+
+ describe 'should handle a try/catch successfully' do
+
+ it 'should demonstrably execute PowerShell code inside a try block' do
+ tryoutfile = 'C:\try_success.txt'
+ try_content = 'try_executed'
+ catchoutfile = 'c:\catch_shouldntexist.txt'
+
+ powershell_cmd = <<-CMD
+ try {
+ $foo = @(1, 2, 3).count
+ "#{try_content}" | Out-File -FilePath "#{tryoutfile}"
+ } catch {
+ "catch_executed" | Out-File -FilePath "#{catchoutfile}"
+ }
+ CMD
+
+ p1 = <<-MANIFEST
+ exec{'TestPowershell':
+ command => '#{powershell_cmd}',
+ provider => powershell,
+ }
+ MANIFEST
+
+ apply_manifest_on(windows_agents, p1, :catch_failures => true, :future_parser => FUTURE_PARSER)
+
+ windows_agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{tryoutfile}\"") do |result|
+ assert_match(/#{try_content}/, result.stdout, "Unexpected result for host '#{agent}'")
+ end
+
+ on(agent, "cmd.exe /c \"type #{catchoutfile}\"", :acceptable_exit_codes => [1]) do |result|
+ assert_match(/^The system cannot find the file specified\./, result.stderr, "Unexpected file content #{result.stdout} on host '#{agent}'")
+ end
+ end
+ end
+
+ it 'should demonstrably execute PowerShell code inside a catch block' do
+
+ tryoutfile = 'C:\try_shouldntexist.txt'
+ catchoutfile = 'c:\catch_success.txt'
+ catch_content = 'catch_executed'
+
+ powershell_cmd = <<-CMD
+ try {
+ throw "execute catch!"
+ "try_executed" | Out-File -FilePath "#{tryoutfile}"
+ } catch {
+ "#{catch_content}" | Out-File -FilePath "#{catchoutfile}"
+ }
+ CMD
+
+ p1 = <<-MANIFEST
+ exec{'TestPowershell':
+ command => '#{powershell_cmd}',
+ provider => powershell,
+ }
+ MANIFEST
+
+ apply_manifest_on(windows_agents, p1, :catch_failures => true, :future_parser => FUTURE_PARSER)
+
+ windows_agents.each do |agent|
+ on(agent, "cmd.exe /c \"type #{tryoutfile}\"", :acceptable_exit_codes => [1]) do |result|
+ assert_match(/^The system cannot find the file specified\./, result.stderr, "Unexpected file content #{result.stdout} on host '#{agent}'")
+ end
+
+ on(agent, "cmd.exe /c \"type #{catchoutfile}\"") do |result|
+ assert_match(/#{catch_content}/, result.stdout, "Unexpected result for host '#{agent}'")
+ end
+ end
+ end
+
+ end
+
+ describe 'should run commands that exit session' do
+
+ exit_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'exit 0',
+ provider => powershell,
+ }
+ MANIFEST
+
+ it 'should not error on first run' do
+ apply_manifest_on(windows_agents, exit_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ it 'should run a second time' do
+ apply_manifest_on(windows_agents, exit_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ end
+
+ describe 'should run commands that break session' do
+
+ break_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'Break',
+ provider => powershell,
+ }
+ MANIFEST
+
+ it 'should not error on first run' do
+ apply_manifest_on(windows_agents, break_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ it 'should run a second time' do
+ apply_manifest_on(windows_agents, break_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ end
+
+ describe 'should run commands that return from session' do
+
+ return_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'return 0',
+ provider => powershell,
+ }
+ MANIFEST
+
+ it 'should not error on first run' do
+ apply_manifest_on(windows_agents, return_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ it 'should run a second time' do
+ apply_manifest_on(windows_agents, return_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ end
+
+ describe 'should not leak variables across calls to single session' do
+
+ var_leak_setup_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => '$special=1',
+ provider => powershell,
+ }
+ MANIFEST
+
+ var_leak_test_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'if ( $special -eq 1 ) { exit 1 } else { exit 0 }',
+ provider => powershell,
+ }
+ MANIFEST
+
+ it 'should not see variable from previous run' do
+ # Setup the variable
+ apply_manifest_on(windows_agents, var_leak_setup_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+
+ # Test to see if subsequent call sees the variable
+ apply_manifest_on(windows_agents, var_leak_test_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ end
+
+ describe 'should not leak environment variables across calls to single session' do
+
+ envar_leak_setup_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => "\\$env:superspecial='1'",
+ provider => powershell,
+ }
+ MANIFEST
+
+ envar_leak_test_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => "if ( \\$env:superspecial -eq '1' ) { exit 1 } else { exit 0 }",
+ provider => powershell,
+ }
+ MANIFEST
+
+ envar_ext_test_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => "if ( \\$env:outside -eq '1' ) { exit 0 } else { exit 1 }",
+ provider => powershell,
+ }
+ MANIFEST
+
+ after(:each) do
+ on(default, powershell("'Remove-Item Env:\\superspecial -ErrorAction Ignore;exit 0'"))
+ on(default, powershell("'Remove-Item Env:\\outside -ErrorAction Ignore;exit 0'"))
+ end
+
+ it 'should not see environment variable from previous run' do
+ # Setup the environment variable
+ apply_manifest_on(windows_agents, envar_leak_setup_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+
+ # Test to see if subsequent call sees the environment variable
+ apply_manifest_on(windows_agents, envar_leak_test_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ it 'should see environment variables set outside of session' do
+ # Setup the environment variable outside of Puppet
+ on(default, powershell("\\$env:outside='1'"))
+
+ # Test to see if initial run sees the environment variable
+ apply_manifest_on(windows_agents, envar_leak_test_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+
+ # Test to see if subsequent call sees the environment variable and environment purge
+ apply_manifest_on(windows_agents, envar_leak_test_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+ end
+
+ describe 'should allow exit from unless' do
+
+ unless_not_triggered_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'exit 0',
+ unless => 'exit 1',
+ provider => powershell,
+ }
+ MANIFEST
+
+ unless_triggered_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'exit 0',
+ unless => 'exit 0',
+ provider => powershell,
+ }
+ MANIFEST
+
+ it 'should RUN command if unless is NOT triggered' do
+ apply_manifest_on(windows_agents, unless_not_triggered_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ it 'should NOT run command if unless IS triggered' do
+ apply_manifest_on(windows_agents, unless_triggered_pp, :catch_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ end
+
+ describe 'should allow exit from onlyif' do
+
+ onlyif_not_triggered_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'exit 0',
+ onlyif => 'exit 1',
+ provider => powershell,
+ }
+ MANIFEST
+
+ onlyif_triggered_pp = <<-MANIFEST
+ exec{'TestPowershell':
+ command => 'exit 0',
+ onlyif => 'exit 0',
+ provider => powershell,
+ }
+ MANIFEST
+
+ it 'should NOT run command if onlyif is NOT triggered' do
+ apply_manifest_on(windows_agents, onlyif_not_triggered_pp, :catch_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ it 'should RUN command if onlyif IS triggered' do
+ apply_manifest_on(windows_agents, onlyif_triggered_pp, :expect_changes => true, :future_parser => FUTURE_PARSER)
+ end
+
+ end
+
+ describe 'should be able to access the files after execution' do
+
+ p2 = <<-MANIFEST
+ exec{"TestPowershell":
+ command => 'Get-Service *puppet* | Out-File -FilePath C:/services.txt -Encoding UTF8',
+ provider => powershell
+ }
+ MANIFEST
+
+ describe file('c:/services.txt') do
+ apply_manifest_on(windows_agents, p2, :catch_failures => true, :future_parser => FUTURE_PARSER)
+ it { should be_file }
+ its(:content) { should match /puppet/ }
+ end
+ end
+
+ describe 'should catch and rethrow exceptions up to puppet' do
+ pexception = <<-MANIFEST
+ exec{'PowershellException':
+ provider => powershell,
+ command => 'throw "We are writing an error"',
+ }
+ MANIFEST
+ it_should_behave_like 'should fail', pexception, /We are writing an error/i
+ end
+
+ describe 'should error if timeout is exceeded' do
+ ptimeoutexception = <<-MANIFEST
+ exec{'PowershellException':
+ command => 'Write-Host "Going to sleep now..."; Start-Sleep 5',
+ timeout => 2,
+ provider => powershell,
+ }
+ MANIFEST
+ it_should_behave_like 'should fail', ptimeoutexception
+ end
+
+ describe 'should be able to execute a ps1 file provided' do
+ p2 = <<-MANIFEST
+ file{'c:/services.ps1':
+ content => '#{File.open(File.join(File.dirname(__FILE__), 'files/services.ps1')).read()}'
+ }
+ exec{"TestPowershellPS1":
+ command => 'c:/services.ps1',
+ provider => powershell,
+ require => File['c:/services.ps1']
+ }
+ MANIFEST
+ describe file('c:/temp/services.csv') do
+ apply_manifest_on(windows_agents, p2, :catch_failures => true, :future_parser => FUTURE_PARSER)
+ it { should be_file }
+ its(:content) { should match /puppet/ }
+ end
+ end
+
+ describe 'passing parameters to the ps1 file' do
+ outfile = 'C:/temp/svchostprocess.txt'
+ processName = 'svchost'
+ pp = <<-MANIFEST
+ $process = '#{processName}'
+ $outFile = '#{outfile}'
+ file{'c:/param_script.ps1':
+ content => '#{File.open(File.join(File.dirname(__FILE__), 'files/param_script.ps1')).read()}'
+ }
+ exec{'run this with param':
+ provider => powershell,
+ command => "c:/param_script.ps1 -ProcessName '$process' -FileOut '$outFile'",
+ require => File['c:/param_script.ps1'],
+ }
+ MANIFEST
+ describe file(outfile) do
+ apply_manifest_on(windows_agents, pp, :catch_failures => true, :future_parser => FUTURE_PARSER)
+ it { should be_file }
+ its(:content) { should match /svchost/ }
+ end
+ end
+
+ describe 'should execute using 64 bit powershell' do
+ p3 = <<-MANIFEST
+ $maxArchNumber = $::architecture? {
+ /(?i)(i386|i686|x86)$/ => 4,
+ /(?i)(x64|x86_64)/=> 8,
+ default => 0
+ }
+ exec{'Test64bit':
+ command => "if([IntPtr]::Size -eq $maxArchNumber) { exit 0 } else { Write-Error 'Architecture mismatch' }",
+ provider => powershell
+ }
+ MANIFEST
+ it_should_behave_like 'apply success', p3
+ end
+
+ shared_examples 'standard exec' do |powershell_cmd|
+ padmin = <<-MANIFEST
+ exec{'no fail test':
+ command => '#{powershell_cmd}',
+ provider => powershell,
+ }
+ MANIFEST
+ it 'should not fail' do
+ apply_manifest_on(windows_agents, padmin, :catch_failures => true, :future_parser => FUTURE_PARSER)
+ end
+ end
+
+ describe 'test admin rights' do
+ ps1 = <<-PS1
+ $id = [Security.Principal.WindowsIdentity]::GetCurrent()
+ $pr = New-Object Security.Principal.WindowsPrincipal $id
+ if(!($pr.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))){Write-Error "Not in admin"}
+ PS1
+ it_should_behave_like 'standard exec', ps1
+ end
+
+ describe 'test import-module' do
+ pimport = <<-PS1
+ $mods = Get-Module -ListAvailable | Sort
+ if($mods.Length -lt 1) {
+ Write-Error "Expected to get at least one module, but none were listed"
+ }
+ Import-Module $mods[0].Name
+ if(-not (Get-Module $mods[0].Name)){
+ Write-Error "Failed to import module ${mods[0].Name}"
+ }
+ PS1
+ it_should_behave_like 'standard exec', pimport
+ end
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/files/param_script.ps1 b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/files/param_script.ps1
new file mode 100644
index 000000000..495f59b94
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/files/param_script.ps1
@@ -0,0 +1,7 @@
+Param(
+ [String] $ProcessName,
+ [String] $fileOut
+)
+$processes = Get-Process $ProcessName
+New-Item $fileOut -ItemType File
+$processes | Out-File $fileOut -Encoding UTF8
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/files/services.ps1 b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/files/services.ps1
new file mode 100644
index 000000000..01ab8a67b
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/files/services.ps1
@@ -0,0 +1,5 @@
+$temp = "C:/temp"
+if(!(Test-Path $temp)){
+ mkdir $temp
+}
+Get-Service *puppet* | Export-Csv "$temp\services.csv"
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2003-i386.yml b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2003-i386.yml
new file mode 100644
index 000000000..eb571eea1
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2003-i386.yml
@@ -0,0 +1,24 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2003_i386:
+ roles:
+ - agent
+ - default
+ platform: windows-2003-i386
+ template: win-2003-i386
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2003-x86_64.yml b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2003-x86_64.yml
new file mode 100644
index 000000000..5c3e83522
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2003-x86_64.yml
@@ -0,0 +1,24 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2003_x86_64:
+ roles:
+ - agent
+ - default
+ platform: windows-2003-x86_64
+ template: win-2003-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2008-x86_64.yml b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2008-x86_64.yml
new file mode 100644
index 000000000..e9d6d4dca
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2008-x86_64.yml
@@ -0,0 +1,24 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2008_x86_64:
+ roles:
+ - agent
+ - default
+ platform: windows-2008-x86_64
+ template: win-2008-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2008r2-x86_64.yml b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2008r2-x86_64.yml
new file mode 100644
index 000000000..ab9c70f78
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2008r2-x86_64.yml
@@ -0,0 +1,24 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2008r2:
+ roles:
+ - agent
+ - default
+ platform: windows-2008r2-x86_64
+ template: win-2008r2-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2012-x86_64.yml b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2012-x86_64.yml
new file mode 100644
index 000000000..058665450
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2012-x86_64.yml
@@ -0,0 +1,24 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2012:
+ roles:
+ - agent
+ - default
+ platform: windows-2012-x86_64
+ template: win-2012-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2012r2-x86_64.yml b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2012r2-x86_64.yml
new file mode 100644
index 000000000..ef06e40c5
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/acceptance/nodesets/windows-2012r2-x86_64.yml
@@ -0,0 +1,24 @@
+HOSTS:
+ ubuntu1204:
+ roles:
+ - master
+ - database
+ - dashboard
+ platform: ubuntu-12.04-amd64
+ template: ubuntu-1204-x86_64
+ hypervisor: vcloud
+ win2012r2:
+ roles:
+ - agent
+ - default
+ platform: windows-2012r2-x86_64
+ template: win-2012r2-x86_64
+ hypervisor: vcloud
+CONFIG:
+ nfs_server: none
+ consoleport: 443
+ datastore: instance0
+ folder: Delivery/Quality Assurance/Enterprise/Dynamic
+ resourcepool: delivery/Quality Assurance/Enterprise/Dynamic
+ pooling_api: http://vcloud.delivery.puppetlabs.net/
+ pe_dir: http://neptune.puppetlabs.lan/3.2/ci-ready/
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/exit-27.ps1 b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/exit-27.ps1
new file mode 100644
index 000000000..c3605d3f5
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/exit-27.ps1
@@ -0,0 +1 @@
+exit 27
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/integration/puppet_x/puppetlabs/powershell_manager_spec.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/integration/puppet_x/puppetlabs/powershell_manager_spec.rb
new file mode 100644
index 000000000..472659eb6
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/integration/puppet_x/puppetlabs/powershell_manager_spec.rb
@@ -0,0 +1,688 @@
+require 'spec_helper'
+require 'puppet/type'
+require 'puppet_x/puppetlabs/powershell/powershell_manager'
+
+module PuppetX
+ module PowerShell
+ class PowerShellManager; end
+ if Puppet::Util::Platform.windows?
+ module WindowsAPI
+ require 'ffi'
+ extend FFI::Library
+
+ ffi_convention :stdcall
+
+ # https://msdn.microsoft.com/en-us/library/ks2530z6%28v=VS.100%29.aspx
+ # intptr_t _get_osfhandle(
+ # int fd
+ # );
+ ffi_lib [FFI::CURRENT_PROCESS, 'msvcrt']
+ attach_function :get_osfhandle, :_get_osfhandle, [:int], :uintptr_t
+
+ # http://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx
+ # BOOL WINAPI CloseHandle(
+ # _In_ HANDLE hObject
+ # );
+ ffi_lib :kernel32
+ attach_function :CloseHandle, [:uintptr_t], :int32
+ end
+ end
+ end
+end
+
+describe PuppetX::PowerShell::PowerShellManager,
+ :if => Puppet::Util::Platform.windows? && PuppetX::PowerShell::PowerShellManager.supported? do
+
+ let (:manager_args) {
+ provider = Puppet::Type.type(:exec).provider(:powershell)
+ powershell = provider.command(:powershell)
+ cli_args = provider.powershell_args
+ "#{powershell} #{cli_args.join(' ')}"
+ }
+
+ def create_manager
+ PuppetX::PowerShell::PowerShellManager.instance(manager_args, true)
+ end
+
+ let (:manager) { create_manager() }
+
+ describe "when managing the powershell process" do
+ describe "the PowerShellManager::instance method" do
+ it "should return the same manager instance / process given the same cmd line" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ manager_2 = create_manager()
+ second_pid = manager_2.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ expect(manager_2).to eq(manager)
+ expect(first_pid).to eq(second_pid)
+ end
+
+ def bad_file_descriptor_regex
+ # Ruby can do something like:
+ #
+ #
+ @bad_file_descriptor_regex ||= (
+ ebadf = Errno::EBADF.new()
+ '^' + Regexp.escape("\#<#{ebadf.class}: #{ebadf.message}")
+ )
+ end
+
+ def pipe_error_regex
+ @pipe_error_regex ||= (
+ epipe = Errno::EPIPE.new()
+ '^' + Regexp.escape("\#<#{epipe.class}: #{epipe.message}")
+ )
+ end
+ # reason should be a string for an exact match
+ # else an array of regex matches
+ def expect_dead_manager(manager, reason, style = :exact)
+ # additional attempts to use the manager will fail for the given reason
+ result = manager.execute('Write-Host "hi"')
+ expect(result[:exitcode]).to eq(-1)
+
+ if reason.is_a?(String)
+ expect(result[:stderr][0]).to eq(reason) if style == :exact
+ expect(result[:stderr][0]).to match(reason) if style == :regex
+ elsif reason.is_a?(Array)
+ expect(reason).to include(result[:stderr][0]) if style == :exact
+ if style == :regex
+ expect(result[:stderr][0]).to satisfy("should match expected error(s): #{reason}") do |msg|
+ reason.any? { |m| msg.match m }
+ end
+ end
+ end
+
+ # and the manager no longer considers itself alive
+ expect(manager.alive?).to eq(false)
+ end
+
+ def expect_different_manager_returned_than(manager, pid)
+ # acquire another manager instance
+ new_manager = create_manager()
+
+ # which should be different than the one passed in
+ expect(new_manager).to_not eq(manager)
+
+ # with a different PID
+ second_pid = new_manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+ expect(pid).to_not eq(second_pid)
+ end
+
+ def close_stream(stream, style = :inprocess)
+ if style == :inprocess
+ stream.close
+ else style == :viahandle
+ handle = PuppetX::PowerShell::WindowsAPI.get_osfhandle(stream.fileno)
+ PuppetX::PowerShell::WindowsAPI.CloseHandle(handle)
+ end
+ end
+
+ it "should create a new PowerShell manager host if user code exits the first process" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+ exitcode = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Kill()')[:exitcode]
+
+ # when a process gets torn down out from under manager before reading stdout
+ # it catches the error and returns a -1 exitcode
+ expect(exitcode).to eq(-1)
+
+ expect_dead_manager(manager, pipe_error_regex, :regex)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+
+ it "should create a new PowerShell manager host if the underlying PowerShell process is killed" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ # kill the PID from Ruby
+ process = manager.instance_variable_get(:@ps_process)
+ Process.kill('KILL', process.pid)
+
+ expect_dead_manager(manager, pipe_error_regex, :regex)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+
+ it "should create a new PowerShell manager host if the input stream is closed" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ # closing pipe from the Ruby side tears down the process
+ close_stream(manager.instance_variable_get(:@pipe), :inprocess)
+
+ expect_dead_manager(manager, IOError.new('closed stream').inspect, :exact)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+
+ it "should create a new PowerShell manager host if the input stream handle is closed" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ # call CloseHandle against pipe, therby tearing down the PowerShell process
+ close_stream(manager.instance_variable_get(:@pipe), :viahandle)
+
+ expect_dead_manager(manager, bad_file_descriptor_regex, :regex)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+
+ it "should create a new PowerShell manager host if the output stream is closed" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ # closing stdout from the Ruby side allows process to run
+ close_stream(manager.instance_variable_get(:@stdout), :inprocess)
+
+ # fails with vanilla EPIPE or closed stream IOError depening on timing / Ruby version
+ msgs = [ Errno::EPIPE.new().inspect, IOError.new('closed stream').inspect ]
+ expect_dead_manager(manager, msgs, :exact)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+
+ it "should create a new PowerShell manager host if the output stream handle is closed" do
+ # currently skipped as it can trigger an internal Ruby thread clean-up race
+ # its unknown why this test fails, but not the identical test against @stderr
+ skip('This test can cause intermittent segfaults in Ruby with w32_reset_event invalid handle')
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ # call CloseHandle against stdout, which leaves PowerShell process running
+ close_stream(manager.instance_variable_get(:@stdout), :viahandle)
+
+ # fails with vanilla EPIPE or various EBADF depening on timing / Ruby version
+ msgs = [
+ '^' + Regexp.escape(Errno::EPIPE.new().inspect),
+ bad_file_descriptor_regex
+ ]
+ expect_dead_manager(manager, msgs, :regex)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+
+ it "should create a new PowerShell manager host if the error stream is closed" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ # closing stderr from the Ruby side allows process to run
+ close_stream(manager.instance_variable_get(:@stderr), :inprocess)
+
+ # fails with vanilla EPIPE or closed stream IOError depening on timing / Ruby version
+ msgs = [ Errno::EPIPE.new().inspect, IOError.new('closed stream').inspect ]
+ expect_dead_manager(manager, msgs, :exact)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+
+ it "should create a new PowerShell manager host if the error stream handle is closed" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ # call CloseHandle against stderr, which leaves PowerShell process running
+ close_stream(manager.instance_variable_get(:@stderr), :viahandle)
+
+ # fails with vanilla EPIPE or various EBADF depening on timing / Ruby version
+ msgs = [
+ '^' + Regexp.escape(Errno::EPIPE.new().inspect),
+ bad_file_descriptor_regex
+ ]
+ expect_dead_manager(manager, msgs, :regex)
+
+ expect_different_manager_returned_than(manager, first_pid)
+ end
+ end
+ end
+
+ let(:powershell_runtime_error) { '$ErrorActionPreference = "Stop";$test = 1/0' }
+ let(:powershell_parseexception_error) { '$ErrorActionPreference = "Stop";if (1 -badoperator 2) { Exit 1 }' }
+ let(:powershell_incompleteparseexception_error) { '$ErrorActionPreference = "Stop";if (1 -eq 2) { ' }
+
+ describe "when provided powershell commands" do
+ it "shows ps version" do
+ result = manager.execute('$psversiontable')
+ puts result[:stdout]
+ end
+
+ it "should return simple output" do
+ result = manager.execute('write-output foo')
+
+ expect(result[:stdout]).to eq("foo\r\n")
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should return the exitcode specified" do
+ result = manager.execute('write-output foo; exit 55')
+
+ expect(result[:stdout]).to eq("foo\r\n")
+ expect(result[:exitcode]).to eq(55)
+ end
+
+ it "should return the exitcode 1 when exception is thrown" do
+ result = manager.execute('throw "foo"')
+
+ expect(result[:stdout]).to eq(nil)
+ expect(result[:exitcode]).to eq(1)
+ end
+
+ it "should return the exitcode of the last command to set an exit code" do
+ result = manager.execute("$LASTEXITCODE = 0; write-output 'foo'; cmd.exe /c 'exit 99'; write-output 'bar'")
+
+ expect(result[:stdout]).to eq("foo\r\nbar\r\n")
+ expect(result[:exitcode]).to eq(99)
+ end
+
+ it "should return the exitcode of a script invoked with the call operator &" do
+ fixture_path = File.expand_path(File.dirname(__FILE__) + '../../../../exit-27.ps1')
+ result = manager.execute("& #{fixture_path}")
+
+ expect(result[:stdout]).to eq(nil)
+ expect(result[:exitcode]).to eq(27)
+ end
+
+ it "should collect anything written to stderr" do
+ result = manager.execute('[System.Console]::Error.WriteLine("foo")')
+
+ expect(result[:stderr]).to eq(["foo\r\n"])
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should collect multiline output written to stderr" do
+ # induce a failure in cmd.exe that emits a multi-iline error message
+ result = manager.execute('cmd.exe /c foo.exe')
+
+ expect(result[:stdout]).to eq(nil)
+ expect(result[:stderr]).to eq(["'foo.exe' is not recognized as an internal or external command,\r\noperable program or batch file.\r\n"])
+ expect(result[:exitcode]).to eq(1)
+ end
+
+ it "should handle writting to stdout and stderr" do
+ result = manager.execute('ps;[System.Console]::Error.WriteLine("foo")')
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:stderr]).to eq(["foo\r\n"])
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle writing to stdout natively" do
+ result = manager.execute('[System.Console]::Out.WriteLine("foo")')
+
+ expect(result[:stdout]).to eq("foo\r\n")
+ expect(result[:native_stdout]).to eq(nil)
+ expect(result[:stderr]).to eq([])
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should properly interleave output written natively to stdout and via Write-XXX cmdlets" do
+ result = manager.execute('Write-Output "bar"; [System.Console]::Out.WriteLine("foo"); Write-Warning "baz";')
+
+ expect(result[:stdout]).to eq("bar\r\nfoo\r\nWARNING: baz\r\n")
+ expect(result[:stderr]).to eq([])
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle writing to regularly captured output AND stdout natively" do
+ result = manager.execute('ps;[System.Console]::Out.WriteLine("foo")')
+
+ expect(result[:stdout]).not_to eq("foo\r\n")
+ expect(result[:native_stdout]).to eq(nil)
+ expect(result[:stderr]).to eq([])
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle writing to regularly captured output, stderr AND stdout natively" do
+ result = manager.execute('ps;[System.Console]::Out.WriteLine("foo");[System.Console]::Error.WriteLine("bar")')
+
+ expect(result[:stdout]).not_to eq("foo\r\n")
+ expect(result[:native_stdout]).to eq(nil)
+ expect(result[:stderr]).to eq(["bar\r\n"])
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ context "it should handle UTF-8" do
+ # different UTF-8 widths
+ # 1-byte A
+ # 2-byte Û¿ - http://www.fileformat.info/info/unicode/char/06ff/index.htm - 0xDB 0xBF / 219 191
+ # 3-byte áš - http://www.fileformat.info/info/unicode/char/16A0/index.htm - 0xE1 0x9A 0xA0 / 225 154 160
+ # 4-byte 𠜎 - http://www.fileformat.info/info/unicode/char/2070E/index.htm - 0xF0 0xA0 0x9C 0x8E / 240 160 156 142
+ let (:mixed_utf8) { "A\u06FF\u16A0\u{2070E}" } # Aۿᚠ𠜎
+
+ it "when writing basic text" do
+ code = "Write-Output '#{mixed_utf8}'"
+ result = manager.execute(code)
+
+ expect(result[:stdout]).to eq("#{mixed_utf8}\r\n")
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "when writing basic text to stderr" do
+ code = "[System.Console]::Error.WriteLine('#{mixed_utf8}')"
+ result = manager.execute(code)
+
+ expect(result[:stderr]).to eq(["#{mixed_utf8}\r\n"])
+ expect(result[:exitcode]).to eq(0)
+ end
+ end
+
+ it "should execute cmdlets" do
+ result = manager.execute('ls')
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should execute cmdlets with pipes" do
+ result = manager.execute('Get-Process | ? { $_.PID -ne $PID }')
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should execute multi-line" do
+ result = manager.execute(<<-CODE
+$foo = ls
+$count = $foo.count
+$count
+ CODE
+ )
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should execute code with a try/catch, receiving the output of Write-Error" do
+ result = manager.execute(<<-CODE
+try{
+ $foo = ls
+ $count = $foo.count
+ $count
+}catch{
+ Write-Error "foo"
+}
+ CODE
+ )
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should be able to execute the code in a try block when using try/catch" do
+ result = manager.execute(<<-CODE
+ try {
+ $foo = @(1, 2, 3).count
+ exit 400
+ } catch {
+ exit 1
+ }
+ CODE
+ )
+
+ expect(result[:stdout]).to eq(nil)
+ # using an explicit exit code ensures we've really executed correct block
+ expect(result[:exitcode]).to eq(400)
+ end
+
+ it "should be able to execute the code in a catch block when using try/catch" do
+ result = manager.execute(<<-CODE
+try {
+ throw "Error!"
+ exit 0
+} catch {
+ exit 500
+}
+ CODE
+ )
+
+ expect(result[:stdout]).to eq(nil)
+ # using an explicit exit code ensures we've really executed correct block
+ expect(result[:exitcode]).to eq(500)
+ end
+
+
+ it "should reuse the same PowerShell process for multiple calls" do
+ first_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+ second_pid = manager.execute('[Diagnostics.Process]::GetCurrentProcess().Id')[:stdout]
+
+ expect(first_pid).to eq(second_pid)
+ end
+
+ it "should remove psvariables between runs" do
+ manager.execute('$foo = "bar"')
+ result = manager.execute('$foo')
+
+ expect(result[:stdout]).to eq(nil)
+ end
+
+ it "should remove env variables between runs" do
+ manager.execute('[Environment]::SetEnvironmentVariable("foo", "bar", "process")')
+ result = manager.execute('Test-Path env:\foo')
+
+ expect(result[:stdout]).to eq("False\r\n")
+ end
+
+ def current_powershell_major_version
+ provider = Puppet::Type.type(:exec).provider(:powershell)
+ powershell = provider.command(:powershell)
+
+ begin
+ version = `#{powershell} -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -Command \"$PSVersionTable.PSVersion.Major.ToString()\"`.chomp!.to_i
+ rescue
+ puts "Unable to determine PowerShell version"
+ version = -1
+ end
+
+ version
+ end
+
+ def output_cmdlet
+ # Write-Output is the default behavior, except on older PS2 where the
+ # behavior of Write-Output introduces newlines after every width number
+ # of characters as specified in the BufferSize of the custom console UI
+ # Write-Host should usually be avoided, but works for this test in old PS2
+ current_powershell_major_version >= 3 ?
+ 'Write-Output' :
+ 'Write-Host'
+ end
+
+ it "should be able to write more than the 64k default buffer size to the managers pipe without deadlocking the Ruby parent process or breaking the pipe" do
+ # this was tested successfully up to 5MB of text
+ buffer_string_96k = 'a' * ((1024 * 96) + 1)
+ result = manager.execute(<<-CODE
+'#{buffer_string_96k}' | #{output_cmdlet}
+ CODE
+ )
+
+ expect(result[:errormessage]).to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ terminator = output_cmdlet == 'Write-Output' ? "\r\n" : "\n"
+ expect(result[:stdout]).to eq("#{buffer_string_96k}#{terminator}")
+ end
+
+ it "should be able to write more than the 64k default buffer size to child process stdout without deadlocking the Ruby parent process" do
+ result = manager.execute(<<-CODE
+$bytes_in_k = (1024 * 64) + 1
+[Text.Encoding]::UTF8.GetString((New-Object Byte[] ($bytes_in_k))) | #{output_cmdlet}
+ CODE
+ )
+
+ expect(result[:errormessage]).to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ terminator = output_cmdlet == 'Write-Output' ? "\r\n" : "\n"
+ expected = "\x0" * (1024 * 64 + 1) + terminator
+ expect(result[:stdout]).to eq(expected)
+ end
+
+ it "should return a response with a timeout error if the execution timeout is exceeded" do
+ timeout_ms = 100
+ result = manager.execute('sleep 1', timeout_ms)
+ # TODO What is the real message now?
+ msg = /Catastrophic failure\: PowerShell module timeout \(#{timeout_ms} ms\) exceeded while executing\r\n/
+ expect(result[:errormessage]).to match(msg)
+ end
+
+ it "should not deadlock and return a valid response given invalid unparseable PowerShell code" do
+ result = manager.execute(<<-CODE
+ {
+
+ CODE
+ )
+
+ expect(result[:errormessage]).not_to be_empty
+ end
+
+ it "should error if working directory does not exist" do
+ work_dir = 'C:/some/directory/that/does/not/exist'
+
+ result = manager.execute('(Get-Location).Path',nil,work_dir)
+
+ expect(result[:exitcode]).to_not eq(0)
+ expect(result[:errormessage]).to match(/Working directory .+ does not exist/)
+ end
+
+ it "should allow forward slashes in working directory" do
+ work_dir = ENV["WINDIR"]
+ forward_work_dir = work_dir.gsub('\\','/')
+
+ result = manager.execute('(Get-Location).Path',nil,work_dir)[:stdout]
+
+ expect(result).to eq("#{work_dir}\r\n")
+ end
+
+ it "should use a specific working directory if set" do
+ work_dir = ENV["WINDIR"]
+
+ result = manager.execute('(Get-Location).Path',nil,work_dir)[:stdout]
+
+ expect(result).to eq("#{work_dir}\r\n")
+ end
+
+ it "should not reuse the same working directory between runs" do
+ work_dir = ENV["WINDIR"]
+ current_work_dir = Dir.getwd
+
+ first_cwd = manager.execute('(Get-Location).Path',nil,work_dir)[:stdout]
+ second_cwd = manager.execute('(Get-Location).Path')[:stdout]
+
+ # Paths should be case insensitive
+ expect(first_cwd.downcase).to eq("#{work_dir}\r\n".downcase)
+ expect(second_cwd.downcase).to eq("#{current_work_dir}\r\n".downcase)
+ end
+
+ context "with runtime error" do
+ it "should not refer to 'EndInvoke' or 'throw' for a runtime error" do
+ result = manager.execute(powershell_runtime_error)
+
+ expect(result[:exitcode]).to eq(1)
+ expect(result[:errormessage]).not_to match(/EndInvoke/)
+ expect(result[:errormessage]).not_to match(/throw/)
+ end
+
+ it "should display line and char information for a runtime error" do
+ result = manager.execute(powershell_runtime_error)
+
+ expect(result[:exitcode]).to eq(1)
+ expect(result[:errormessage]).to match(/At line\:\d+ char\:\d+/)
+ end
+ end
+
+ context "with ParseException error" do
+ it "should not refer to 'EndInvoke' or 'throw' for a ParseException error" do
+ result = manager.execute(powershell_parseexception_error)
+
+ expect(result[:exitcode]).to eq(1)
+ expect(result[:errormessage]).not_to match(/EndInvoke/)
+ expect(result[:errormessage]).not_to match(/throw/)
+ end
+
+ it "should display line and char information for a ParseException error" do
+ result = manager.execute(powershell_parseexception_error)
+
+ expect(result[:exitcode]).to eq(1)
+ expect(result[:errormessage]).to match(/At line\:\d+ char\:\d+/)
+ end
+ end
+
+ context "with IncompleteParseException error" do
+ it "should not refer to 'EndInvoke' or 'throw' for an IncompleteParseException error" do
+ result = manager.execute(powershell_incompleteparseexception_error)
+
+ expect(result[:exitcode]).to eq(1)
+ expect(result[:errormessage]).not_to match(/EndInvoke/)
+ expect(result[:errormessage]).not_to match(/throw/)
+ end
+
+ it "should not display line and char information for an IncompleteParseException error" do
+ result = manager.execute(powershell_incompleteparseexception_error)
+
+ expect(result[:exitcode]).to eq(1)
+ expect(result[:errormessage]).not_to match(/At line\:\d+ char\:\d+/)
+ end
+ end
+ end
+
+ describe "when output is written to a PowerShell Stream" do
+ it "should collect anything written to verbose stream" do
+ msg = SecureRandom.uuid.to_s.gsub('-', '')
+ result = manager.execute("$VerbosePreference = 'Continue';Write-Verbose '#{msg}'")
+
+ expect(result[:stdout]).to match(/^VERBOSE\: #{msg}/)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should collect anything written to debug stream" do
+ msg = SecureRandom.uuid.to_s.gsub('-', '')
+ result = manager.execute("$debugPreference = 'Continue';Write-debug '#{msg}'")
+
+ expect(result[:stdout]).to match(/^DEBUG: #{msg}/)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should collect anything written to Warning stream" do
+ msg = SecureRandom.uuid.to_s.gsub('-', '')
+ result = manager.execute("Write-Warning '#{msg}'")
+
+ expect(result[:stdout]).to match(/^WARNING: #{msg}/)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should collect anything written to Error stream" do
+ msg = SecureRandom.uuid.to_s.gsub('-', '')
+ result = manager.execute("Write-Error '#{msg}'")
+
+ expect(result[:stdout]).to eq("Write-Error '#{msg}' : #{msg}\r\n + CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException\r\n + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException\r\n \r\n")
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle a Write-Error in the middle of code" do
+ result = manager.execute('ls;Write-Error "Hello";ps')
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle a Out-Default in the user code" do
+ result = manager.execute('\'foo\' | Out-Default')
+
+ expect(result[:stdout]).to eq("foo\r\n")
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle lots of output from user code" do
+ result = manager.execute('1..1000 | %{ (65..90) + (97..122) | Get-Random -Count 5 | % {[char]$_} }')
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle a larger return of output from user code" do
+ result = manager.execute('1..1000 | %{ (65..90) + (97..122) | Get-Random -Count 5 | % {[char]$_} } | %{ $f="" } { $f+=$_ } {$f }')
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+
+ it "should handle shell redirection" do
+ # the test here is to ensure that this doesn't break. because we merge the streams regardless
+ # the opposite of this test shows the same thing
+ result = manager.execute('function test-error{ ps;write-error \'foo\' }; test-error 2>&1')
+
+ expect(result[:stdout]).not_to eq(nil)
+ expect(result[:exitcode]).to eq(0)
+ end
+ end
+
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec.opts b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec.opts
new file mode 100644
index 000000000..91cd6427e
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec.opts
@@ -0,0 +1,6 @@
+--format
+s
+--colour
+--loadby
+mtime
+--backtrace
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec_helper.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec_helper.rb
new file mode 100644
index 000000000..92082847c
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec_helper.rb
@@ -0,0 +1,54 @@
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift File.join(dir, 'lib')
+
+require 'puppet'
+require 'puppetlabs_spec_helper/module_spec_helper'
+require 'pathname'
+require 'rspec'
+
+require 'tmpdir'
+require 'fileutils'
+
+if Puppet.features.microsoft_windows?
+ require 'puppet/util/windows/security'
+
+ def take_ownership(path)
+ path = path.gsub('/', '\\')
+ output = %x(takeown.exe /F #{path} /R /A /D Y 2>&1)
+ if $? != 0 #check if the child process exited cleanly.
+ puts "#{path} got error #{output}"
+ end
+ end
+end
+
+RSpec.configure do |config|
+ tmpdir = Dir.mktmpdir("rspecrun_powershell")
+ oldtmpdir = Dir.tmpdir()
+ ENV['TMPDIR'] = tmpdir
+
+ if Puppet::Util::Platform.windows?
+ config.output_stream = $stdout
+ config.error_stream = $stderr
+ config.formatters.each { |f| f.instance_variable_set(:@output, $stdout) }
+ end
+
+ config.expect_with :rspec do |c|
+ c.syntax = [:should, :expect]
+ end
+
+ config.after :suite do
+ # return to original tmpdir
+ ENV['TMPDIR'] = oldtmpdir
+ if Puppet::Util::Platform.windows?
+ take_ownership(tmpdir)
+ end
+ FileUtils.rm_rf(tmpdir)
+ end
+end
+
+# We need this because the RAL uses 'should' as a method. This
+# allows us the same behavior but with a different method name.
+class Object
+ alias :must :should
+ alias :must_not :should_not
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec_helper_acceptance.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec_helper_acceptance.rb
new file mode 100644
index 000000000..be6dd592f
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/spec_helper_acceptance.rb
@@ -0,0 +1,36 @@
+require 'beaker-rspec/spec_helper'
+require 'beaker-rspec/helpers/serverspec'
+require 'beaker/puppet_install_helper'
+
+UNSUPPORTED_PLATFORMS = ['debian', 'ubuntu', 'Solaris']
+FUTURE_PARSER = ENV['FUTURE_PARSER'] == 'true' || false
+
+run_puppet_install_helper
+
+unless ENV['MODULE_provision'] == 'no'
+
+ on default, "mkdir -p #{default['distmoduledir']}/powershell"
+ result = on default, "echo #{default['distmoduledir']}/powershell"
+ target = result.raw_output.chomp
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ %w(lib metadata.json).each do |file|
+ scp_to default, "#{proj_root}/#{file}", target
+ end
+end
+
+RSpec.configure do |c|
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+
+ # Readable test descriptions
+ c.formatter = :documentation
+
+ # Configure all nodes in nodeset
+ c.before :suite do
+ shell("/bin/touch #{default['puppetpath']}/hiera.yaml")
+ end
+ c.after :suite do
+ absent_files = 'file{["c:/services.txt","c:/process.txt","c:/try_success.txt","c:/catch_shouldntexist.txt","c:/try_shouldntexist.txt","c:/catch_success.txt"]: ensure => absent }'
+ apply_manifest(absent_files)
+ end
+end
+
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/provider/exec/powershell_spec.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/provider/exec/powershell_spec.rb
new file mode 100644
index 000000000..532367fc0
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/provider/exec/powershell_spec.rb
@@ -0,0 +1,240 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/util'
+require 'puppet_x/puppetlabs/powershell/powershell_manager'
+require 'fileutils'
+
+describe Puppet::Type.type(:exec).provider(:powershell) do
+
+ # Override the run value so we can test the super call
+ # There is no real good way to do this otherwise, previously we were
+ # testing Puppet internals that changed in 3.4.0 and made the specs
+ # no longer work the way they were originally specified.
+ Puppet::Type::Exec::ProviderPowershell.instance_eval do
+ alias_method :run_spec_override, :run
+ end
+
+ let(:command) { '$(Get-WMIObject Win32_Account -Filter "SID=\'S-1-5-18\'") | Format-List' }
+ let(:args) { '-NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -Command -' }
+ let(:resource) { Puppet::Type.type(:exec).new(:command => command, :provider => :powershell) }
+ let(:provider) { described_class.new(resource) }
+
+ let(:powershell) {
+ if File.exists?("#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe")
+ "#{ENV['SYSTEMROOT']}\\sysnative\\WindowsPowershell\\v1.0\\powershell.exe"
+ elsif File.exists?("#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe")
+ "#{ENV['SYSTEMROOT']}\\system32\\WindowsPowershell\\v1.0\\powershell.exe"
+ else
+ 'powershell.exe'
+ end
+ }
+
+ describe "#run" do
+ context "stubbed calls" do
+ before :each do
+ PuppetX::PowerShell::PowerShellManager.stubs(:supported?).returns(false)
+ Puppet::Provider::Exec.any_instance.stubs(:run)
+ end
+
+ it "should call exec run" do
+ Puppet::Type::Exec::ProviderPowershell.any_instance.expects(:run)
+
+ provider.run_spec_override(command)
+ end
+
+ it "should call cmd.exe /c" do
+ Puppet::Type::Exec::ProviderPowershell.any_instance.expects(:run)
+ .with(regexp_matches(/^cmd.exe \/c/), anything)
+
+ provider.run_spec_override(command)
+ end
+
+ it "should quote powershell.exe path", :if => Puppet.features.microsoft_windows? do
+ Puppet::Type::Exec::ProviderPowershell.any_instance.expects(:run).
+ with(regexp_matches(/"#{Regexp.escape(powershell)}"/), false)
+
+ provider.run_spec_override(command)
+ end
+
+ it "should quote the path to the temp file" do
+ path = 'C:\Users\albert\AppData\Local\Temp\puppet-powershell20130715-788-1n66f2j.ps1'
+
+ provider.expects(:write_script).with(command).yields(path)
+ Puppet::Type::Exec::ProviderPowershell.any_instance.expects(:run).
+ with(regexp_matches(/^cmd.exe \/c ".* < "#{Regexp.escape(path)}""/), false)
+
+ provider.run_spec_override(command)
+ end
+
+ it "should supply default arguments to supress user interaction" do
+ Puppet::Type::Exec::ProviderPowershell.any_instance.expects(:run).
+ with(regexp_matches(/^cmd.exe \/c ".* #{args} < .*"/), false)
+
+ provider.run_spec_override(command)
+ end
+ end
+
+ context "actual runs", :if => Puppet.features.microsoft_windows? do
+ it "returns the output and status" do
+ output, status = provider.run(command)
+
+ expect(output).to match(/SID\s+:\s+S-1-5-18/)
+ expect(status.exitstatus).to eq(0)
+ end
+
+ it "returns true if the `onlyif` check command succeeds" do
+ resource[:onlyif] = command
+
+ expect(resource.parameter(:onlyif).check(command)).to eq(true)
+ end
+
+ it "returns false if the `unless` check command succeeds" do
+ resource[:unless] = command
+
+ expect(resource.parameter(:unless).check(command)).to eq(false)
+ end
+
+ it "runs commands properly that output to multiple streams" do
+ command = 'echo "foo"; [System.Console]::Error.WriteLine("bar"); cmd.exe /c foo.exe'
+ output, status = provider.run(command)
+
+ if PuppetX::PowerShell::PowerShellManager.supported?
+ expected = "foo\r\n"
+ else
+ # when PowerShellManager is not used, the v1 style module collected
+ # all streams inside of a single output string
+ expected = [
+ "foo\n",
+ "bar\n'",
+ "foo.exe' is not recognized as an internal or external command,\n",
+ "operable program or batch file.\n"
+ ].join('')
+ end
+
+ expect(output).to eq(expected)
+ expect(status.exitstatus).to eq(1)
+ end
+ end
+ end
+
+ describe "#checkexe" do
+ it "should skip checking the exe" do
+ expect(provider.checkexe(command)).to be_nil
+ end
+ end
+
+ describe "#validatecmd" do
+ it "should always successfully validate the command to execute" do
+ expect(provider.validatecmd(command)).to eq(true)
+ end
+ end
+
+ describe 'when specifying a working directory' do
+ describe 'that does not exist' do
+ let(:work_dir) {
+ if Puppet.features.microsoft_windows?
+ "#{ENV['SYSTEMROOT']}\\some\\directory\\that\\does\\not\\exist"
+ else
+ '/some/directory/that/does/not/exist'
+ end
+ }
+ let(:command) { 'exit 0' }
+ let(:resource) { Puppet::Type.type(:exec).new(:command => command, :provider => :powershell, :cwd => work_dir) }
+ let(:provider) { described_class.new(resource) }
+
+ it 'emits an error when working directory does not exist' do
+ expect { provider.run(command) }.to raise_error(/Working directory .+ does not exist/)
+ end
+ end
+ end
+
+ describe 'when applying a catalog' do
+ let(:manifests) { <<-MANIFEST
+ exec { 'PS':
+ command => 'exit 0',
+ provider => powershell,
+ }
+ MANIFEST
+ }
+ let(:tmpdir) { Dir.mktmpdir('statetmp').encode!(Encoding::UTF_8) }
+
+ before :each do
+ # a statedir setting must now exist per the new transactionstore code
+ # introduced in Puppet 4.6 for corrective changes, as a new YAML file
+ # called transactionstore.yaml will be written under this path
+ # which defaults to c:\dev\null when not set on Windows
+ Puppet[:statedir] = tmpdir
+ end
+
+ after :each do
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ def compile_to_catalog(string, node = Puppet::Node.new('foonode'))
+ Puppet[:code] = string
+
+ # see lib/puppet/indirector/catalog/compiler.rb#filter
+ Puppet::Parser::Compiler.compile(node).filter { |r| r.virtual? }
+ end
+
+ def compile_to_ral(manifest)
+ catalog = compile_to_catalog(manifest)
+ ral = catalog.to_ral
+ ral.finalize
+ ral
+ end
+
+ def apply_compiled_manifest(manifest)
+ catalog = compile_to_ral(manifest)
+
+ # ensure compilation works from Puppet 3.0.0 forward
+ args = [catalog, Puppet::Transaction::Report.new('apply')]
+ args << Puppet::Graph::SequentialPrioritizer.new if defined?(Puppet::Graph)
+ transaction = Puppet::Transaction.new(*args)
+ transaction.evaluate
+ transaction.report.finalize_report
+
+ transaction
+ end
+
+ it 'does not emit an irrelevant upgrade message when in a non-Windows environment',
+ :if => !Puppet.features.microsoft_windows? do
+
+ expect(PuppetX::PowerShell::PowerShellManager.supported?).to eq(false)
+
+ # run should never be called on an unsuitable provider
+ Puppet::Type::Exec::ProviderPowershell.any_instance.expects(:run).never
+ # and therefore neither should our upgrade message
+ Puppet::Type::Exec::ProviderPowershell.expects(:upgrade_message).never
+
+ apply_compiled_manifest(manifest)
+ end
+
+ it 'does not emit a warning message when PowerShellManager is usable in a Windows environment',
+ :if => Puppet.features.microsoft_windows? do
+
+ PuppetX::PowerShell::PowerShellManager.stubs(:win32console_enabled?).returns(false)
+
+ expect(PuppetX::PowerShell::PowerShellManager.supported?).to eq(true)
+
+ # given PowerShellManager is supported, never emit an upgrade message
+ Puppet::Type::Exec::ProviderPowershell.expects(:upgrade_message).never
+
+ apply_compiled_manifest(manifest)
+ end
+
+ it 'emits a warning message when PowerShellManager cannot be used in a Windows environment',
+ :if => Puppet.features.microsoft_windows? do
+
+ # pretend we're Ruby 1.9.3 / Puppet 3.x x86
+ PuppetX::PowerShell::PowerShellManager.stubs(:win32console_enabled?).returns(true)
+
+ expect(PuppetX::PowerShell::PowerShellManager.supported?).to eq(false)
+
+ # given PowerShellManager is NOT supported, emit an upgrade message
+ Puppet::Type::Exec::ProviderPowershell.expects(:upgrade_message).once
+
+ apply_compiled_manifest(manifest)
+ end
+ end
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/puppet_x/puppetlabs/powershell/compatible_powershell_version_spec.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/puppet_x/puppetlabs/powershell/compatible_powershell_version_spec.rb
new file mode 100644
index 000000000..cac9d3d8c
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/puppet_x/puppetlabs/powershell/compatible_powershell_version_spec.rb
@@ -0,0 +1,58 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/type'
+require 'puppet_x/puppetlabs/powershell/powershell_version'
+require 'puppet_x/puppetlabs/powershell/compatible_powershell_version'
+
+describe PuppetX::PuppetLabs::PowerShell::CompatiblePowerShellVersion, :if => Puppet::Util::Platform.windows? do
+ before(:each) do
+ @compat = PuppetX::PuppetLabs::PowerShell::CompatiblePowerShellVersion
+ end
+
+ describe "when a newer version of PowerShell is installed" do
+ it "should return true when PowerShell v3 is installed" do
+ PuppetX::PuppetLabs::PowerShell::PowerShellVersion.expects(:version).returns('3.0')
+
+ expect(@compat.compatible_version?).to eq(true)
+ end
+
+ it "should return true when PowerShell v5.0 is installed" do
+ PuppetX::PuppetLabs::PowerShell::PowerShellVersion.expects(:version).returns('5.0.201001.1')
+
+ expect(@compat.compatible_version?).to eq(true)
+ end
+ end
+
+ describe "when PowerShell v2 is installed" do
+ before(:each) do
+ PuppetX::PuppetLabs::PowerShell::PowerShellVersion.expects(:version).returns('2.0')
+ end
+
+ it "should return true when .NET 3.5 is installed" do
+ reg_key = mock('bob')
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', Win32::Registry::KEY_READ | 0x100).yields(reg_key)
+
+ expect(@compat.compatible_version?).to eq(true)
+ end
+
+ it "should return false when .NET 3.5 is not installed" do
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5', Win32::Registry::KEY_READ | 0x100).raises(Win32::Registry::Error.new(2), 'nope').once
+
+ expect(@compat.compatible_version?).to eq(false)
+ end
+ end
+
+ describe "when PowerShell is not installed or not compatible" do
+ it "should return false when PowerShell is not installed" do
+ PuppetX::PuppetLabs::PowerShell::PowerShellVersion.expects(:version).returns(nil)
+
+ expect(@compat.compatible_version?).to eq(false)
+ end
+
+ it "should return false when PowerShell is v1" do
+ PuppetX::PuppetLabs::PowerShell::PowerShellVersion.expects(:version).returns('1.0')
+
+ expect(@compat.compatible_version?).to eq(false)
+ end
+ end
+end
diff --git a/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/puppet_x/puppetlabs/powershell/powershell_version_spec.rb b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/puppet_x/puppetlabs/powershell/powershell_version_spec.rb
new file mode 100644
index 000000000..a182cc0d1
--- /dev/null
+++ b/modules/utilities/windows/shells/puppetlabs_powershell_local/spec/unit/puppet_x/puppetlabs/powershell/powershell_version_spec.rb
@@ -0,0 +1,75 @@
+#! /usr/bin/env ruby
+require 'spec_helper'
+require 'puppet/type'
+require 'puppet_x/puppetlabs/powershell/powershell_version'
+
+describe PuppetX::PuppetLabs::PowerShell::PowerShellVersion, :if => Puppet::Util::Platform.windows? do
+ before(:each) do
+ @ps = PuppetX::PuppetLabs::PowerShell::PowerShellVersion
+ end
+
+ describe "when powershell is installed" do
+
+ describe "when powershell version is greater than three" do
+
+ it "should detect a powershell version" do
+ Win32::Registry.any_instance.expects(:[]).with('PowerShellVersion').returns('5.0.10514.6')
+
+ version = @ps.version
+
+ expect(version).to eq '5.0.10514.6'
+ end
+
+ it "should call the powershell three registry path" do
+ reg_key = mock('bob')
+ reg_key.expects(:[]).with('PowerShellVersion').returns('5.0.10514.6')
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).yields(reg_key).once
+
+ @ps.version
+ end
+
+ it "should not call powershell one registry path" do
+ reg_key = mock('bob')
+ reg_key.expects(:[]).with('PowerShellVersion').returns('5.0.10514.6')
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).yields(reg_key)
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).times(0)
+
+ @ps.version
+ end
+ end
+
+ describe "when powershell version is less than three" do
+
+ it "should detect a powershell version" do
+ Win32::Registry.any_instance.expects(:[]).with('PowerShellVersion').returns('2.0')
+
+ version = @ps.version
+
+ expect(version).to eq '2.0'
+ end
+
+ it "should call powershell one registry path" do
+ reg_key = mock('bob')
+ reg_key.expects(:[]).with('PowerShellVersion').returns('2.0')
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).raises(Win32::Registry::Error.new(2), 'nope').once
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).yields(reg_key).once
+
+ version = @ps.version
+
+ expect(version).to eq '2.0'
+ end
+ end
+ end
+
+ describe "when powershell is not installed" do
+
+ it "should return nil and not throw" do
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).raises(Win32::Registry::Error.new(2), 'nope').once
+ Win32::Registry.any_instance.expects(:open).with('SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine', Win32::Registry::KEY_READ | 0x100).raises(Win32::Registry::Error.new(2), 'nope').once
+
+ version = @ps.version
+
+ expect(version).to eq nil
+ end
+ end
+end
diff --git a/modules/utilities/windows/system_monitors/install_procmon/install_procmon.pp b/modules/utilities/windows/system_monitors/install_procmon/install_procmon.pp
new file mode 100644
index 000000000..6c6349be9
--- /dev/null
+++ b/modules/utilities/windows/system_monitors/install_procmon/install_procmon.pp
@@ -0,0 +1 @@
+include install_procmon::install
\ No newline at end of file
diff --git a/modules/utilities/windows/system_monitors/install_procmon/manifests/install.pp b/modules/utilities/windows/system_monitors/install_procmon/manifests/install.pp
new file mode 100644
index 000000000..40baf889b
--- /dev/null
+++ b/modules/utilities/windows/system_monitors/install_procmon/manifests/install.pp
@@ -0,0 +1,8 @@
+class install_procmon::install {
+ include chocolatey
+
+ package { 'procmon':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/system_monitors/install_procmon/secgen_metadata.xml b/modules/utilities/windows/system_monitors/install_procmon/secgen_metadata.xml
new file mode 100644
index 000000000..26006c702
--- /dev/null
+++ b/modules/utilities/windows/system_monitors/install_procmon/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Procmon install
+ Jason Keighley
+ Apache v2
+ An installation of procmon
+
+ system_monitors
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/text_editor/notepadplusplus/manifests/install.pp b/modules/utilities/windows/text_editor/notepadplusplus/manifests/install.pp
new file mode 100644
index 000000000..75a555700
--- /dev/null
+++ b/modules/utilities/windows/text_editor/notepadplusplus/manifests/install.pp
@@ -0,0 +1,12 @@
+class notepadplusplus::install {
+ include chocolatey
+
+ notice('Installing notepad++')
+
+ package { 'notepadplusplus':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+
+ notice('Notepad++ install finished')
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/text_editor/notepadplusplus/notepadplusplus.pp b/modules/utilities/windows/text_editor/notepadplusplus/notepadplusplus.pp
new file mode 100644
index 000000000..d17804ad6
--- /dev/null
+++ b/modules/utilities/windows/text_editor/notepadplusplus/notepadplusplus.pp
@@ -0,0 +1 @@
+include notepadplusplus::install
\ No newline at end of file
diff --git a/modules/utilities/windows/text_editor/notepadplusplus/secgen_metadata.xml b/modules/utilities/windows/text_editor/notepadplusplus/secgen_metadata.xml
new file mode 100644
index 000000000..fd8e5d183
--- /dev/null
+++ b/modules/utilities/windows/text_editor/notepadplusplus/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Notepadplusplus install
+ Jason Keighley
+ Apache v2
+ A Notepadplusplus installation
+
+ text_editor
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/firefox/firefox.pp b/modules/utilities/windows/web_browsers/firefox/firefox.pp
new file mode 100644
index 000000000..832d010f2
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/firefox/firefox.pp
@@ -0,0 +1 @@
+include firefox::install
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/firefox/manifests/install.pp b/modules/utilities/windows/web_browsers/firefox/manifests/install.pp
new file mode 100644
index 000000000..152580361
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/firefox/manifests/install.pp
@@ -0,0 +1,10 @@
+class firefox::install {
+ include chocolatey
+
+ notice('Installing Firefox')
+
+ package { 'firefox':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/firefox/secgen_metadata.xml b/modules/utilities/windows/web_browsers/firefox/secgen_metadata.xml
new file mode 100644
index 000000000..cd1582504
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/firefox/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Firefox install
+ Jason Keighley
+ Apache v2
+ A Firefox installation
+
+ web_browsers
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/google_chrome/google_chrome.pp b/modules/utilities/windows/web_browsers/google_chrome/google_chrome.pp
new file mode 100644
index 000000000..4ccadf5dd
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/google_chrome/google_chrome.pp
@@ -0,0 +1,2 @@
+include google_chrome::install
+include google_chrome::configure
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/google_chrome/manifests/configure.pp b/modules/utilities/windows/web_browsers/google_chrome/manifests/configure.pp
new file mode 100644
index 000000000..b2c2486d2
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/google_chrome/manifests/configure.pp
@@ -0,0 +1,23 @@
+class google_chrome::configure {
+ $cmd_executable_install_path = 'C:\windows\system32\cmd.exe'
+
+ # Need to ensure unique to each version of Windows,
+ # different versions may have different install locations
+ exec { 'google-chrome-initialize':
+ require => Package[googlechrome],
+ command => 'C:\windows\system32\cmd.exe /C start "" "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" https://www.google.com',
+ }
+
+ exec { 'sleep-10':
+ require => Exec[google-chrome-initialize],
+ command => 'Start-Sleep -s 10',
+ provider => powershell,
+ }
+
+ exec { 'google-chrome-kill-all-processes':
+ require => Exec[sleep-10],
+ # command => "$cmd_executable_install_path\\cmd.exe /C \"taskkill /F /IM chrome.exe /T\""
+ # command => 'C:\windows\system32\cmd.exe /C "taskkill /F /IM chrome.exe /T"'
+ command => 'C:\windows\system32\cmd.exe /C "taskkill /F /IM chrome.exe /T && exit /b 0"'
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/google_chrome/manifests/install.pp b/modules/utilities/windows/web_browsers/google_chrome/manifests/install.pp
new file mode 100644
index 000000000..fafd6a493
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/google_chrome/manifests/install.pp
@@ -0,0 +1,10 @@
+class google_chrome::install {
+ include chocolatey
+
+ notice('Installing google chrome')
+
+ package { 'googlechrome':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/google_chrome/secgen_metadata.xml b/modules/utilities/windows/web_browsers/google_chrome/secgen_metadata.xml
new file mode 100644
index 000000000..ecdf22d6e
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/google_chrome/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Google Chrome install
+ Jason Keighley
+ Apache v2
+ A Google Chrome installation
+
+ web_browsers
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/internet_explorer_11/internet_explorer_11.pp b/modules/utilities/windows/web_browsers/internet_explorer_11/internet_explorer_11.pp
new file mode 100644
index 000000000..50706d629
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/internet_explorer_11/internet_explorer_11.pp
@@ -0,0 +1 @@
+include internet_explorer_11::install
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/internet_explorer_11/manifests/install.pp b/modules/utilities/windows/web_browsers/internet_explorer_11/manifests/install.pp
new file mode 100644
index 000000000..d265c4366
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/internet_explorer_11/manifests/install.pp
@@ -0,0 +1,10 @@
+class internet_explorer_11::install {
+ include chocolatey
+
+ notice('Installing Internet Explorer 11')
+
+ package { 'ie11':
+ ensure => installed,
+ provider => 'chocolatey',
+ }
+}
\ No newline at end of file
diff --git a/modules/utilities/windows/web_browsers/internet_explorer_11/secgen_metadata.xml b/modules/utilities/windows/web_browsers/internet_explorer_11/secgen_metadata.xml
new file mode 100644
index 000000000..cc1ea294d
--- /dev/null
+++ b/modules/utilities/windows/web_browsers/internet_explorer_11/secgen_metadata.xml
@@ -0,0 +1,20 @@
+
+
+
+ Internet Explorer 11 install
+ Jason Keighley
+ Apache v2
+ An Internet Explorer 11 installation
+
+ web_browsers
+ windows
+
+
+
+
+
+ Chocolatey install
+
+
\ No newline at end of file
diff --git a/scenarios/examples/services_utilities_examples/procmon_program_install_example.xml b/scenarios/examples/services_utilities_examples/procmon_program_install_example.xml
new file mode 100644
index 000000000..3b0c59d7e
--- /dev/null
+++ b/scenarios/examples/services_utilities_examples/procmon_program_install_example.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ windows_server_with_procmon
+
+
+
+
+
+
+
+
diff --git a/scenarios/examples/windows_scenario.xml b/scenarios/examples/windows_scenario.xml
index 1051e39a3..95c45ac4f 100644
--- a/scenarios/examples/windows_scenario.xml
+++ b/scenarios/examples/windows_scenario.xml
@@ -1,16 +1,55 @@
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.github/cliffe/SecGen/scenario">
-
-
- storage_server
-
-
+
+
+ windows_server
+
-
-
+
+
+
+
+ HKLM\System\CurrentControlSet\Services\Puppet
+
+
+
+ string
+
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/chrome_history_example.xml b/scenarios/simple_examples/forensic_examples/chrome_history_example.xml
new file mode 100644
index 000000000..c6287f20d
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/chrome_history_example.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ storage_server
+
+
+
+
+
+
+
+
+ 100
+
+
+ 3rd july 2013 15:16:20
+
+
+ 5th june 2015 15:16:20
+
+
+ 10
+
+
+ 4th july 2013 12:00:00
+
+
+ 4th july 2013 15:00:00
+
+
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/multiple_module_example.xml b/scenarios/simple_examples/forensic_examples/multiple_module_example.xml
new file mode 100644
index 000000000..6d8a8499d
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/multiple_module_example.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+ windows_box
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/simple_encoders_example_scenario.xml b/scenarios/simple_examples/forensic_examples/simple_encoders_example_scenario.xml
new file mode 100644
index 000000000..3f495bcac
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/simple_encoders_example_scenario.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+ windows_server
+
+
+
+
+ C:\Users\vagrant\Desktop\Hash_file
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+ String to demonstrate the module
+
+
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/simple_illegal_images_cats_example.xml b/scenarios/simple_examples/forensic_examples/simple_illegal_images_cats_example.xml
new file mode 100644
index 000000000..8dfcacaa7
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/simple_illegal_images_cats_example.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+ storage_server
+
+
+
+
+
+ C:\Users\vagrant\Desktop\Hello.jpg
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/simple_prefetch_example.xml b/scenarios/simple_examples/forensic_examples/simple_prefetch_example.xml
new file mode 100644
index 000000000..16947dbc7
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/simple_prefetch_example.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ windows_server
+
+
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/simple_registry_example.xml b/scenarios/simple_examples/forensic_examples/simple_registry_example.xml
new file mode 100644
index 000000000..99868efce
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/simple_registry_example.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ windows_server
+
+
+
+
+
+
+ HKLM\System\CurrentControlSet\Services\Example1
+
+
+
+
+
+ HKLM\System\CurrentControlSet\Services\Example2
+
+
+
+ string
+
+
+
+ String to demonstrate the module
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/simple_timestamp_example.xml b/scenarios/simple_examples/forensic_examples/simple_timestamp_example.xml
new file mode 100644
index 000000000..2a3711f74
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/simple_timestamp_example.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ storage_server
+
+
+
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+ File contents
+
+
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+
+
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+
+
+
+
+ C:\Users\vagrant\Desktop\Hello.txt
+
+
+
+
+
+
+
+
+
+
diff --git a/scenarios/simple_examples/forensic_examples/simple_timestamp_example_2.xml b/scenarios/simple_examples/forensic_examples/simple_timestamp_example_2.xml
new file mode 100644
index 000000000..db36d67ee
--- /dev/null
+++ b/scenarios/simple_examples/forensic_examples/simple_timestamp_example_2.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ storage_server
+
+
+
+
+
+ C:\Users\vagrant\Desktop\Hello
+
+
+
+
+
+ C:\Users\vagrant\Desktop\Hello
+
+
+
+
+
+
+
diff --git a/secgen.rb b/secgen.rb
index 21c8d934a..8ab83d3ed 100644
--- a/secgen.rb
+++ b/secgen.rb
@@ -34,6 +34,9 @@ def usage
--nopae: Disable PAE support
--hwvirtex: Enable HW virtex support
--vtxvpid: Enable VTX support
+ --memory-per-vm: set amount of memory to give each VM
+ --total-memory: set total amount of memory to give all VMs
+ --max-cpu-cores: set total amount of cpu cores for each VM
--max-cpu-usage [1-100]: Controls how much cpu time a virtual CPU can use
(e.g. 50 implies a single virtual CPU can use up to 50% of a single host CPU)
@@ -87,6 +90,10 @@ def build_config(scenario, out_dir, options)
all_available_utilities = ModuleReader.read_utilities
Print.std "#{all_available_utilities.size} utility modules loaded"
+ Print.info 'Reading available forensic modules...'
+ all_available_forensics = ModuleReader.read_forensics
+ Print.std "#{all_available_forensics.size} forensic modules loaded"
+
Print.info 'Reading available generator modules...'
all_available_generators = ModuleReader.read_generators
Print.std "#{all_available_generators.size} generator modules loaded"
@@ -102,7 +109,8 @@ def build_config(scenario, out_dir, options)
Print.info 'Resolving systems: randomising scenario...'
# for each system, select modules
all_available_modules = all_available_bases + all_available_builds + all_available_vulnerabilties +
- all_available_services + all_available_utilities + all_available_generators + all_available_encoders + all_available_networks
+ all_available_services + all_available_utilities + all_available_forensics + all_available_generators +
+ all_available_encoders + all_available_networks
# update systems with module selections
systems.map! { |system|
system.module_selections = system.resolve_module_selection(all_available_modules, options)
@@ -168,7 +176,7 @@ def build_vms(scenario, project_dir, options)
# TODO: not sure if there is a need to remove_uncreated_vms() here too? (I don't think so?)
end
end
-
+
failures_to_destroy = failures_to_destroy.uniq
if failures_to_destroy.size == 0
@@ -242,6 +250,11 @@ def delete_virtualbox_vm(vm_name)
# Make forensic image helper methods \end
#################################################
+# Make forensic image
+#
+# @author Jason Keighley
+# @param [Hash] options Main options hash containing all options for the running ForGen instance
+# @return [Hash] options Main options hash containing all options for the running ForGen instance
def make_forensic_image(project_dir, image_output_location, image_type)
drive_path = %x(VBoxManage list hdds | grep '#{project_dir.split('/').last}').sub(/\ALocation:\s*/, '').sub(/\n/, '')
drive_name = drive_path.split('/').last
@@ -338,7 +351,8 @@ def delete_all_projects
['--total-memory', GetoptLong::REQUIRED_ARGUMENT],
['--cpu-cores', GetoptLong::REQUIRED_ARGUMENT],
['--max-cpu-usage', GetoptLong::REQUIRED_ARGUMENT],
- ['--shutdown', GetoptLong::NO_ARGUMENT],
+ ['--delete-vm-after-image-creation', GetoptLong::NO_ARGUMENT],
+ [ '--shutdown', GetoptLong::NO_ARGUMENT],
['--network-ranges', GetoptLong::REQUIRED_ARGUMENT],
['--forensic-image-type', GetoptLong::REQUIRED_ARGUMENT],
['--ovirtuser', GetoptLong::REQUIRED_ARGUMENT],
@@ -405,6 +419,9 @@ def delete_all_projects
when '--max-cpu-usage'
Print.info "Max CPU usage set to #{arg}"
options[:max_cpu_usage] = arg
+ when '--delete-vm-after-image-creation'
+ Print.info "Will delete the virtual machine after a forensic image has been generated"
+ options[:delete_vm_after_image_creation] = true
when '--shutdown'
Print.info 'Shutdown VMs after provisioning'
options[:shutdown] = true