├── NanoServerPackage ├── BitsOnNano.exe ├── Json.coreclr.dll ├── NanoServerPackage.Format.ps1xml ├── NanoServerPackage.psd1 ├── NanoServerPackage.psm1 ├── SaveHTTPItemUsingBITS.psm1 └── Test │ ├── Comprehensive.tests.ps1 │ ├── NanoServerPackage.Find.Tests.ps1 │ ├── NanoServerPackage.Install.Tests.ps1 │ └── NanoServerPackage.Save.Tests.ps1 └── README.md /NanoServerPackage/BitsOnNano.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneGet/NanoServerPackage/57b99c642aa2e4d969c3d675baf9827a38b590f1/NanoServerPackage/BitsOnNano.exe -------------------------------------------------------------------------------- /NanoServerPackage/Json.coreclr.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OneGet/NanoServerPackage/57b99c642aa2e4d969c3d675baf9827a38b590f1/NanoServerPackage/Json.coreclr.dll -------------------------------------------------------------------------------- /NanoServerPackage/NanoServerPackage.Format.ps1xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | CBSItemInfo 6 | 7 | Microsoft.PowerShell.Commands.NanoServerPackageItemInfo 8 | 9 | 10 | 11 | 12 | 50 13 | 14 | 15 | 20 16 | 17 | 18 | 16 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Name 27 | 28 | 29 | Version 30 | 31 | 32 | Culture 33 | 34 | 35 | Description 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /NanoServerPackage/NanoServerPackage.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | RootModule = 'NanoServerPackage.psm1' 3 | ModuleVersion = '1.0.1.0' 4 | GUID = '25831220-0a16-4f2e-9b66-864a4252c857' 5 | Author = 'Microsoft Corporation' 6 | Description = 'A PackageManagement provider to Discover, Save and Install Nano Server Packages on-demand' 7 | CompanyName = 'Microsoft Corporation' 8 | Copyright = '© Microsoft Corporation. All rights reserved.' 9 | PowerShellVersion = '5.0' 10 | VariablesToExport = "*" 11 | FunctionsToExport = @('Install-NanoServerPackage', 12 | 'Find-NanoServerPackage', 13 | 'Save-NanoServerPackage') 14 | FormatsToProcess = 'NanoServerPackage.Format.ps1xml' 15 | #RequiredModules = @('Dism') 16 | PrivateData = @{ 17 | "PackageManagementProviders" = @( 18 | 'NanoServerPackage.psm1' 19 | ) 20 | 21 | PSData = @{ 22 | 23 | # Tags applied to this module. These help with module discovery in online galleries. 24 | Tags = 'Packagemanagement','Provider' 25 | 26 | # A URL to the license for this module. 27 | # LicenseUri = '' 28 | 29 | # A URL to the main website for this project. 30 | # ProjectUri = '' 31 | 32 | # A URL to an icon representing this module. 33 | # IconUri = '' 34 | 35 | # ReleaseNotes of this module 36 | ReleaseNotes = @" 37 | To use this with PowerShell PackageManagement you need to run the following commands: 38 | 39 | 1. Install-PackageProvider NanoServerPackage 40 | 41 | 2. Set-ExecutionPolicy RemoteSigned -Scope Process 42 | 43 | 3. Import-PackageProvider NanoServerPackage 44 | 45 | Now commands like Find-Package, Install-Package, Save-Package can recognize optional Nano Server Packages. 46 | "@ 47 | 48 | # External dependent modules of this module 49 | # ExternalModuleDependencies = '' 50 | 51 | } # End of PSData hashtable 52 | } 53 | } 54 | 55 | 56 | # SIG # Begin signature block 57 | # MIIarwYJKoZIhvcNAQcCoIIaoDCCGpwCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB 58 | # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR 59 | # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU/R1pwsbIWPF4HnEpR+1Zj5lD 60 | # epKgghWCMIIEwzCCA6ugAwIBAgITMwAAAJb6gDHvN2RGRQAAAAAAljANBgkqhkiG 61 | # 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G 62 | # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw 63 | # HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTUxMDA3MTgxNDI0 64 | # WhcNMTcwMTA3MTgxNDI0WjCBszELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp 65 | # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw 66 | # b3JhdGlvbjENMAsGA1UECxMETU9QUjEnMCUGA1UECxMebkNpcGhlciBEU0UgRVNO 67 | # OkJCRUMtMzBDQS0yREJFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBT 68 | # ZXJ2aWNlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1pYSwjyVGa6 69 | # tIZe8M6+zXQQ33WKYIyKYcI3oiZcZgVcxdizVjv3hKmjqmRTC5REuLtaSYbdeCuG 70 | # bdMP2+NGWrqeWKLQIxb/Gs/BkEzrr+ewnZ+UQ7xON8jkhPhMSdT5ZiVVNdhVgo+y 71 | # 3hvrk0tk4iDpr5Xwqk5U2W5yZkXras/mIIfO54mjfS31tKQbIsxxubm8Np9ioBit 72 | # boqgiC1iwSxGh7/LGPp1NJVacuQc1JMuzkhRNXxwALbWbyrsUV8Aztz5eaUASLoF 73 | # jkK43ety0X/rV9Qlws43Q2LjKhztpEaxloEr0gioCAEmkJssDjd1qqCZ6X/bht1e 74 | # ggluXnz2tQIDAQABo4IBCTCCAQUwHQYDVR0OBBYEFMfD/XvxW9NCtvwEw94qmvuS 75 | # ht7IMB8GA1UdIwQYMBaAFCM0+NlSRnAK7UD7dvuzK7DDNbMPMFQGA1UdHwRNMEsw 76 | # SaBHoEWGQ2h0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3Rz 77 | # L01pY3Jvc29mdFRpbWVTdGFtcFBDQS5jcmwwWAYIKwYBBQUHAQEETDBKMEgGCCsG 78 | # AQUFBzAChjxodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY3Jv 79 | # c29mdFRpbWVTdGFtcFBDQS5jcnQwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI 80 | # hvcNAQEFBQADggEBADQzONHGQV0X/NPCsvaZQv26Syn1rUGW85E9wUCgtf0iWG55 81 | # ntOcHryYkkVIkjB/vd9ixfzGlW2Bz08YdPHJc5he9ZNkfwhjHqW9r6ii06pa4kzE 82 | # PbgYlLwVRRvxzJwLZpSe56UceM8FmEnsRUSVKzabhLjmiIAFpnNlGgYd6g0eDvxT 83 | # FM9SOJozV4Mjyb7e+Gv//ZxUeZcTK2S/Nam+B6m/mlRVajUYotCDwziVxrm1irMt 84 | # a15M55pT3aawt+QrwXaRUMRSRmIgXTHgFWdM3AksQGA0a77rRKGYldX0iPyH2XOw 85 | # rTHQww9kEcX1r+2R+9QjmsljYc3ZPGnA+2YCADEwggTsMIID1KADAgECAhMzAAAB 86 | # Cix5rtd5e6asAAEAAAEKMA0GCSqGSIb3DQEBBQUAMHkxCzAJBgNVBAYTAlVTMRMw 87 | # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN 88 | # aWNyb3NvZnQgQ29ycG9yYXRpb24xIzAhBgNVBAMTGk1pY3Jvc29mdCBDb2RlIFNp 89 | # Z25pbmcgUENBMB4XDTE1MDYwNDE3NDI0NVoXDTE2MDkwNDE3NDI0NVowgYMxCzAJ 90 | # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k 91 | # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xDTALBgNVBAsTBE1PUFIx 92 | # HjAcBgNVBAMTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIwDQYJKoZIhvcNAQEB 93 | # BQADggEPADCCAQoCggEBAJL8bza74QO5KNZG0aJhuqVG+2MWPi75R9LH7O3HmbEm 94 | # UXW92swPBhQRpGwZnsBfTVSJ5E1Q2I3NoWGldxOaHKftDXT3p1Z56Cj3U9KxemPg 95 | # 9ZSXt+zZR/hsPfMliLO8CsUEp458hUh2HGFGqhnEemKLwcI1qvtYb8VjC5NJMIEb 96 | # e99/fE+0R21feByvtveWE1LvudFNOeVz3khOPBSqlw05zItR4VzRO/COZ+owYKlN 97 | # Wp1DvdsjusAP10sQnZxN8FGihKrknKc91qPvChhIqPqxTqWYDku/8BTzAMiwSNZb 98 | # /jjXiREtBbpDAk8iAJYlrX01boRoqyAYOCj+HKIQsaUCAwEAAaOCAWAwggFcMBMG 99 | # A1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBSJ/gox6ibN5m3HkZG5lIyiGGE3 100 | # NDBRBgNVHREESjBIpEYwRDENMAsGA1UECxMETU9QUjEzMDEGA1UEBRMqMzE1OTUr 101 | # MDQwNzkzNTAtMTZmYS00YzYwLWI2YmYtOWQyYjFjZDA1OTg0MB8GA1UdIwQYMBaA 102 | # FMsR6MrStBZYAck3LjMWFrlMmgofMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9j 103 | # cmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY0NvZFNpZ1BDQV8w 104 | # OC0zMS0yMDEwLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6 105 | # Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljQ29kU2lnUENBXzA4LTMx 106 | # LTIwMTAuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQCmqFOR3zsB/mFdBlrrZvAM2PfZ 107 | # hNMAUQ4Q0aTRFyjnjDM4K9hDxgOLdeszkvSp4mf9AtulHU5DRV0bSePgTxbwfo/w 108 | # iBHKgq2k+6apX/WXYMh7xL98m2ntH4LB8c2OeEti9dcNHNdTEtaWUu81vRmOoECT 109 | # oQqlLRacwkZ0COvb9NilSTZUEhFVA7N7FvtH/vto/MBFXOI/Enkzou+Cxd5AGQfu 110 | # FcUKm1kFQanQl56BngNb/ErjGi4FrFBHL4z6edgeIPgF+ylrGBT6cgS3C6eaZOwR 111 | # XU9FSY0pGi370LYJU180lOAWxLnqczXoV+/h6xbDGMcGszvPYYTitkSJlKOGMIIF 112 | # vDCCA6SgAwIBAgIKYTMmGgAAAAAAMTANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZIm 113 | # iZPyLGQBGRYDY29tMRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQD 114 | # EyRNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMTAwODMx 115 | # MjIxOTMyWhcNMjAwODMxMjIyOTMyWjB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMK 116 | # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 117 | # IENvcnBvcmF0aW9uMSMwIQYDVQQDExpNaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBD 118 | # QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJyWVwZMGS/HZpgICBC 119 | # mXZTbD4b1m/My/Hqa/6XFhDg3zp0gxq3L6Ay7P/ewkJOI9VyANs1VwqJyq4gSfTw 120 | # aKxNS42lvXlLcZtHB9r9Jd+ddYjPqnNEf9eB2/O98jakyVxF3K+tPeAoaJcap6Vy 121 | # c1bxF5Tk/TWUcqDWdl8ed0WDhTgW0HNbBbpnUo2lsmkv2hkL/pJ0KeJ2L1TdFDBZ 122 | # +NKNYv3LyV9GMVC5JxPkQDDPcikQKCLHN049oDI9kM2hOAaFXE5WgigqBTK3S9dP 123 | # Y+fSLWLxRT3nrAgA9kahntFbjCZT6HqqSvJGzzc8OJ60d1ylF56NyxGPVjzBrAlf 124 | # A9MCAwEAAaOCAV4wggFaMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMsR6MrS 125 | # tBZYAck3LjMWFrlMmgofMAsGA1UdDwQEAwIBhjASBgkrBgEEAYI3FQEEBQIDAQAB 126 | # MCMGCSsGAQQBgjcVAgQWBBT90TFO0yaKleGYYDuoMW+mPLzYLTAZBgkrBgEEAYI3 127 | # FAIEDB4KAFMAdQBiAEMAQTAfBgNVHSMEGDAWgBQOrIJgQFYnl+UlE/wq4QpTlVnk 128 | # pDBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtp 129 | # L2NybC9wcm9kdWN0cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEE 130 | # SDBGMEQGCCsGAQUFBzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2Nl 131 | # cnRzL01pY3Jvc29mdFJvb3RDZXJ0LmNydDANBgkqhkiG9w0BAQUFAAOCAgEAWTk+ 132 | # fyZGr+tvQLEytWrrDi9uqEn361917Uw7LddDrQv+y+ktMaMjzHxQmIAhXaw9L0y6 133 | # oqhWnONwu7i0+Hm1SXL3PupBf8rhDBdpy6WcIC36C1DEVs0t40rSvHDnqA2iA6VW 134 | # 4LiKS1fylUKc8fPv7uOGHzQ8uFaa8FMjhSqkghyT4pQHHfLiTviMocroE6WRTsgb 135 | # 0o9ylSpxbZsa+BzwU9ZnzCL/XB3Nooy9J7J5Y1ZEolHN+emjWFbdmwJFRC9f9Nqu 136 | # 1IIybvyklRPk62nnqaIsvsgrEA5ljpnb9aL6EiYJZTiU8XofSrvR4Vbo0HiWGFzJ 137 | # NRZf3ZMdSY4tvq00RBzuEBUaAF3dNVshzpjHCe6FDoxPbQ4TTj18KUicctHzbMrB 138 | # 7HCjV5JXfZSNoBtIA1r3z6NnCnSlNu0tLxfI5nI3EvRvsTxngvlSso0zFmUeDord 139 | # EN5k9G/ORtTTF+l5xAS00/ss3x+KnqwK+xMnQK3k+eGpf0a7B2BHZWBATrBC7E7t 140 | # s3Z52Ao0CW0cgDEf4g5U3eWh++VHEK1kmP9QFi58vwUheuKVQSdpw5OPlcmN2Jsh 141 | # rg1cnPCiroZogwxqLbt2awAdlq3yFnv2FoMkuYjPaqhHMS+a3ONxPdcAfmJH0c6I 142 | # ybgY+g5yjcGjPa8CQGr/aZuW4hCoELQ3UAjWwz0wggYHMIID76ADAgECAgphFmg0 143 | # AAAAAAAcMA0GCSqGSIb3DQEBBQUAMF8xEzARBgoJkiaJk/IsZAEZFgNjb20xGTAX 144 | # BgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBSb290 145 | # IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0wNzA0MDMxMjUzMDlaFw0yMTA0MDMx 146 | # MzAzMDlaMHcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD 147 | # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xITAf 148 | # BgNVBAMTGE1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQTCCASIwDQYJKoZIhvcNAQEB 149 | # BQADggEPADCCAQoCggEBAJ+hbLHf20iSKnxrLhnhveLjxZlRI1Ctzt0YTiQP7tGn 150 | # 0UytdDAgEesH1VSVFUmUG0KSrphcMCbaAGvoe73siQcP9w4EmPCJzB/LMySHnfL0 151 | # Zxws/HvniB3q506jocEjU8qN+kXPCdBer9CwQgSi+aZsk2fXKNxGU7CG0OUoRi4n 152 | # rIZPVVIM5AMs+2qQkDBuh/NZMJ36ftaXs+ghl3740hPzCLdTbVK0RZCfSABKR2YR 153 | # JylmqJfk0waBSqL5hKcRRxQJgp+E7VV4/gGaHVAIhQAQMEbtt94jRrvELVSfrx54 154 | # QTF3zJvfO4OToWECtR0Nsfz3m7IBziJLVP/5BcPCIAsCAwEAAaOCAaswggGnMA8G 155 | # A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCM0+NlSRnAK7UD7dvuzK7DDNbMPMAsG 156 | # A1UdDwQEAwIBhjAQBgkrBgEEAYI3FQEEAwIBADCBmAYDVR0jBIGQMIGNgBQOrIJg 157 | # QFYnl+UlE/wq4QpTlVnkpKFjpGEwXzETMBEGCgmSJomT8ixkARkWA2NvbTEZMBcG 158 | # CgmSJomT8ixkARkWCW1pY3Jvc29mdDEtMCsGA1UEAxMkTWljcm9zb2Z0IFJvb3Qg 159 | # Q2VydGlmaWNhdGUgQXV0aG9yaXR5ghB5rRahSqClrUxzWPQHEy5lMFAGA1UdHwRJ 160 | # MEcwRaBDoEGGP2h0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1 161 | # Y3RzL21pY3Jvc29mdHJvb3RjZXJ0LmNybDBUBggrBgEFBQcBAQRIMEYwRAYIKwYB 162 | # BQUHMAKGOGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z 163 | # b2Z0Um9vdENlcnQuY3J0MBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0GCSqGSIb3DQEB 164 | # BQUAA4ICAQAQl4rDXANENt3ptK132855UU0BsS50cVttDBOrzr57j7gu1BKijG1i 165 | # uFcCy04gE1CZ3XpA4le7r1iaHOEdAYasu3jyi9DsOwHu4r6PCgXIjUji8FMV3U+r 166 | # kuTnjWrVgMHmlPIGL4UD6ZEqJCJw+/b85HiZLg33B+JwvBhOnY5rCnKVuKE5nGct 167 | # xVEO6mJcPxaYiyA/4gcaMvnMMUp2MT0rcgvI6nA9/4UKE9/CCmGO8Ne4F+tOi3/F 168 | # NSteo7/rvH0LQnvUU3Ih7jDKu3hlXFsBFwoUDtLaFJj1PLlmWLMtL+f5hYbMUVbo 169 | # nXCUbKw5TNT2eb+qGHpiKe+imyk0BncaYsk9Hm0fgvALxyy7z0Oz5fnsfbXjpKh0 170 | # NbhOxXEjEiZ2CzxSjHFaRkMUvLOzsE1nyJ9C/4B5IYCeFTBm6EISXhrIniIh0EPp 171 | # K+m79EjMLNTYMoBMJipIJF9a6lbvpt6Znco6b72BJ3QGEe52Ib+bgsEnVLaxaj2J 172 | # oXZhtG6hE6a/qkfwEm/9ijJssv7fUciMI8lmvZ0dhxJkAj0tr1mPuOQh5bWwymO0 173 | # eFQF1EEuUKyUsKV4q7OglnUa2ZKHE3UiLzKoCG6gW4wlv6DvhMoh1useT8ma7kng 174 | # 9wFlb4kLfchpyOZu6qeXzjEp/w7FW1zYTRuh2Povnj8uVRZryROj/TGCBJcwggST 175 | # AgEBMIGQMHkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD 176 | # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xIzAh 177 | # BgNVBAMTGk1pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBAhMzAAABCix5rtd5e6as 178 | # AAEAAAEKMAkGBSsOAwIaBQCggbAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw 179 | # HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFPtx 180 | # aEnMvssm/yGMRzmZQ4WWih3uMFAGCisGAQQBgjcCAQwxQjBAoBaAFABQAG8AdwBl 181 | # AHIAUwBoAGUAbABsoSaAJGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9Qb3dlclNo 182 | # ZWxsIDANBgkqhkiG9w0BAQEFAASCAQBGfSeBs3zO1cHrliTRKFIO3/JXU9myppwe 183 | # yODuUIxytNlJBvukDFe5tu34aKXm5uAhoZlE8xzteIxnyOVYFekHLBHCpZK/qKK0 184 | # dJUdwdSaihIJyh3LXElp5QsQLpV85DHoK723v79uxc9wdtFAuN8t9lqyUax44C0g 185 | # TO3G7jwCEPlFiIEAtxrft1919ZU+1TDqo7p7NCQ5+wHQCKnthJJIyM4OxnUUAk3f 186 | # 5qsj19GZHX9SSm8OZvr9+yiNRc21qtrVUbnst63s09+5xRVrbdDvNgrxPzwGbT3v 187 | # +qW98aIjMZ/BYDqdQCvCKeLy+Ve1ytWdZseenTOcTKYKg7DYvvUYoYICKDCCAiQG 188 | # CSqGSIb3DQEJBjGCAhUwggIRAgEBMIGOMHcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI 189 | # EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv 190 | # ZnQgQ29ycG9yYXRpb24xITAfBgNVBAMTGE1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD 191 | # QQITMwAAAJb6gDHvN2RGRQAAAAAAljAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkD 192 | # MQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTYwNDIxMjI1MTM5WjAjBgkq 193 | # hkiG9w0BCQQxFgQUC4u+riKWZ3p1D4mJuxdrkM13XnUwDQYJKoZIhvcNAQEFBQAE 194 | # ggEAXBW0hZnEn/GKwJ99/WhNx29K3hOyNyWuqNZsVXaVZsrS0tC3rLRXriElTd64 195 | # hxYhAzIvC4mnzg2a9gCgG5Bv8GKwJP8M7h33ddw3kYqPHCMlIApwmSNgPDuFeDg5 196 | # FjGEnACprNdR6R0booQ4ut722KVsAkl3fQIMDdmL1TIk+GyzDIsp00GbWdUquHxG 197 | # 7utdzUD+Ko94+D++9q5pZTKXBNqmzgXc+wO7dzWnWYP/pLasiHAUB8ymS9CRExmi 198 | # XtLtaSzjz5UNmzN2msqlRt0yiZL7Wbkoyom/3s7p1rMX9/a1ZcQaXRTNLGjGR+tx 199 | # GkOXKcgUuFW+/7A5HbPrHomU9A== 200 | # SIG # End signature block 201 | -------------------------------------------------------------------------------- /NanoServerPackage/NanoServerPackage.psm1: -------------------------------------------------------------------------------- 1 | #region Script variables 2 | 3 | Microsoft.PowerShell.Core\Set-StrictMode -Version Latest 4 | 5 | $script:providerName ="NanoServerPackage" 6 | $script:WindowsPackageExtension = ".cab" 7 | $script:onlinePackageCache = @{} 8 | $script:imageCultureCache = @{} 9 | $script:imagePathCache = @{} 10 | $script:wildcardOptions = [System.Management.Automation.WildcardOptions]::CultureInvariant -bor ` 11 | [System.Management.Automation.WildcardOptions]::IgnoreCase 12 | 13 | $script:WindowsPackage = "$env:LOCALAPPDATA\NanoServerPackageProvider" 14 | $script:downloadedCabLocation = "$script:WindowsPackage\DownloadedCabs" 15 | $script:file_modules = "$script:WindowsPackage\sources.txt" 16 | $script:windowsPackageSources = $null 17 | $script:defaultPackageName = "NanoServerPackageSource" 18 | $script:defaultPackageLocation = "http://go.microsoft.com/fwlink/?LinkID=723027&clcid=0x409" 19 | $script:isNanoServerInitialized = $false 20 | $script:isNanoServer = $false 21 | $script:systemSKU = -1 22 | $script:systemVersion = $null 23 | $script:availablePackages = @() 24 | $separator = "|#|" 25 | 26 | #endregion Script variables 27 | 28 | #region Stand-Alone 29 | 30 | function Find-NanoServerPackage 31 | { 32 | [cmdletbinding()] 33 | param 34 | ( 35 | [Parameter(Mandatory=$false, 36 | Position=0)] 37 | [ValidateNotNullOrEmpty()] 38 | [string[]] 39 | $Name, 40 | 41 | [System.Version] 42 | $MinimumVersion, 43 | 44 | [System.Version] 45 | $MaximumVersion, 46 | 47 | [System.Version] 48 | $RequiredVersion, 49 | 50 | [switch] 51 | $AllVersions, 52 | 53 | [string] 54 | $Culture 55 | ) 56 | 57 | $PSBoundParameters["Provider"] = $script:providerName 58 | 59 | $packages = PackageManagement\Find-Package @PSBoundParameters 60 | 61 | foreach($package in $packages) { 62 | Microsoft.PowerShell.Utility\Add-Member -InputObject $package -MemberType NoteProperty -Name "Description" -Value $package.Summary 63 | 64 | try { 65 | if ($package.Metadata["NanoServerVersion"] -ne $null) 66 | { 67 | Microsoft.PowerShell.Utility\Add-Member -InputObject $package -MemberType NoteProperty -Name "NanoServerVersion" -Value (ConvertNanoServerVersionToString $package.Metadata["NanoServerVersion"][0]) 68 | } 69 | } 70 | catch {} 71 | 72 | $package.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.NanoServerPackageItemInfo") | Out-Null 73 | $package 74 | } 75 | } 76 | 77 | function Save-NanoServerPackage 78 | { 79 | [CmdletBinding(DefaultParameterSetName='NameAndPathParameterSet', 80 | SupportsShouldProcess=$true)] 81 | Param 82 | ( 83 | [Parameter(Mandatory=$true, 84 | ValueFromPipelineByPropertyName=$true, 85 | Position=0, 86 | ParameterSetName='NameAndPathParameterSet')] 87 | [Parameter(Mandatory=$true, 88 | ValueFromPipelineByPropertyName=$true, 89 | Position=0, 90 | ParameterSetName='NameAndLiteralPathParameterSet')] 91 | [ValidateNotNullOrEmpty()] 92 | 93 | [string[]] 94 | $Name, 95 | 96 | [Parameter(Mandatory=$true, 97 | ValueFromPipeline=$true, 98 | ValueFromPipelineByPropertyName=$true, 99 | ParameterSetName='InputOjectAndPathParameterSet')] 100 | [Parameter(Mandatory=$true, 101 | ValueFromPipeline=$true, 102 | ValueFromPipelineByPropertyName=$true, 103 | ParameterSetName='InputOjectAndLiteralPathParameterSet')] 104 | [ValidateNotNull()] 105 | [PSCustomObject[]] 106 | $InputObject, 107 | 108 | [Parameter(Mandatory=$false, 109 | ValueFromPipelineByPropertyName=$true, 110 | ParameterSetName='NameAndPathParameterSet')] 111 | [Parameter(Mandatory=$false, 112 | ValueFromPipelineByPropertyName=$true, 113 | ParameterSetName='NameAndLiteralPathParameterSet')] 114 | [string] 115 | $Culture, 116 | 117 | [Parameter(ValueFromPipelineByPropertyName=$true, 118 | ParameterSetName='NameAndPathParameterSet')] 119 | [Parameter(ValueFromPipelineByPropertyName=$true, 120 | ParameterSetName='NameAndLiteralPathParameterSet')] 121 | [Version] 122 | $MinimumVersion, 123 | 124 | [Parameter(ValueFromPipelineByPropertyName=$true, 125 | ParameterSetName='NameAndPathParameterSet')] 126 | [Parameter(ValueFromPipelineByPropertyName=$true, 127 | ParameterSetName='NameAndLiteralPathParameterSet')] 128 | [Version] 129 | $MaximumVersion, 130 | 131 | [Parameter(ValueFromPipelineByPropertyName=$true, 132 | ParameterSetName='NameAndPathParameterSet')] 133 | [Parameter(ValueFromPipelineByPropertyName=$true, 134 | ParameterSetName='NameAndLiteralPathParameterSet')] 135 | [Alias('Version')] 136 | [Version] 137 | $RequiredVersion, 138 | 139 | [Parameter(Mandatory=$true, ParameterSetName='NameAndPathParameterSet')] 140 | [Parameter(Mandatory=$true, ParameterSetName='InputOjectAndPathParameterSet')] 141 | [string] 142 | $Path, 143 | 144 | [Parameter(Mandatory=$true, ParameterSetName='NameAndLiteralPathParameterSet')] 145 | [Parameter(Mandatory=$true, ParameterSetName='InputOjectAndLiteralPathParameterSet')] 146 | [string] 147 | $LiteralPath, 148 | 149 | [Parameter()] 150 | [switch] 151 | $Force 152 | ) 153 | 154 | Begin 155 | { 156 | } 157 | 158 | Process 159 | { 160 | # verify name does not have wild card 161 | foreach ($packageName in $Name) 162 | { 163 | if ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($packageName)) 164 | { 165 | ThrowError -CallerPSCmdlet $PSCmdlet ` 166 | -ExceptionName System.Exception ` 167 | -ExceptionMessage "Name cannot contain wildcards" ` 168 | -ExceptionObject $packageName ` 169 | -ErrorId WildCardCharsAreNotSupported ` 170 | -ErrorCategory InvalidData 171 | 172 | return 173 | } 174 | } 175 | 176 | if($InputObject) 177 | { 178 | $Name = $InputObject.Name 179 | $RequiredVersion = $InputObject.Version 180 | $Culture = $InputObject.Culture 181 | 182 | if (-not [string]::IsNullOrWhiteSpace($Culture) -and $Culture.Contains(',')) 183 | { 184 | $Culture = '' 185 | } 186 | } 187 | 188 | if($Path) 189 | { 190 | $ExceptionObject = $Path 191 | $destinationPath = Resolve-PathHelper -Path $Path ` 192 | -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 193 | } 194 | else 195 | { 196 | $ExceptionObject = $LiteralPath 197 | $destinationPath = Resolve-PathHelper -Path $LiteralPath ` 198 | -IsLiteralPath ` 199 | -CallerPSCmdlet $PSCmdlet | Microsoft.PowerShell.Utility\Select-Object -First 1 200 | } 201 | 202 | if(-not $destinationPath -or -not (Microsoft.PowerShell.Management\Test-Path -LiteralPath $destinationPath)) 203 | { 204 | # When -Force is specified, Path will be created if not available. 205 | if($Force -and $destinationPath) { 206 | $null = Microsoft.PowerShell.Management\New-Item -Path $destinationPath -ItemType Directory -Force 207 | } else { 208 | $errorMessage = ("Cannot find the path '{0}' because it does not exist" -f $LiteralPath) 209 | ThrowError -ExceptionName "System.ArgumentException" ` 210 | -ExceptionMessage $errorMessage ` 211 | -ErrorId "PathNotFound" ` 212 | -CallerPSCmdlet $PSCmdlet ` 213 | -ExceptionObject $ExceptionObject ` 214 | -ErrorCategory InvalidArgument 215 | } 216 | } 217 | 218 | if($Name) 219 | { 220 | # no culture given, use culture of the system 221 | if ([string]::IsNullOrWhiteSpace($Culture)) 222 | { 223 | $Culture = (Get-Culture).Name 224 | } 225 | 226 | $listOfNames = @() 227 | foreach ($packageName in $Name) 228 | { 229 | $listOfNames += $packagename 230 | } 231 | 232 | $findResults = @() 233 | $findResults += (Find -Name $listOfNames ` 234 | -MinimumVersion $MinimumVersion ` 235 | -MaximumVersion $MaximumVersion ` 236 | -RequiredVersion $RequiredVersion ` 237 | -Culture $Culture ` 238 | -Force:$Force) 239 | 240 | if ($findResults.Count -eq 0) 241 | { 242 | Write-Error "No results found for $listOfNames" 243 | return 244 | } 245 | 246 | foreach($findResult in $findResults) 247 | { 248 | $dependenciesToBeInstalled = [System.Collections.ArrayList]::new() 249 | 250 | if (-not (Get-DependenciesToInstall -availablePackages $script:availablePackages -culture $Culture -package $findResult -dependenciesToBeInstalled $dependenciesToBeInstalled)) { 251 | return 252 | } 253 | 254 | foreach ($result in $dependenciesToBeInstalled) { 255 | $currLang = $result.Culture 256 | 257 | $skipBase = $false 258 | 259 | # check whether base package is in list of available packages, if so, don't save 260 | foreach ($availablePackage in $script:availablePackages) { 261 | if (Test-PackageWithSearchQuery -fullyQualifiedName $availablePackage -name $result.Name -requiredVersion $result.Version -Culture "Base") 262 | { 263 | # if it is, no need to download base installer 264 | $skipBase = $true 265 | } 266 | } 267 | 268 | if (-not $skipBase) { 269 | # Base Installer 270 | $fileName_base = Get-FileName -name $result.Name ` 271 | -Culture "" ` 272 | -version $result.Version.ToString() 273 | 274 | $destination_base = Join-Path $destinationPath $fileName_base 275 | 276 | if($PSCmdlet.ShouldProcess($fileName_base, "Save-NanoServerPackage")) 277 | { 278 | if(Test-Path $destination_base) 279 | { 280 | if($Force) 281 | { 282 | Remove-Item $destination_base 283 | 284 | $token = $result.Locations.base 285 | DownloadFile -downloadURL $token -destination $destination_base 286 | } 287 | else 288 | { 289 | # The file exists, not downloading 290 | Write-Information "$fileName_base already existsat $destinationPath. Skipping save." 291 | } 292 | } 293 | else 294 | { 295 | $token = $result.Locations.base 296 | DownloadFile -downloadURL $token -destination $destination_base 297 | } 298 | } 299 | } 300 | 301 | # Language Installer 302 | $fileName_lang = Get-FileName -name $result.Name ` 303 | -Culture $currLang ` 304 | -version $result.Version.ToString() 305 | 306 | $destination_lang = Join-Path $destinationPath $fileName_lang 307 | 308 | if($PSCmdlet.ShouldProcess($fileName_lang, "Save-NanoServerPackage")) 309 | { 310 | if(Test-Path $destination_lang) 311 | { 312 | if($Force) 313 | { 314 | Remove-Item $destination_lang 315 | 316 | $token = $result.Locations.$currLang 317 | DownloadFile -downloadURL $token -destination $destination_lang 318 | } 319 | else 320 | { 321 | # The file exists, not downloading 322 | Write-Information "$fileName_lang already exists at $destinationPath. Skipping save." 323 | } 324 | } 325 | else 326 | { 327 | $token = $result.Locations.$currLang 328 | DownloadFile -downloadURL $token -destination $destination_lang 329 | } 330 | } 331 | 332 | $result 333 | } 334 | } 335 | 336 | } 337 | } 338 | 339 | End 340 | { 341 | } 342 | } 343 | 344 | function Install-NanoServerPackage 345 | { 346 | [CmdletBinding(SupportsShouldProcess=$true)] 347 | param 348 | ( 349 | [parameter(Mandatory=$true, 350 | Position=0, 351 | ValueFromPipelineByPropertyName=$true, 352 | ParameterSetName='NameParameterSet')] 353 | [ValidateNotNullOrEmpty()] 354 | [System.String[]]$Name, 355 | 356 | [Parameter(ValueFromPipelineByPropertyName=$true, 357 | ParameterSetName='NameParameterSet')] 358 | [ValidateNotNull()] 359 | [Version] 360 | $MinimumVersion, 361 | 362 | [parameter(Mandatory=$false, 363 | ValueFromPipelineByPropertyName=$true, 364 | ParameterSetName='NameParameterSet')] 365 | [ValidateNotNullOrEmpty()] 366 | [System.String]$Culture, 367 | 368 | [Parameter(ValueFromPipelineByPropertyName=$true, 369 | ParameterSetName='NameParameterSet')] 370 | [Alias('Version')] 371 | [System.Version]$RequiredVersion, 372 | 373 | [Parameter(ValueFromPipelineByPropertyName=$true, 374 | ParameterSetName='NameParameterSet')] 375 | [ValidateNotNull()] 376 | [System.Version]$MaximumVersion, 377 | 378 | [ValidateNotNullOrEmpty()] 379 | [System.String]$ToVhd, 380 | 381 | [parameter()] 382 | [switch]$Force 383 | ) 384 | 385 | Begin 386 | { 387 | } 388 | 389 | Process 390 | { 391 | # verify name does not have wild card 392 | foreach ($packageName in $Name) 393 | { 394 | if ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($packageName)) 395 | { 396 | ThrowError -CallerPSCmdlet $PSCmdlet ` 397 | -ExceptionName System.Exception ` 398 | -ExceptionMessage "Name cannot contain wildcards" ` 399 | -ExceptionObject $packageName ` 400 | -ErrorId WildCardCharsAreNotSupported ` 401 | -ErrorCategory InvalidData 402 | 403 | return 404 | } 405 | } 406 | 407 | # pipeline case where culture passed in is en-us, de-de, etc. 408 | if (-not [string]::IsNullOrWhiteSpace($Culture) -and $Culture.Contains(',')) 409 | { 410 | $Culture = '' 411 | } 412 | 413 | $packagesToBeInstalled = @() 414 | 415 | # do a find first, if there are any errors, don't install 416 | $packagesToBeInstalled += (Find -Name $Name -MinimumVersion $MinimumVersion -MaximumVersion $MaximumVersion -RequiredVersion $RequiredVersion ` 417 | -Culture $Culture -ErrorAction Stop) 418 | 419 | if ($packagesToBeInstalled.Count -eq 0) 420 | { 421 | ThrowError -CallerPSCmdlet $PSCmdlet ` 422 | -ExceptionName System.InvalidOperationException ` 423 | -ExceptionMessage ("Package '{0}' not found" -f $Name) ` 424 | -ExceptionObject $packageName ` 425 | -ErrorId PackageNotFound ` 426 | -ErrorCategory InvalidOperation 427 | } 428 | 429 | $mountDrive = $null 430 | 431 | # the available packages on the system 432 | $availablePackages = $() 433 | 434 | $installedPackage = $null 435 | 436 | if (-not [string]::IsNullOrWhiteSpace($ToVhd)) 437 | { 438 | if($PSCmdlet.ShouldProcess($ToVhd, "Mount-WindowsImage")) 439 | { 440 | $ToVhd = Resolve-PathHelper $ToVhd -callerPSCmdlet $PSCmdlet 441 | 442 | if (-not ([System.IO.File]::Exists($ToVhd))) 443 | { 444 | $exception = New-Object System.ArgumentException "$ToVhd does not exist" 445 | $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument 446 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "InvalidVhdPath", $errorCategory, $ToVhd 447 | 448 | $PSCmdlet.ThrowTerminatingError($errorRecord) 449 | } 450 | 451 | # mount image 452 | $mountDrive = New-MountDrive 453 | 454 | Write-Verbose "Mounting $ToVhd to $mountDrive" 455 | 456 | Write-Progress -Activity "Mounting $ToVhd to $mountDrive" -PercentComplete 0 457 | 458 | $null = Mount-WindowsImage -ImagePath $ToVhd -Index 1 -Path $mountDrive 459 | 460 | $mountedVHDEdition = $null 461 | 462 | foreach ($packageToBeInstalled in $packagesToBeInstalled) 463 | { 464 | # if this package can't be install on standard, should do a check 465 | if (-not $packageToBeInstalled.Sku.Contains("144") -or (-not [string]::IsNullOrWhiteSpace($packageToBeInstalled.NanoServerVersion))) 466 | { 467 | # initialize the regkey 468 | if ($mountedVHDEdition -eq $null) 469 | { 470 | $regKey = $null 471 | 472 | $vhdNanoServerVersion = $null 473 | $mountedVHDEdition = "ERROR" 474 | 475 | try 476 | { 477 | reg load HKLM\NANOSERVERPACKAGEVHDSYS "$mountDrive\Windows\System32\config\SOFTWARE" | Out-Null 478 | $regKey = dir 'HKLM:\NANOSERVERPACKAGEVHDSYS\Microsoft\Windows NT' 479 | $mountedVHDEdition = $regKey.GetValue("EditionID") 480 | $majorVersion = $regKey.GetValue("CurrentMajorVersionNumber") 481 | $minorVersion = $regKey.GetValue("CurrentMinorVersionNumber") 482 | $buildVersion = $regKey.GetValue("CurrentBuildNumber") 483 | $vhdNanoServerVersion = [version]::new($majorVersion, $minorVersion, $buildVersion, 0) 484 | } 485 | catch 486 | { 487 | # ERROR 488 | $mountedVHDEdition = "ERROR" 489 | $vhdNanoServerVersion = $null 490 | } 491 | finally 492 | { 493 | try 494 | { 495 | if ($regKey -ne $null) 496 | { 497 | $regKey.Handle.Close() 498 | [gc]::Collect() 499 | reg unload HKLM\NANOSERVERPACKAGEVHDSYS | Out-Null 500 | } 501 | } 502 | catch { } 503 | } 504 | } 505 | 506 | if (-not [string]::IsNullOrWhiteSpace($packageToBeInstalled.NanoServerVersion) -and -not (NanoServerVersionMatched -dependencyVersionString $packageToBeInstalled.NanoServerVersion -version $vhdNanoServerVersion)) 507 | { 508 | # unmount the drive 509 | if ($null -ne $mountDrive) 510 | { 511 | Write-Progress -Activity "Unmounting mount drive $mountDrive" -PercentComplete 90 512 | Write-Verbose "Unmounting mount drive $mountDrive" 513 | Remove-MountDrive $mountDrive -discard $true 514 | Write-Progress -Completed -Activity "Completed" 515 | } 516 | 517 | $exception = New-Object System.ArgumentException "The package '$name' with version $($packageToBeInstalled.Version) requires $(ConvertNanoServerVersionToString $packageToBeInstalled.NanoServerVersion). But the current Nano Server has version $vhdNanoServerVersion which is out of this range. Please see https://github.com/OneGet/NanoServerPackage for instructions." ` 518 | $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData 519 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "WrongNanoServerEdition", $errorCategory, $packageToBeInstalled.Name 520 | 521 | $PSCmdlet.ThrowTerminatingError($errorRecord) 522 | } 523 | 524 | if (-not $packageToBeInstalled.Sku.Contains("144") -and $mountedVHDEdition -eq "ServerStandardNano") 525 | { 526 | # unmount the drive 527 | if ($null -ne $mountDrive) 528 | { 529 | Write-Progress -Activity "Unmounting mount drive $mountDrive" -PercentComplete 90 530 | Write-Verbose "Unmounting mount drive $mountDrive" 531 | Remove-MountDrive $mountDrive -discard $true 532 | Write-Progress -Completed -Activity "Completed" 533 | } 534 | 535 | $exception = New-Object System.ArgumentException "$($packageToBeInstalled.Name) cannot be installed on this edition of NanoServer" 536 | $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData 537 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "WrongNanoServerEdition", $errorCategory, $packageToBeInstalled.Name 538 | 539 | $PSCmdlet.ThrowTerminatingError($errorRecord) 540 | } 541 | } 542 | } 543 | } 544 | } 545 | else 546 | { 547 | foreach ($packageToBeInstalled in $packagesToBeInstalled) 548 | { 549 | # this package can't be installed on standard 550 | if (IsNanoServer) 551 | { 552 | # if this is a nano, then systemSKU would be populated after isnanoserver call 553 | if (-not $packageToBeInstalled.Sku.Contains($script:systemSKU.ToString())) 554 | { 555 | $exception = New-Object System.ArgumentException "$($packageToBeInstalled.Name) cannot be installed on this edition of NanoServer" 556 | $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData 557 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "WrongNanoServerEdition", $errorCategory, $packageToBeInstalled.Name 558 | 559 | $PSCmdlet.ThrowTerminatingError($errorRecord) 560 | } 561 | 562 | # if this is nanoserver, then we should also have the version populated 563 | if (-not (NanoServerVersionMatched -dependencyVersionString $packageToBeInstalled.NanoServerVersion -version $script:systemVersion)) 564 | { 565 | 566 | $exception = New-Object System.ArgumentException "The package '$($packageToBeInstalled.Name)' with version $($packageToBeInstalled.Version) requires $(ConvertNanoServerVersionToString $packageToBeInstalled.NanoServerVersion). But the current Nano Server has version $script:systemVersion which is out of this range. Please see https://github.com/OneGet/NanoServerPackage for instructions." 567 | $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidData 568 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "WrongNanoServerVersion", $errorCategory, $packageToBeInstalled.Name 569 | 570 | $PSCmdlet.ThrowTerminatingError($errorRecord) 571 | } 572 | } 573 | } 574 | } 575 | 576 | $discard = $false 577 | 578 | try 579 | { 580 | # If no force, then just check whether the packages are already installed before proceeding 581 | if (-not $Force) 582 | { 583 | Write-Verbose "Getting available packages" 584 | 585 | # installing online 586 | if ([string]::IsNullOrWhiteSpace($ToVhd)) 587 | { 588 | $availablePackages = (Get-WindowsPackage -Online).PackageName.ToLower() 589 | } 590 | else 591 | { 592 | if($PSCmdlet.ShouldProcess($mountDrive, "Get-WindowsPackage")) 593 | { 594 | Write-Progress -Activity "Getting available packages on $mountDrive" -PercentComplete 10 595 | 596 | $availablePackages = (Get-WindowsPackage -Path $mountDrive).PackageName.ToLower() 597 | } 598 | } 599 | } 600 | 601 | if($PSCmdlet.ShouldProcess($Name, "Install-NanoServerPackage")) 602 | { 603 | [bool]$success = $false 604 | 605 | if (-not [string]::IsNullOrWhiteSpace($ToVhd)) 606 | { 607 | Write-Progress -Activity "Mounting $ToVhd to $mountDrive" -PercentComplete 20 608 | } 609 | 610 | #Installing the package 611 | $installedPackage = Install-PackageHelper -Name $Name ` 612 | -Culture $Culture ` 613 | -RequiredVersion $RequiredVersion ` 614 | -MinimumVersion $MinimumVersion ` 615 | -MaximumVersion $MaximumVersion ` 616 | -imagePath $ToVhd ` 617 | -mountDrive $mountDrive ` 618 | -availablePackages $availablePackages ` 619 | -successfullyInstalled ([ref]$success) ` 620 | -Force:$Force ` 621 | -PackagesToBeInstalled $packagesToBeInstalled 622 | 623 | if (-not $success) 624 | { 625 | $exception = New-Object System.ArgumentException "Cannot install package $packageName with culture $Culture and version $RequiredVersion" 626 | $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument 627 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, "FailedToInstallPackage", $errorCategory, $packageName 628 | 629 | Write-Error $errorRecord 630 | $discard = $true 631 | break 632 | } 633 | 634 | $installedPackage 635 | } 636 | } 637 | catch 638 | { 639 | $errorCategory = [System.Management.Automation.ErrorCategory]::InvalidArgument 640 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $_.Exception, "FailedToInstallPackage", $errorCategory, $Name 641 | Write-Error $errorRecord 642 | $discard = $true 643 | } 644 | finally 645 | { 646 | # unmount the drive 647 | if ($null -ne $mountDrive) 648 | { 649 | Write-Progress -Activity "Unmounting mount drive $mountDrive" -PercentComplete 90 650 | Write-Verbose "Unmounting mount drive $mountDrive" 651 | Remove-MountDrive $mountDrive -discard $discard 652 | Write-Progress -Completed -Activity "Completed" 653 | } 654 | } 655 | } 656 | 657 | End 658 | { 659 | } 660 | } 661 | 662 | #endregion Stand-Alone 663 | 664 | #region Helpers 665 | 666 | function Find 667 | { 668 | [CmdletBinding()] 669 | param 670 | ( 671 | [string[]] 672 | $Name, 673 | 674 | [System.Version] 675 | $MinimumVersion, 676 | 677 | [System.Version] 678 | $MaximumVersion, 679 | 680 | [System.Version] 681 | $RequiredVersion, 682 | 683 | [switch] 684 | $AllVersions, 685 | 686 | [string] 687 | $Culture, 688 | 689 | [switch] 690 | $Force 691 | ) 692 | 693 | if(-not (CheckVersion $MinimumVersion $MaximumVersion $RequiredVersion $AllVersions)) 694 | { 695 | return $null 696 | } 697 | 698 | $allSources = Get-Source 699 | 700 | $searchResults = @() 701 | 702 | if ($null -eq $Name -or $Name.Count -eq 0) 703 | { 704 | $Name = @('') 705 | } 706 | 707 | foreach($currSource in $allSources) 708 | { 709 | foreach ($singleName in $Name) 710 | { 711 | if ([string]::IsNullOrWhiteSpace($singleName) -or $singleName.Trim() -eq '*') 712 | { 713 | # if no name is supplied but min or max version is supplied, error out 714 | if ($null -ne $MinimumVersion -or $null -ne $MaximumVersion) 715 | { 716 | ThrowError -CallerPSCmdlet $PSCmdlet ` 717 | -ExceptionName System.Exception ` 718 | -ExceptionMessage "Name is required when either MinimumVersion or MaximumVersion parameter is used" ` 719 | -ExceptionObject $Name ` 720 | -ErrorId NameRequiredForMinOrMaxVersion ` 721 | -ErrorCategory InvalidData 722 | } 723 | } 724 | 725 | $result = Find-Azure -Name $singleName ` 726 | -MinimumVersion $MinimumVersion ` 727 | -MaximumVersion $MaximumVersion ` 728 | -RequiredVersion $RequiredVersion ` 729 | -AllVersions:$AllVersions ` 730 | -Repository $currSource ` 731 | -Culture $Culture ` 732 | -Force:$Force 733 | 734 | if($null -eq $result) 735 | { 736 | # Error must have been thrown already 737 | # Just continue 738 | continue 739 | } 740 | 741 | if ($result.GetType().IsArray -and $result.Count -eq 0) 742 | { 743 | $sourceName = $currSource.Name 744 | Write-Error "No matching packages could be found for $singleName in $sourceName" 745 | continue 746 | } 747 | 748 | $searchResults += $result 749 | } 750 | } 751 | 752 | return $searchResults 753 | } 754 | 755 | function Find-Azure 756 | { 757 | param 758 | ( 759 | [string] 760 | $Name, 761 | 762 | [System.Version] 763 | $MinimumVersion, 764 | 765 | [System.Version] 766 | $MaximumVersion, 767 | 768 | [System.Version] 769 | $RequiredVersion, 770 | 771 | [switch] 772 | $AllVersions, 773 | 774 | [System.Object] 775 | $Repository, 776 | 777 | [string] 778 | $Culture, 779 | 780 | [switch] 781 | $Force 782 | ) 783 | 784 | $searchFile = Get-SearchIndex -Force:$Force -fwdLink $Repository.SourceLocation 785 | $searchFileContent = Get-Content $searchFile 786 | 787 | if($null -eq $searchFileContent) 788 | { 789 | return $null 790 | } 791 | 792 | if(IsNanoServer) 793 | { 794 | $jsonDll = [Microsoft.PowerShell.CoreCLR.AssemblyExtensions]::LoadFrom($PSScriptRoot + "\Json.coreclr.dll") 795 | $jsonParser = $jsonDll.GetTypes() | Where-Object name -match jsonparser 796 | $searchContent = $jsonParser::FromJson($searchFileContent) 797 | $searchStuff = $searchContent.Get_Item("array0") 798 | $searchData = @() 799 | foreach($searchStuffEntry in $searchStuff) 800 | { 801 | $obj = New-Object PSObject 802 | $obj | Add-Member NoteProperty Name $searchStuffEntry.Name 803 | $obj | Add-Member NoteProperty Version $searchStuffEntry.Version 804 | $obj | Add-Member NoteProperty Description $searchStuffEntry.Description 805 | $obj | Add-Member NoteProperty SKU $searchStuffEntry.Sku 806 | $obj | Add-Member NoteProperty NanoServerVersion $searchStuffEntry.NanoServerVersion 807 | 808 | $languageObj = New-Object PSObject 809 | $languageDictionary = $searchStuffEntry.Language 810 | $languageDictionary.Keys | ForEach-Object { 811 | $languageObj | Add-Member NoteProperty $_ $languageDictionary.Item($_) 812 | } 813 | 814 | # process dependencies 815 | if ($searchStuffEntry.ContainsKey("Dependencies")) { 816 | $dependencies = @() 817 | foreach ($dep in $searchStuffEntry.Dependencies) { 818 | $depObject = New-Object PSObject 819 | $depObject | Add-Member NoteProperty Name $dep.Name 820 | $depObject | Add-Member NoteProperty Version $dep.Version 821 | $dependencies += $depObject 822 | } 823 | 824 | $obj | Add-Member NoteProperty Dependencies $dependencies 825 | } 826 | 827 | $obj | Add-Member NoteProperty Language $languageObj 828 | $searchData += $obj 829 | } 830 | } 831 | else 832 | { 833 | $searchData = $searchFileContent | ConvertFrom-Json 834 | } 835 | 836 | $searchResults = @() 837 | $searchDictionary = @{} 838 | 839 | # If name is null or whitespace, interpret as * 840 | if ([string]::IsNullOrWhiteSpace($Name)) 841 | { 842 | $Name = "*" 843 | } 844 | 845 | # Handle the version not given scenario 846 | if((-not ($MinimumVersion -or $MaximumVersion -or $RequiredVersion -or $AllVersions))) 847 | { 848 | $MinimumVersion = [System.Version]'0.0.0.0' 849 | } 850 | 851 | foreach($entry in $searchData) 852 | { 853 | $toggle = $false 854 | 855 | # Check if the search string has * in it 856 | if ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name)) 857 | { 858 | if($entry.name -like $Name) 859 | { 860 | $toggle = $true 861 | } 862 | else 863 | { 864 | continue 865 | } 866 | } 867 | else 868 | { 869 | if($entry.name -eq $Name) 870 | { 871 | $toggle = $true 872 | } 873 | else 874 | { 875 | continue 876 | } 877 | } 878 | 879 | $thisVersion = Convert-Version $entry.version 880 | 881 | if($MinimumVersion) 882 | { 883 | $convertedMinimumVersion = Convert-Version $MinimumVersion 884 | 885 | if(($thisVersion -ge $convertedMinimumVersion)) 886 | { 887 | if($searchDictionary.ContainsKey($entry.name)) 888 | { 889 | $objEntry = $searchDictionary[$entry.name] 890 | $objVersion = Convert-Version $objEntry.Version 891 | 892 | if($thisVersion -gt $objVersion) 893 | { 894 | $toggle = $true 895 | } 896 | else 897 | { 898 | $toggle = $false 899 | } 900 | } 901 | else 902 | { 903 | $toggle = $true 904 | } 905 | } 906 | else 907 | { 908 | $toggle = $false 909 | } 910 | } 911 | 912 | if($MaximumVersion) 913 | { 914 | $convertedMaximumVersion = Convert-Version $MaximumVersion 915 | 916 | if(($thisVersion -le $convertedMaximumVersion)) 917 | { 918 | if($searchDictionary.ContainsKey($entry.name)) 919 | { 920 | $objEntry = $searchDictionary[$entry.name] 921 | $objVersion = Convert-Version $objEntry.Version 922 | 923 | if($thisVersion -gt $objVersion) 924 | { 925 | $toggle = $true 926 | } 927 | else 928 | { 929 | $toggle = $false 930 | } 931 | } 932 | else 933 | { 934 | $toggle = $true 935 | } 936 | } 937 | else 938 | { 939 | $toggle = $false 940 | } 941 | } 942 | 943 | if($RequiredVersion) 944 | { 945 | $convertedRequiredVersion = Convert-Version $RequiredVersion 946 | 947 | if(($thisVersion -eq $convertedRequiredVersion)) 948 | { 949 | $toggle = $true 950 | } 951 | else 952 | { 953 | $toggle = $false 954 | } 955 | } 956 | 957 | if($AllVersions) 958 | { 959 | if($toggle) 960 | { 961 | $searchResults += $entry 962 | } 963 | } 964 | 965 | if($toggle) 966 | { 967 | if($searchDictionary.ContainsKey($entry.name)) 968 | { 969 | $searchDictionary.Remove($entry.Name) 970 | } 971 | 972 | $searchDictionary.Add($entry.name, $entry) 973 | } 974 | } 975 | 976 | if(-not $AllVersions) 977 | { 978 | $searchDictionary.Keys | ForEach-Object { 979 | $searchResults += $searchDictionary.Item($_) 980 | } 981 | } 982 | 983 | $searchLanguageResults = @() 984 | 985 | foreach($searchEntry in $searchResults) 986 | { 987 | $EntryName = $searchEntry.Name 988 | $EntryVersion = $searchEntry.Version 989 | $EntryDescription = $searchEntry.Description 990 | $langDict = $searchEntry.Language 991 | $props= Get-Member -InputObject $langDict -MemberType NoteProperty 992 | $theSource = $Repository.Name 993 | $sku = [string]::Join(";", @($searchEntry.Sku)) 994 | $nanoServerVersion = $searchEntry.NanoServerVersion 995 | 996 | $dependencies = @() 997 | $dependenciesProperty = Get-Member -InputObject $searchEntry -MemberType NoteProperty -Name Dependencies 998 | if ($null -ne $dependenciesProperty) { 999 | $dependencies = $searchEntry.Dependencies 1000 | } 1001 | 1002 | if (-not [string]::IsNullOrWhiteSpace($Culture)) 1003 | { 1004 | if(($props.Name -notcontains $Culture) -or ` 1005 | ($Culture -eq "base")) 1006 | { 1007 | ThrowError -CallerPSCmdlet $PSCmdlet ` 1008 | -ExceptionName System.Exception ` 1009 | -ExceptionMessage "Culture: $Culture is not supported" ` 1010 | -ExceptionObject $EntryName ` 1011 | -ErrorId WildCardCharsAreNotSupported ` 1012 | -ErrorCategory InvalidData 1013 | return 1014 | } 1015 | 1016 | $languageObj = New-Object PSObject 1017 | $languageObj | Add-Member NoteProperty "base" $langDict."base" 1018 | $languageObj | Add-Member NoteProperty $Culture $langDict.$Culture 1019 | 1020 | $ResultEntry = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ 1021 | Name = $EntryName 1022 | Version = $EntryVersion 1023 | Description = $EntryDescription 1024 | Source = $theSource 1025 | Locations = $languageObj 1026 | Culture = $Culture 1027 | Sku = $sku 1028 | Dependencies = $dependencies 1029 | NanoServerVersion = $NanoServerVersion 1030 | }) 1031 | $ResultEntry.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.NanoServerPackageItemInfo") 1032 | $searchLanguageResults += $ResultEntry 1033 | } 1034 | else 1035 | { 1036 | $langList = @() 1037 | $langListString = "" 1038 | 1039 | $props.Name | ForEach-Object { 1040 | $langList += $_ 1041 | if($_ -ne "base"){ 1042 | $langListString += $_ 1043 | $langListString += ", " 1044 | } 1045 | } 1046 | 1047 | $langListString = $langListString.Substring(0, $langListString.Length - 2) 1048 | 1049 | $ResultEntry = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ 1050 | Name = $EntryName 1051 | Version = $EntryVersion 1052 | Description = $EntryDescription 1053 | Source = $theSource 1054 | Locations = $langDict 1055 | Culture = $langListString 1056 | Sku = $sku 1057 | Dependencies = $dependencies 1058 | NanoServerVersion = $NanoServerVersion 1059 | }) 1060 | $ResultEntry.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.NanoServerPackageItemInfo") 1061 | $searchLanguageResults += $ResultEntry 1062 | } 1063 | } 1064 | 1065 | return $searchLanguageResults 1066 | } 1067 | 1068 | ### 1069 | ### SUMMARY: Download the file given the URI to the given location 1070 | ### 1071 | function DownloadFile 1072 | { 1073 | [CmdletBinding()] 1074 | param($downloadURL, $destination, [switch]$noProgress) 1075 | 1076 | $startTime = Get-Date 1077 | 1078 | try 1079 | { 1080 | # Download the file 1081 | Write-Verbose "Downloading $downloadUrl to $destination" 1082 | $saveItemPath = $PSScriptRoot + "\SaveHTTPItemUsingBITS.psm1" 1083 | Import-Module "$saveItemPath" 1084 | Save-HTTPItemUsingBitsTransfer -Uri $downloadURL ` 1085 | -Destination $destination ` 1086 | -NoProgress:$noProgress 1087 | Write-Verbose "Finished downloading" 1088 | 1089 | $endTime = Get-Date 1090 | $difference = New-TimeSpan -Start $startTime -End $endTime 1091 | $downloadTime = "Downloaded in " + $difference.Hours + " hours, " + $difference.Minutes + " minutes, " + $difference.Seconds + " seconds." 1092 | Write-Verbose $downloadTime 1093 | } 1094 | catch 1095 | { 1096 | ThrowError -CallerPSCmdlet $PSCmdlet ` 1097 | -ExceptionName $_.Exception.GetType().FullName ` 1098 | -ExceptionMessage $_.Exception.Message ` 1099 | -ExceptionObject $downloadURL ` 1100 | -ErrorId FailedToDownload ` 1101 | -ErrorCategory InvalidOperation 1102 | } 1103 | } 1104 | 1105 | function Install-PackageHelper 1106 | { 1107 | [cmdletbinding()] 1108 | param( 1109 | [string[]]$Name, 1110 | [string]$Culture, 1111 | [string]$source, 1112 | [string]$mountDrive, 1113 | [string]$imagePath, 1114 | [ref]$successfullyInstalled, 1115 | [version]$MinimumVersion, 1116 | [version]$MaximumVersion, 1117 | [version][Alias('Version')]$RequiredVersion, 1118 | [string[]]$availablePackages, 1119 | [switch]$Force, 1120 | [PSCustomObject[]]$PackagesToBeInstalled 1121 | ) 1122 | 1123 | $installedWindowsPackages = @() 1124 | 1125 | $successfullyInstalled.Value = $false 1126 | 1127 | if ([string]::IsNullOrWhiteSpace($Culture)) 1128 | { 1129 | # if the culture is null for the online case, we can find out easily 1130 | 1131 | if ([string]::IsNullOrWhiteSpace($mountDrive)) 1132 | { 1133 | $Culture = (Get-Culture).Name 1134 | } 1135 | else 1136 | { 1137 | Write-Verbose "Determining the culture of $mountDrive" 1138 | 1139 | $fileKey = Get-FileKey -filePath $imagePath 1140 | 1141 | if (-not $script:imageCultureCache.ContainsKey($fileKey)) 1142 | { 1143 | $Culture = Get-ImageCulture -mountDrive $mountDrive 1144 | 1145 | if ($null -eq $Culture) 1146 | { 1147 | Write-Verbose "Cannot determine culture of $mountDrive with /Get-Intl. Trying to find culture using a sample package" 1148 | 1149 | $packagesOnTheMachine = $availablePackages 1150 | 1151 | if ($null -eq $packagesOnTheMachine -or $packagesOnTheMachine.Count -eq 0) 1152 | { 1153 | $packagesOnTheMachine = (Get-WindowsPackage -Path $mountDrive).PackageName 1154 | } 1155 | 1156 | foreach ($package in $packagesOnTheMachine) 1157 | { 1158 | $Culture = $package.Split('~')[3] 1159 | 1160 | # we have found a culture from a package installed! 1161 | if (-not [string]::IsNullOrWhiteSpace($Culture)) 1162 | { 1163 | break 1164 | } 1165 | } 1166 | } 1167 | 1168 | # if after all that, culture still null then we have to abort 1169 | if ($null -eq $Culture) 1170 | { 1171 | Write-Warning "Cannot determine culture of the vhd. Please supply it directly." 1172 | return 1173 | } 1174 | 1175 | $script:imageCultureCache[$fileKey] = $Culture 1176 | } 1177 | else 1178 | { 1179 | $Culture = $script:imageCultureCache[$fileKey] 1180 | } 1181 | } 1182 | 1183 | Write-Verbose "The culture to be installed is $Culture" 1184 | } 1185 | 1186 | foreach ($packageName in $Name) 1187 | { 1188 | $randomName = [System.IO.Path]::GetRandomFileName() 1189 | $destinationFolder = Join-Path $script:downloadedCabLocation $randomName 1190 | 1191 | $baseVersion = $null 1192 | $languageVersion = $null 1193 | 1194 | foreach ($availablePackage in $availablePackages) 1195 | { 1196 | # check whether base package is already installed 1197 | if (Test-PackageWithSearchQuery -fullyQualifiedName $availablePackage -name $packageName -requiredVersion $RequiredVersion -minimumVersion $MinimumVersion -maximumVersion $MaximumVersion -Culture "Base") 1198 | { 1199 | $baseVersion = Convert-Version ($availablePackage.Split('~')[4]) 1200 | } 1201 | # check whether language pack is installed 1202 | elseif (Test-PackageWithSearchQuery -fullyQualifiedName $availablePackage -name $packageName -requiredVersion $RequiredVersion -minimumVersion $MinimumVersion -maximumVersion $MaximumVersion -Culture $Culture) 1203 | { 1204 | $languageVersion = Convert-Version ($availablePackage.Split('~')[4]) 1205 | } 1206 | } 1207 | 1208 | # no force and both are installed, just returned 1209 | if (-not $Force) 1210 | { 1211 | if ($null -ne $baseVersion -and $null -ne $languageVersion) 1212 | { 1213 | Write-Verbose "Skipping installed package $packageName" 1214 | $successfullyInstalled.Value = $true 1215 | 1216 | # returned the package to be installed 1217 | 1218 | if ($null -ne $PackagesToBeInstalled) 1219 | { 1220 | $PackagesToBeInstalled | Where-Object {$_.Name -eq $packageName} | ForEach-Object {$_.Culture = $Culture; $_} 1221 | } 1222 | 1223 | continue 1224 | } 1225 | } 1226 | 1227 | # This means source is offline 1228 | if ((-not [string]::IsNullOrWhiteSpace($source)) -and (Test-Path $source)) 1229 | { 1230 | Write-Verbose "Installing package from $source" 1231 | $savedCabFilesToInstall = @($source) 1232 | } 1233 | else 1234 | { 1235 | if (-not (Test-Path $destinationFolder)) 1236 | { 1237 | $null = mkdir $destinationFolder 1238 | } 1239 | 1240 | Write-Verbose "Downloading cab files to $destinationFolder" 1241 | try { 1242 | $script:availablePackages = $availablePackages 1243 | $savedPackages = Save-NanoServerPackage -Name $packageName -Culture $Culture -RequiredVersion $RequiredVersion -MinimumVersion $MinimumVersion ` 1244 | -MaximumVersion $MaximumVersion -Path $destinationFolder -Force 1245 | } 1246 | finally { 1247 | $script:availablePackages = @() 1248 | } 1249 | } 1250 | 1251 | $savedCabFilesToInstall = @() 1252 | $savedCabFilesToInstallTuple = @() 1253 | 1254 | foreach ($savedPackage in $savedPackages) 1255 | { 1256 | $basePackageFile = (Join-Path $destinationFolder (Get-FileName -name $savedPackage.Name -Culture "" -version $savedPackage.Version)) 1257 | 1258 | $basePackagePath = "" 1259 | 1260 | if (Test-Path $basePackageFile) { 1261 | $savedCabFilesToInstall += $basePackageFile 1262 | $basePackagePath = $basePackageFile 1263 | } 1264 | 1265 | # proceed with installation, 1266 | $languagePackageFile = (Join-Path $destinationFolder (Get-FileName -name $savedPackage.Name -Culture $Culture -version $savedPackage.Version)) 1267 | 1268 | $langPackagePath = "" 1269 | 1270 | if (Test-Path $languagePackageFile) { 1271 | $savedCabFilesToInstall += $languagePackageFile 1272 | $installedWindowsPackages += $savedPackage 1273 | $langPackagePath = $languagePackageFile 1274 | } 1275 | 1276 | $savedCabFilesToInstallTuple += ([System.Tuple]::Create($basePackagePath, $langPackagePath)) 1277 | } 1278 | 1279 | $restartNeeded = $false 1280 | 1281 | try 1282 | { 1283 | # Installing offline scenario 1284 | if (-not [string]::IsNullOrWhiteSpace($mountDrive)) 1285 | { 1286 | # in this scenario, the function that calls us already mount the drive 1287 | Write-Verbose "Installing to mountdrive $mountDrive" 1288 | $successfullyInstalled.Value = Install-CabOfflineFromPath -mountDrive $mountDrive -packagePaths $savedCabFilesToInstall 1289 | } 1290 | else 1291 | { 1292 | Write-Verbose "Installing cab files $savedCabFilesToInstallTuple" 1293 | $successfullyInstalled.Value = Install-Online $savedCabFilesToInstallTuple -restartNeeded ([ref]$restartNeeded) 1294 | 1295 | if ($restartNeeded) 1296 | { 1297 | Write-Warning "Restart is needed to complete installation" 1298 | } 1299 | } 1300 | 1301 | } 1302 | catch 1303 | { 1304 | $successfullyInstalled.Value = $false 1305 | ThrowError -CallerPSCmdlet $PSCmdlet ` 1306 | -ExceptionName $_.Exception.GetType().FullName ` 1307 | -ExceptionMessage $_.Exception.Message ` 1308 | -ExceptionObject $Name ` 1309 | -ErrorId FailedToInstall ` 1310 | -ErrorCategory InvalidOperation 1311 | } 1312 | finally 1313 | { 1314 | # Remove the online source 1315 | if (([string]::IsNullOrWhiteSpace($source)) -or (-not (Test-Path $source))) 1316 | { 1317 | Remove-Item $destinationFolder -Recurse -Force 1318 | } 1319 | } 1320 | } 1321 | 1322 | $installedWindowsPackages 1323 | } 1324 | 1325 | ### 1326 | ### SUMMARY: Checks if the system is nano server or not 1327 | ### Look into the win32 operating system class 1328 | ### Returns True if running on Nano 1329 | ### False otherwise 1330 | ### 144: Server Standard 1331 | ### 143: Server Datacenter 1332 | ### 109: Test Images 1333 | ### 1334 | function IsNanoServer 1335 | { 1336 | if ($script:isNanoServerInitialized) 1337 | { 1338 | return $script:isNanoServer 1339 | } 1340 | else 1341 | { 1342 | $script:isNanoServerInitialized = $true 1343 | $operatingSystem = Get-CimInstance -ClassName win32_operatingsystem 1344 | $script:systemSKU = $operatingSystem.OperatingSystemSKU 1345 | $script:systemVersion = [System.Environment]::OSVersion.Version 1346 | $script:isNanoServer = ($systemSKU -eq 109) -or ($systemSKU -eq 144) -or ($systemSKU -eq 143) 1347 | return $script:isNanoServer 1348 | } 1349 | } 1350 | 1351 | ### 1352 | ### SUMMARY: Checks if the given destination is kosher or not 1353 | ### 1354 | function CheckDestination 1355 | { 1356 | param($Destination) 1357 | 1358 | # Check if entire path is folder structure 1359 | $dest_item = Get-Item $Destination ` 1360 | -ErrorAction SilentlyContinue ` 1361 | -WarningAction SilentlyContinue 1362 | 1363 | if($dest_item -is [System.IO.DirectoryInfo]) 1364 | { 1365 | return $true 1366 | } 1367 | else 1368 | { 1369 | Write-Verbose "Creating directory structure: $Destination" 1370 | mkdir $Destination 1371 | return $true 1372 | } 1373 | 1374 | return $false 1375 | } 1376 | 1377 | function CheckVersion 1378 | { 1379 | param 1380 | ( 1381 | [System.Version]$MinimumVersion, 1382 | [System.Version]$MaximumVersion, 1383 | [System.Version]$RequiredVersion, 1384 | [switch]$AllVersions 1385 | ) 1386 | 1387 | if($AllVersions -and $RequiredVersion) 1388 | { 1389 | Write-Error "AllVersions and RequiredVersion cannot be used together" 1390 | return $false 1391 | } 1392 | 1393 | if($AllVersions -or $RequiredVersion) 1394 | { 1395 | if($MinimumVersion -or $MaximumVersion) 1396 | { 1397 | Write-Error "AllVersions and RequiredVersion switch cannot be used with MinimumVersion or MaximumVersion" 1398 | return $false 1399 | } 1400 | } 1401 | 1402 | if($MinimumVersion -and $MaximumVersion) 1403 | { 1404 | if($MaximumVersion -lt $MinimumVersion) 1405 | { 1406 | Write-Error "Minimum Version cannot be more than Maximum Version" 1407 | return $false 1408 | } 1409 | } 1410 | 1411 | return $true 1412 | } 1413 | 1414 | function Get-FileName 1415 | { 1416 | param( 1417 | [string]$Culture, 1418 | [string]$name, 1419 | [string]$version 1420 | ) 1421 | 1422 | $fileName = $name + "_" + $Culture + "_" + $version.replace('.','-') + $script:WindowsPackageExtension 1423 | return $fileName 1424 | } 1425 | 1426 | ### 1427 | ### SUMMARY: Get the search index from Azure 1428 | ### 1429 | function Get-SearchIndex 1430 | { 1431 | param 1432 | ( 1433 | [switch] 1434 | $Force, 1435 | 1436 | [string] 1437 | $fwdLink 1438 | ) 1439 | 1440 | $fullUrl = Resolve-FwdLink $fwdLink 1441 | $fullUrl = $fullUrl.AbsoluteUri 1442 | $destination = $script:WindowsPackage + "\searchNanoPackageIndex.txt" 1443 | 1444 | if(Test-Path $destination) 1445 | { 1446 | Remove-Item $destination 1447 | DownloadFile -downloadURL $fullUrl ` 1448 | -destination $destination ` 1449 | -noProgress 1450 | } 1451 | else 1452 | { 1453 | DownloadFile -downloadURL $fullUrl ` 1454 | -destination $destination ` 1455 | -noProgress 1456 | } 1457 | 1458 | return $destination 1459 | } 1460 | 1461 | function Get-ImageCulture 1462 | { 1463 | param 1464 | ( 1465 | [string]$mountDrive 1466 | ) 1467 | 1468 | $languageSearch = dism /Image:$mountDrive /Get-Intl 1469 | 1470 | foreach ($languageString in $languageSearch) 1471 | { 1472 | if ($languageString -match "\s*Default\s*system\s*UI\s*language\s*:\s*([a-z][a-z]-[A-Z][A-Z])\s*") 1473 | { 1474 | return $matches[1] 1475 | } 1476 | } 1477 | 1478 | } 1479 | 1480 | ### 1481 | ### SUMMARY: Resolve the fwdlink to get the actual search URL 1482 | ### 1483 | function Resolve-FwdLink 1484 | { 1485 | param 1486 | ( 1487 | [parameter(Mandatory=$false)] 1488 | [System.String]$Uri 1489 | ) 1490 | 1491 | if(-not (IsNanoServer)) 1492 | { 1493 | Add-Type -AssemblyName System.Net.Http 1494 | } 1495 | $httpClient = New-Object System.Net.Http.HttpClient 1496 | $response = $httpclient.GetAsync($Uri) 1497 | $link = $response.Result.RequestMessage.RequestUri 1498 | 1499 | 1500 | return $link 1501 | } 1502 | 1503 | function Resolve-PathHelper 1504 | { 1505 | param 1506 | ( 1507 | [Parameter()] 1508 | [ValidateNotNullOrEmpty()] 1509 | [string[]] 1510 | $path, 1511 | 1512 | [Parameter()] 1513 | [switch] 1514 | $isLiteralPath, 1515 | 1516 | [Parameter()] 1517 | [ValidateNotNullOrEmpty()] 1518 | [System.Management.Automation.PSCmdlet] 1519 | $callerPSCmdlet 1520 | ) 1521 | 1522 | $resolvedPaths =@() 1523 | 1524 | foreach($currentPath in $path) 1525 | { 1526 | try 1527 | { 1528 | if($isLiteralPath) 1529 | { 1530 | $currentResolvedPaths = Microsoft.PowerShell.Management\Resolve-Path -LiteralPath $currentPath -ErrorAction Stop 1531 | } 1532 | else 1533 | { 1534 | $currentResolvedPaths = Microsoft.PowerShell.Management\Resolve-Path -Path $currentPath -ErrorAction Stop 1535 | } 1536 | } 1537 | catch 1538 | { 1539 | # Caller checks and throws an error if required 1540 | $resolvedPaths += $currentPath 1541 | continue 1542 | } 1543 | 1544 | foreach($currentResolvedPath in $currentResolvedPaths) 1545 | { 1546 | $resolvedPaths += $currentResolvedPath.ProviderPath 1547 | } 1548 | } 1549 | 1550 | $resolvedPaths 1551 | } 1552 | 1553 | ### Function to get package dependencies that need to be install 1554 | ### This will return false if there is a dependency loop 1555 | function Get-DependenciesToInstall($availablePackages, $culture, [psobject]$package, [System.Collections.ArrayList]$dependenciesToBeInstalled) 1556 | { 1557 | # no dependencies to be installed 1558 | if ($null -eq $package.Dependencies -or $package.Dependencies.Count -eq 0) { 1559 | $dependenciesToBeInstalled.Add($package) | Out-NUll 1560 | return $true 1561 | } 1562 | 1563 | $permanentlyMarked = [System.Collections.ArrayList]::new() 1564 | $temporarilyMarked = [System.Collections.ArrayList]::new() 1565 | 1566 | if (-not (DepthFirstVisit -package $package ` 1567 | -temporarilyMarked $temporarilyMarked ` 1568 | -permanentlyMarked $permanentlyMarked ` 1569 | -dependenciesToBeInstalled $dependenciesToBeInstalled ` 1570 | -culture $culture ` 1571 | -availablePackages $availablePackages)) { 1572 | return $false 1573 | } 1574 | 1575 | return $true 1576 | } 1577 | 1578 | function DepthFirstVisit( 1579 | [psobject]$package, 1580 | [System.Collections.ArrayList]$permanentlyMarked, 1581 | [System.Collections.ArrayList]$temporarilyMarked, 1582 | [System.Collections.ArrayList]$dependenciesToBeInstalled, 1583 | $culture, 1584 | $availablePackages) { 1585 | 1586 | # get the hash of the package which is name!#!version 1587 | $hash = $package.Name.ToLower() + "!#!" + (Convert-Version $package.Version) 1588 | 1589 | if ($temporarilyMarked.IndexOf($hash) -ge 0) { 1590 | # dependency loop! 1591 | return $false 1592 | } 1593 | 1594 | # no need to visit permanently marked node 1595 | if ($permanentlyMarked.IndexOf($hash) -ge 0) { 1596 | return $true 1597 | } 1598 | 1599 | $temporarilyMarked.Add($hash) | Out-Null 1600 | 1601 | foreach ($dependency in $package.Dependencies) { 1602 | $skip = $false 1603 | 1604 | # check which dependencies are already installed 1605 | foreach ($availablePackage in $availablePackages) 1606 | { 1607 | # check whether language pack is installed (don't need to check base because if language pack is installed then base must be there) 1608 | if (Test-PackageWithSearchQuery -fullyQualifiedName $availablePackage -name $dependency.Name -requiredVersion $dependency.Version -Culture $culture) 1609 | { 1610 | # if it is, skipped this dependency 1611 | $skip = $true 1612 | } 1613 | } 1614 | 1615 | if ($skip) { 1616 | continue 1617 | } 1618 | 1619 | $dependencyPackage = Find -Name $dependency.Name -RequiredVersion $dependency.Version -Culture $culture 1620 | 1621 | if (-not (DepthFirstVisit -package $dependencyPackage -permanentlyMarked $permanentlyMarked ` 1622 | -temporarilyMarked $temporarilyMarked -culture $culture ` 1623 | -availablePackages $availablePackages -dependenciesToBeInstalled $dependenciesToBeInstalled)) { 1624 | return $false 1625 | } 1626 | } 1627 | 1628 | # add to list to install later 1629 | $dependenciesToBeInstalled.Add($package) | Out-Null 1630 | 1631 | # mark the node permanently 1632 | $permanentlyMarked.Add($hash) | Out-Null 1633 | 1634 | # remove the temporary mark 1635 | $temporarilyMarked.Remove($hash) | Out-Null 1636 | 1637 | return $true 1638 | } 1639 | 1640 | <# 1641 | Parse and return a dependency version 1642 | The version string is either a simple version or an arithmetic range 1643 | e.g. 1644 | 1.0 --> 1.0 ≤ x 1645 | (,1.0] --> x ≤ 1.0 1646 | (,1.0) --> x lt 1.0 1647 | [1.0] --> x == 1.0 1648 | (1.0,) --> 1.0 lt x 1649 | (1.0, 2.0) --> 1.0 lt x lt 2.0 1650 | [1.0, 2.0] --> 1.0 ≤ x ≤ 2.0 1651 | 1652 | #> 1653 | function NanoServerVersionMatched([string]$dependencyVersionString, [version]$version) 1654 | { 1655 | if ([string]::IsNullOrWhiteSpace($dependencyVersionString) -or $version -eq $null) 1656 | { 1657 | return $true 1658 | } 1659 | 1660 | $dependencyVersionString = $dependencyVersionString.Trim() 1661 | 1662 | $first = $dependencyVersionString[0] 1663 | $last = $dependencyVersionString[-1] 1664 | 1665 | if ($first -ne '(' -and $first -ne '[' -and $last -ne ']' -and $last -ne ')') 1666 | { 1667 | # stand alone so it is min inclusive 1668 | $versionToBeCompared = Convert-Version $dependencyVersionString 1669 | 1670 | return ($versionToBeCompared -ge $version) 1671 | } 1672 | 1673 | # now dep version string must have length > 3 1674 | if ($dependencyVersionString.Length -lt 3) 1675 | { 1676 | return $true 1677 | } 1678 | 1679 | if ($first -ne '(' -and $first -ne '[') 1680 | { 1681 | # first character must be either ( or [ 1682 | return $true 1683 | } 1684 | 1685 | if ($last -ne ']' -and $last -ne ')') 1686 | { 1687 | # last character must be either ] or ) 1688 | return $true 1689 | } 1690 | 1691 | # inclusive if the first or last is [ or ], otherwise exclusive 1692 | $minInclusive = ($first -eq '[') 1693 | $maxInclusive = ($last -eq ']') 1694 | 1695 | $dependencyVersionString = $dependencyVersionString.Substring(1, $dependencyVersionString.Length - 2) 1696 | 1697 | $parts = $dependencyVersionString.Split(',') 1698 | 1699 | if ($parts.Length -gt 2) 1700 | { 1701 | return $true 1702 | } 1703 | 1704 | $minVersion = Convert-Version $parts[0] 1705 | 1706 | if ($parts.Length -eq 1) 1707 | { 1708 | $maxVersion = $minVersion 1709 | } 1710 | else 1711 | { 1712 | $maxVersion = Convert-Version $parts[1] 1713 | } 1714 | 1715 | if ($minVersion -eq $null -and $maxVersion -eq $null) 1716 | { 1717 | return $true 1718 | } 1719 | 1720 | # now we can compare 1721 | if ($minVersion -ne $null) 1722 | { 1723 | if ($minInclusive) 1724 | { 1725 | # min inclusive so version must be >= minversion 1726 | if ($version -lt $minVersion) 1727 | { 1728 | return $false 1729 | } 1730 | } 1731 | else 1732 | { 1733 | # not mininclusive so version must be > minversion 1734 | if ($version -le $minVersion) 1735 | { 1736 | return $false 1737 | } 1738 | } 1739 | } 1740 | 1741 | if ($maxVersion -ne $null) 1742 | { 1743 | if ($maxInclusive) 1744 | { 1745 | if ($version -gt $maxVersion) 1746 | { 1747 | return $false 1748 | } 1749 | } 1750 | else 1751 | { 1752 | if ($version -ge $maxVersion) 1753 | { 1754 | return $false 1755 | } 1756 | } 1757 | } 1758 | 1759 | return $true 1760 | } 1761 | 1762 | function ConvertNanoServerVersionToString([string]$NanoServerVersion) 1763 | { 1764 | $result = $NanoServerVersion 1765 | 1766 | if ([string]::IsNullOrWhiteSpace($NanoServerVersion)) 1767 | { 1768 | return $result 1769 | } 1770 | 1771 | $NanoServerVersion = $NanoServerVersion.Trim() 1772 | 1773 | $first = $NanoServerVersion[0] 1774 | $last = $NanoServerVersion[-1] 1775 | 1776 | if ($first -ne '(' -and $first -ne '[' -and $last -ne ']' -and $last -ne ')') 1777 | { 1778 | return "minimum NanoServer version of $NanoServerVersion (inclusive)" 1779 | } 1780 | 1781 | # now dep version string must have length > 3 1782 | if ($NanoServerVersion.Length -lt 3) 1783 | { 1784 | return $NanoServerVersion 1785 | } 1786 | 1787 | if ($first -ne '(' -and $first -ne '[') 1788 | { 1789 | # first character must be either ( or [ 1790 | return $NanoServerVersion 1791 | } 1792 | 1793 | if ($last -ne ']' -and $last -ne ')') 1794 | { 1795 | # last character must be either ] or ) 1796 | return $NanoServerVersion 1797 | } 1798 | 1799 | # inclusive if the first or last is [ or ], otherwise exclusive 1800 | $minInclusive = ($first -eq '[') 1801 | $maxInclusive = ($last -eq ']') 1802 | 1803 | $NanoServerVersion = $NanoServerVersion.Substring(1, $NanoServerVersion.Length - 2) 1804 | 1805 | $parts = $NanoServerVersion.Split(',') 1806 | 1807 | if ($parts.Length -gt 2) 1808 | { 1809 | return $NanoServerVersion 1810 | } 1811 | 1812 | $minVersion = $parts[0] 1813 | 1814 | if ($parts.Length -eq 1) 1815 | { 1816 | $maxVersion = $minVersion 1817 | } 1818 | else 1819 | { 1820 | $maxVersion = $parts[1] 1821 | } 1822 | 1823 | if ($minVersion -eq $null -and $maxVersion -eq $null) 1824 | { 1825 | return $NanoServerVersion 1826 | } 1827 | 1828 | $result = "" 1829 | 1830 | # now we can compare 1831 | if (-not [string]::IsNullOrWhiteSpace($minVersion)) 1832 | { 1833 | $result += "minimum NanoServer version of $minVersion" 1834 | 1835 | if ($minInclusive) 1836 | { 1837 | $result += " (inclusive)" 1838 | } 1839 | } 1840 | 1841 | if (-not [string]::IsNullOrWhiteSpace($maxVersion)) 1842 | { 1843 | # there is already something in result, so add an and 1844 | if (-not [string]::IsNullOrWhiteSpace($result)) 1845 | { 1846 | $result += " and " 1847 | } 1848 | 1849 | $result += "maximum NanoServer version of $maxVersion" 1850 | if ($maxInclusive) 1851 | { 1852 | $result += " (inclusive)" 1853 | } 1854 | } 1855 | 1856 | return $result 1857 | } 1858 | 1859 | #endregion Helpers 1860 | 1861 | #region Source 1862 | 1863 | ### 1864 | ### SUMMARY: Gets the source from where to get the images 1865 | ### Initializes the variables for find, download and install 1866 | ### RETURN: 1867 | ### Returns the type of 1868 | ### 1869 | function Get-Source 1870 | { 1871 | param($sources) 1872 | 1873 | Set-ModuleSourcesVariable 1874 | 1875 | $listOfSources = @() 1876 | 1877 | # if sources is supplied and we cannot find it, error out 1878 | if((-not [string]::IsNullOrWhiteSpace($sources)) -and (-not $script:windowsPackageSources.Contains($sources))) 1879 | { 1880 | ThrowError -CallerPSCmdlet $PSCmdlet ` 1881 | -ExceptionName System.Exception ` 1882 | -ExceptionMessage "Unable to find package source '$sources'. Use Get-PackageSource to see all available package sources." ` 1883 | -ExceptionObject $sources ` 1884 | -ErrorId WildCardCharsAreNotSupported ` 1885 | -ErrorCategory InvalidData 1886 | } 1887 | 1888 | foreach($mySource in $script:WindowsPackageSources.Values) 1889 | { 1890 | if((-not $sources) -or 1891 | (($mySource.Name -eq $sources) -or 1892 | ($mySource.Location -eq $sources))) 1893 | { 1894 | $tempHolder = @{} 1895 | 1896 | $location = $mySource.SourceLocation 1897 | $tempHolder.Add("SourceLocation", $location) 1898 | 1899 | $packageSourceName = $mySource.Name 1900 | $tempHolder.Add("Name", $packageSourceName) 1901 | 1902 | $listOfSources += $tempHolder 1903 | } 1904 | } 1905 | 1906 | return $listOfSources 1907 | } 1908 | 1909 | function Set-ModuleSourcesVariable 1910 | { 1911 | if(Microsoft.PowerShell.Management\Test-Path $script:file_modules) 1912 | { 1913 | $script:windowsPackageSources = DeSerializePSObject -Path $script:file_modules 1914 | } 1915 | 1916 | if((-not (Microsoft.PowerShell.Management\Test-Path $script:file_modules))) 1917 | { 1918 | $script:windowsPackageSources = [ordered]@{} 1919 | $defaultModuleName = "NanoServerPackageSource" 1920 | 1921 | $defaultModuleSource = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ 1922 | Name = $script:defaultPackageName 1923 | SourceLocation = $script:defaultPackageLocation 1924 | Trusted=$false 1925 | Registered= $true 1926 | InstallationPolicy = "Untrusted" 1927 | }) 1928 | 1929 | $script:windowsPackageSources.Add($defaultModuleName, $defaultModuleSource) 1930 | Save-ModuleSource 1931 | } 1932 | } 1933 | 1934 | function Get-PackageProviderName 1935 | { 1936 | return $script:providerName 1937 | } 1938 | 1939 | ### 1940 | ### SUMMARY: Deserializes the PSObject 1941 | ### 1942 | function DeSerializePSObject 1943 | { 1944 | [CmdletBinding(PositionalBinding=$false)] 1945 | Param 1946 | ( 1947 | [Parameter(Mandatory=$true)] 1948 | $Path 1949 | ) 1950 | $filecontent = Microsoft.PowerShell.Management\Get-Content -Path $Path 1951 | [System.Management.Automation.PSSerializer]::Deserialize($filecontent) 1952 | } 1953 | 1954 | function Save-ModuleSource 1955 | { 1956 | # check if exists 1957 | if(-not (Test-Path $script:WindowsPackage)) 1958 | { 1959 | $null = mkdir $script:WindowsPackage 1960 | } 1961 | 1962 | # seralize module 1963 | Microsoft.PowerShell.Utility\Out-File -FilePath $script:file_modules ` 1964 | -Force ` 1965 | -InputObject ([System.Management.Automation.PSSerializer]::Serialize($script:windowsPackageSources)) 1966 | } 1967 | 1968 | function Resolve-PackageSource 1969 | { 1970 | Set-ModuleSourcesVariable 1971 | 1972 | $SourceName = $request.PackageSources 1973 | 1974 | if(-not $SourceName) 1975 | { 1976 | $SourceName = "*" 1977 | } 1978 | 1979 | foreach($moduleSourceName in $SourceName) 1980 | { 1981 | if($request.IsCanceled) 1982 | { 1983 | return 1984 | } 1985 | 1986 | $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $moduleSourceName,$script:wildcardOptions 1987 | $moduleSourceFound = $false 1988 | 1989 | $script:windowsPackageSources.GetEnumerator() | 1990 | Microsoft.PowerShell.Core\Where-Object {$wildcardPattern.IsMatch($_.Key)} | 1991 | Microsoft.PowerShell.Core\ForEach-Object { 1992 | $moduleSource = $script:windowsPackageSources[$_.Key] 1993 | $packageSource = New-PackageSourceFromModuleSource -ModuleSource $moduleSource 1994 | Write-Output -InputObject $packageSource 1995 | $moduleSourceFound = $true 1996 | } 1997 | 1998 | if(-not $moduleSourceFound) 1999 | { 2000 | $sourceName = Get-SourceName -Location $moduleSourceName 2001 | if($sourceName) 2002 | { 2003 | $moduleSource = $script:windowsPackageSources[$sourceName] 2004 | $packageSource = New-PackageSourceFromModuleSource -ModuleSource $moduleSource 2005 | Write-Output -InputObject $packageSource 2006 | } 2007 | } 2008 | } 2009 | } 2010 | 2011 | function Add-PackageSource 2012 | { 2013 | [CmdletBinding()] 2014 | param 2015 | ( 2016 | [string] 2017 | $Name, 2018 | 2019 | [string] 2020 | $Location, 2021 | 2022 | [bool] 2023 | $Trusted 2024 | ) 2025 | 2026 | Set-ModuleSourcesVariable 2027 | 2028 | $options = $request.Options 2029 | $Default = $false 2030 | 2031 | if($options) 2032 | { 2033 | foreach( $o in $options.Keys ) 2034 | { 2035 | Write-Debug ("OPTION dictionary: {0} => {1}" -f ($o, $options[$o]) ) 2036 | } 2037 | 2038 | if($options.ContainsKey('Default')) 2039 | { 2040 | $Default = $options['Default'] 2041 | } 2042 | } 2043 | 2044 | if($Default) 2045 | { 2046 | $Name = $script:defaultPackageName 2047 | $Location = $script:defaultPackageLocation 2048 | } 2049 | 2050 | # Check if this package source already exists 2051 | foreach($psModuleSource in $script:windowsPackageSources.Values) 2052 | { 2053 | if(($Name -eq $psModuleSource.Name) -or 2054 | ($Location -eq $psModuleSource.SourceLocation)) 2055 | { 2056 | throw "Package Source $Name with $Location already exists" 2057 | } 2058 | } 2059 | 2060 | # Add new module source 2061 | $moduleSource = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ 2062 | Name = $Name 2063 | SourceLocation = $Location 2064 | Trusted=$Trusted 2065 | Registered= $true 2066 | InstallationPolicy = if($Trusted) {'Trusted'} else {'Untrusted'} 2067 | }) 2068 | 2069 | $script:windowsPackageSources.Add($Name, $moduleSource) 2070 | Save-ModuleSource 2071 | Write-Output -InputObject (New-PackageSourceFromModuleSource -ModuleSource $moduleSource) 2072 | } 2073 | 2074 | function Remove-PackageSource 2075 | { 2076 | param 2077 | ( 2078 | [string] 2079 | $Name 2080 | ) 2081 | 2082 | Set-ModuleSourcesVariable -Force 2083 | 2084 | if(-not $script:windowsPackageSources.Contains($Name)) 2085 | { 2086 | Write-Error -Message "Package source $Name not found" ` 2087 | -ErrorId "Package source $Name not found" ` 2088 | -Category InvalidOperation ` 2089 | -TargetObject $Name 2090 | continue 2091 | } 2092 | 2093 | $script:windowsPackageSources.Remove($Name) 2094 | 2095 | Save-ModuleSource 2096 | } 2097 | 2098 | function New-PackageSourceFromModuleSource 2099 | { 2100 | param 2101 | ( 2102 | [Parameter(Mandatory=$true)] 2103 | $ModuleSource 2104 | ) 2105 | 2106 | $packageSourceDetails = @{} 2107 | 2108 | # create a new package source 2109 | $src = New-PackageSource -Name $ModuleSource.Name ` 2110 | -Location $ModuleSource.SourceLocation ` 2111 | -Trusted $ModuleSource.Trusted ` 2112 | -Registered $ModuleSource.Registered ` 2113 | -Details $packageSourceDetails 2114 | 2115 | # return the package source object. 2116 | Write-Output -InputObject $src 2117 | } 2118 | 2119 | function Get-SourceName 2120 | { 2121 | [CmdletBinding()] 2122 | [OutputType("string")] 2123 | Param 2124 | ( 2125 | [Parameter(Mandatory=$true)] 2126 | [ValidateNotNullOrEmpty()] 2127 | [string] 2128 | $Location 2129 | ) 2130 | 2131 | Set-ModuleSourcesVariable 2132 | 2133 | foreach($psModuleSource in $script:windowsPackageSources.Values) 2134 | { 2135 | if(($psModuleSource.Name -eq $Location) -or 2136 | ($psModuleSource.SourceLocation -eq $Location)) 2137 | { 2138 | return $psModuleSource.Name 2139 | } 2140 | } 2141 | } 2142 | 2143 | #endregion Source 2144 | 2145 | #region OneGet 2146 | 2147 | function Find-Package 2148 | { 2149 | [CmdletBinding()] 2150 | param 2151 | ( 2152 | [string] 2153 | $Name, 2154 | 2155 | [string] 2156 | $requiredVersion, 2157 | 2158 | [string] 2159 | $minimumVersion, 2160 | 2161 | [string] 2162 | $maximumVersion 2163 | ) 2164 | 2165 | $options = $request.Options 2166 | $languageChosen = $null 2167 | $wildcardPattern = $null 2168 | $force = $false 2169 | $allVersions = $false 2170 | 2171 | # path to the offline nano image 2172 | $imagePath = $null 2173 | $source = $null 2174 | 2175 | # check out what options the users give us 2176 | if($options) 2177 | { 2178 | foreach( $o in $options.Keys ) 2179 | { 2180 | Write-Debug ("OPTION dictionary: {0} => {1}" -f ($o, $options[$o]) ) 2181 | } 2182 | 2183 | if($options.ContainsKey('Force')) 2184 | { 2185 | $force = $options['Force'] 2186 | } 2187 | 2188 | if ($options.ContainsKey("ImagePath")) 2189 | { 2190 | $imagePath = $options['ImagePath'] 2191 | } 2192 | 2193 | if ($options.ContainsKey("Culture")) 2194 | { 2195 | $languageChosen = $options['Culture'] 2196 | } 2197 | 2198 | if ($options.ContainsKey('Source')) 2199 | { 2200 | $source = $options['Source'] 2201 | } 2202 | 2203 | if ($options.ContainsKey('AllVersions')) 2204 | { 2205 | $allVersions = $options['AllVersions'] 2206 | } 2207 | } 2208 | 2209 | # Let find-windowspackage handle the query 2210 | $convertedRequiredVersion = Convert-Version $requiredVersion 2211 | $convertedMinVersion = Convert-Version $minimumVersion 2212 | $convertedMaxVersion = Convert-Version $maximumVersion 2213 | 2214 | if ([string]::IsNullOrWhiteSpace($Name)) 2215 | { 2216 | $Name = @('*') 2217 | } 2218 | 2219 | $packages = Find -Name $Name ` 2220 | -MinimumVersion $convertedMinVersion ` 2221 | -MaximumVersion $convertedMaxVersion ` 2222 | -RequiredVersion $convertedRequiredVersion ` 2223 | -AllVersions:$AllVersions ` 2224 | -Culture $languageChosen ` 2225 | -Force:$Force 2226 | 2227 | if ($null -eq $packages) 2228 | { 2229 | return 2230 | } 2231 | 2232 | # check for packages that match the query 2233 | foreach ($package in $packages) 2234 | { 2235 | $swid = New-SoftwareIdentityFromWindowsPackageItemInfo $package 2236 | Write-Output $swid 2237 | } 2238 | } 2239 | 2240 | function Install-Package 2241 | { 2242 | [CmdletBinding()] 2243 | param 2244 | ( 2245 | [string] 2246 | $fastPackageReference 2247 | ) 2248 | 2249 | Write-Verbose $fastPackageReference 2250 | 2251 | # path to the offline nano image 2252 | $imagePath = $null 2253 | 2254 | $options = $request.Options 2255 | 2256 | $force = $false 2257 | 2258 | # check out what options the users give us 2259 | if($options) 2260 | { 2261 | foreach( $o in $options.Keys ) 2262 | { 2263 | Write-Debug ("OPTION dictionary: {0} => {1}" -f ($o, $options[$o]) ) 2264 | } 2265 | 2266 | if($options.ContainsKey('Force')) 2267 | { 2268 | $force = $options['Force'] 2269 | } 2270 | 2271 | if ($options.ContainsKey("ToVhd")) 2272 | { 2273 | $imagePath = $options['ToVhd'] 2274 | } 2275 | 2276 | if ($options.ContainsKey("Culture")) 2277 | { 2278 | $languageChosen = $options['Culture'] 2279 | } 2280 | } 2281 | 2282 | # if image path is supplied and it points to non existing file, returns 2283 | if (-not [string]::IsNullOrWhiteSpace($imagePath) -and (-not ([System.IO.File]::Exists($ImagePath)))) 2284 | { 2285 | ThrowError -CallerPSCmdlet $PSCmdlet ` 2286 | -ExceptionName System.ArgumentException ` 2287 | -ExceptionMessage "$ImagePath does not exist" ` 2288 | -ExceptionObject $imagePath ` 2289 | -ErrorId "InvalidImagePath" ` 2290 | -ErrorCategory InvalidData 2291 | 2292 | return 2293 | } 2294 | 2295 | [string[]] $splitterArray = @("$separator") 2296 | 2297 | [string[]] $resultArray = $fastPackageReference.Split($splitterArray, [System.StringSplitOptions]::None) 2298 | 2299 | $name = $resultArray[0] 2300 | $version = $resultArray[1] 2301 | $Culture = $resultArray[3] 2302 | $Sku = $resultArray[4] 2303 | $NanoServerVersion = $resultArray[5] 2304 | 2305 | # if culture is a string, set it to null (this means user did not supply culture) 2306 | if ($Culture.Contains(',')) 2307 | { 2308 | $Culture = '' 2309 | } 2310 | 2311 | $convertedVersion = Convert-Version $version 2312 | 2313 | [bool]$success = $false 2314 | 2315 | $mountDrive = $null 2316 | 2317 | $availablePackages = @() 2318 | 2319 | if (-not [string]::IsNullOrWhiteSpace($imagePath)) 2320 | { 2321 | $mountDrive = New-MountDrive 2322 | 2323 | Write-Verbose "Mounting $imagePath to $mountDrive" 2324 | 2325 | $null = Mount-WindowsImage -ImagePath $imagePath -Index 1 -Path $mountDrive 2326 | 2327 | if (-not $force) { 2328 | $fileKey = Get-FileKey -filePath $imagePath 2329 | 2330 | $availablePackages = @(($script:imagePathCache[$fileKey]).Keys) 2331 | } 2332 | 2333 | # if this package does not apply to standard, we have to check whether the nano is standard or not 2334 | if (-not $Sku.Contains("144") -or (-not [string]::IsNullOrWhiteSpace($NanoServerVersion))) 2335 | { 2336 | $regKey = $null 2337 | 2338 | $mountedVhdEdition = "ERROR" 2339 | $vhdNanoServerVersion = $null 2340 | 2341 | try 2342 | { 2343 | reg load HKLM\NANOSERVERPACKAGEVHDSYS "$mountDrive\Windows\System32\config\SOFTWARE" | Out-Null 2344 | $regKey = dir 'HKLM:\NANOSERVERPACKAGEVHDSYS\Microsoft\Windows NT' 2345 | $mountedVHDEdition = $regKey.GetValue("EditionID") 2346 | $majorVersion = $regKey.GetValue("CurrentMajorVersionNumber") 2347 | $minorVersion = $regKey.GetValue("CurrentMinorVersionNumber") 2348 | $buildVersion = $regKey.GetValue("CurrentBuildNumber") 2349 | $vhdNanoServerVersion = [version]::new($majorVersion, $minorVersion, $buildVersion, 0) 2350 | } 2351 | catch 2352 | { 2353 | # ERROR 2354 | $mountedVHDEdition = "ERROR" 2355 | $vhdNanoServerVersion = $null 2356 | } 2357 | finally 2358 | { 2359 | try 2360 | { 2361 | if ($regKey -ne $null) 2362 | { 2363 | $regKey.Handle.Close() 2364 | [gc]::Collect() 2365 | reg unload HKLM\NANOSERVERPACKAGEVHDSYS | Out-Null 2366 | } 2367 | } 2368 | catch { } 2369 | } 2370 | 2371 | # if this is not applicable to server standard nano 2372 | if (-not $Sku.Contains("144") -and $mountedVHDEdition -eq "ServerStandardNano") 2373 | { 2374 | # cannot be installed 2375 | # unmount 2376 | if ($null -ne $mountDrive) 2377 | { 2378 | Write-Verbose "Unmounting mountdrive $mountDrive" 2379 | Remove-MountDrive $mountDrive -discard $true 2380 | } 2381 | 2382 | ThrowError -CallerPSCmdlet $PSCmdlet ` 2383 | -ExceptionName System.ArgumentException ` 2384 | -ExceptionMessage "$name cannot be installed on this edition of NanoServer" ` 2385 | -ExceptionObject $fastPackageReference ` 2386 | -ErrorId FailedToInstall ` 2387 | -ErrorCategory InvalidData 2388 | } 2389 | 2390 | if (-not [string]::IsNullOrWhiteSpace($NanoServerVersion) -and -not (NanoServerVersionMatched -dependencyVersionString $NanoServerVersion -version $vhdNanoServerVersion)) 2391 | { 2392 | ThrowError -CallerPSCmdlet $PSCmdlet ` 2393 | -ExceptionName System.ArgumentException ` 2394 | -ExceptionMessage "The package '$name $version' requires $(ConvertNanoServerVersionToString $NanoServerVersion). But the current Nano Server has version $vhdNanoServerVersion which is out of this range. Please see https://github.com/OneGet/NanoServerPackage for instructions." ` 2395 | -ExceptionObject $fastPackageReference ` 2396 | -ErrorId FailedToInstall ` 2397 | -ErrorCategory InvalidData 2398 | } 2399 | } 2400 | } 2401 | else { 2402 | if (IsNanoServer) 2403 | { 2404 | # if this is a nano, then systemSKU would be populated after isnanoserver call 2405 | if (-not $Sku.Contains($script:systemSKU.ToString())) 2406 | { 2407 | ThrowError -CallerPSCmdlet $PSCmdlet ` 2408 | -ExceptionName System.ArgumentException ` 2409 | -ExceptionMessage "$name cannot be installed on this edition of NanoServer" ` 2410 | -ExceptionObject $fastPackageReference ` 2411 | -ErrorId FailedToInstall ` 2412 | -ErrorCategory InvalidData 2413 | } 2414 | 2415 | # if this is nanoserver, then we should also have the version populated 2416 | if (-not (NanoServerVersionMatched -dependencyVersionString $NanoServerVersion -version $script:systemVersion)) 2417 | { 2418 | ThrowError -CallerPSCmdlet $PSCmdlet ` 2419 | -ExceptionName System.ArgumentException ` 2420 | -ExceptionMessage "The package '$name' with version $version requires $(ConvertNanoServerVersionToString $NanoServerVersion). But current Nano Server has version $script:systemVersion which is out of this range. Please see https://github.com/OneGet/NanoServerPackage for instructions." ` 2421 | -ExceptionObject $fastPackageReference ` 2422 | -ErrorId FailedToInstall ` 2423 | -ErrorCategory InvalidData 2424 | } 2425 | } 2426 | 2427 | if (-not $force) { 2428 | $availablePackages = @($script:onlinePackageCache.Keys) 2429 | } 2430 | } 2431 | 2432 | try 2433 | { 2434 | $installedPackages = Install-PackageHelper -Name $name ` 2435 | -Culture $Culture ` 2436 | -Version $convertedVersion ` 2437 | -mountDrive $mountDrive ` 2438 | -successfullyInstalled ([ref]$success) ` 2439 | -availablePackages: $availablePackages 2440 | 2441 | foreach ($installedPackage in $installedPackages) 2442 | { 2443 | Write-Output (New-SoftwareIdentityFromWindowsPackageItemInfo ($installedPackage)) 2444 | } 2445 | } 2446 | finally 2447 | { 2448 | # unmount 2449 | if ($null -ne $mountDrive) 2450 | { 2451 | Write-Verbose "Unmounting mountdrive $mountDrive" 2452 | Remove-MountDrive $mountDrive -discard (-not $success) 2453 | } 2454 | } 2455 | } 2456 | 2457 | function Download-Package 2458 | { 2459 | [CmdletBinding()] 2460 | param 2461 | ( 2462 | [Parameter(Mandatory=$true)] 2463 | [ValidateNotNullOrEmpty()] 2464 | [string] 2465 | $FastPackageReference, 2466 | 2467 | [Parameter(Mandatory=$true)] 2468 | [ValidateNotNullOrEmpty()] 2469 | [string] 2470 | $Location 2471 | ) 2472 | 2473 | [string[]] $splitterArray = @("$separator") 2474 | 2475 | [string[]] $resultArray = $fastPackageReference.Split($splitterArray, [System.StringSplitOptions]::None) 2476 | 2477 | $name = $resultArray[0] 2478 | $version = $resultArray[1] 2479 | #$source = $resultArray[2] 2480 | $Culture = $resultArray[3] 2481 | $convertedVersion = Convert-Version $version 2482 | 2483 | # if culture is a string, set it to null (this means user did not supply culture) 2484 | if ($Culture.Contains(',')) 2485 | { 2486 | $Culture = '' 2487 | } 2488 | 2489 | # no culture given, use culture of the system 2490 | if ([string]::IsNullOrWhiteSpace($Culture)) 2491 | { 2492 | $Culture = (Get-Culture).Name 2493 | } 2494 | 2495 | $force = $false 2496 | $options = $request.Options 2497 | 2498 | if ($options) 2499 | { 2500 | if ($options.ContainsKey('Force')) 2501 | { 2502 | $force = $options['Force'] 2503 | } 2504 | } 2505 | 2506 | $savedWindowsPackageItems = Save-NanoServerPackage -Name $name ` 2507 | -Culture $Culture ` 2508 | -RequiredVersion $convertedVersion ` 2509 | -Path $Location ` 2510 | -Force:$force 2511 | 2512 | foreach ($savedWindowsPackageItem in $savedWindowsPackageItems) 2513 | { 2514 | Write-Output (New-SoftwareIdentityFromWindowsPackageItemInfo $savedWindowsPackageItem) 2515 | } 2516 | } 2517 | 2518 | function Get-InstalledPackage 2519 | { 2520 | [CmdletBinding()] 2521 | param 2522 | ( 2523 | [Parameter()] 2524 | [string] 2525 | $Name, 2526 | 2527 | [Parameter()] 2528 | [Version] 2529 | $RequiredVersion, 2530 | 2531 | [Parameter()] 2532 | [Version] 2533 | $MinimumVersion, 2534 | 2535 | [Parameter()] 2536 | [Version] 2537 | $MaximumVersion 2538 | ) 2539 | 2540 | $options = $request.Options 2541 | $wildcardPattern = $null 2542 | $languageChosen = $null 2543 | 2544 | # If name is null or whitespace, interpret as * 2545 | if ([string]::IsNullOrWhiteSpace($Name)) 2546 | { 2547 | $Name = "*" 2548 | } 2549 | 2550 | if ([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name)) 2551 | { 2552 | $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $Name,$script:wildcardOptions 2553 | } 2554 | 2555 | $force = $false 2556 | 2557 | # path to the offline nano image 2558 | $imagePath = $null 2559 | 2560 | # check out what options the users give us 2561 | if($options) 2562 | { 2563 | foreach( $o in $options.Keys ) 2564 | { 2565 | Write-Debug ("OPTION dictionary: {0} => {1}" -f ($o, $options[$o]) ) 2566 | } 2567 | 2568 | if($options.ContainsKey('Force')) 2569 | { 2570 | $force = $options['Force'] 2571 | } 2572 | 2573 | if ($options.ContainsKey("FromVhd")) 2574 | { 2575 | $imagePath = $options['FromVhd'] 2576 | } 2577 | elseif ($options.ContainsKey("ToVhd")) 2578 | { 2579 | # in case of install 2580 | $imagePath = $options['ToVhd'] 2581 | } 2582 | 2583 | if ($options.ContainsKey("Culture")) 2584 | { 2585 | $languageChosen = $options['Culture'] 2586 | 2587 | $cannotConvertCulture = $false 2588 | 2589 | # try to convert the culture 2590 | try 2591 | { 2592 | $convertedCulture = [cultureinfo]$languageChosen 2593 | 2594 | # apparently, converting culture 'blah' will not work but 'bla' will work ?!? 2595 | if ($null -eq $convertedCulture -or $null -eq $convertedCulture.DisplayName -or $convertedCulture.DisplayName.Trim() -match "Unknown Language") 2596 | { 2597 | $cannotConvertCulture = $true 2598 | } 2599 | } 2600 | catch 2601 | { 2602 | $cannotConvertCulture = $true 2603 | } 2604 | 2605 | # if we cannot convert culture, throw error 2606 | if ($cannotConvertCulture) 2607 | { 2608 | ThrowError -CallerPSCmdlet $PSCmdlet ` 2609 | -ExceptionName System.ArgumentException ` 2610 | -ExceptionMessage "$languageChosen is not a valid culture" ` 2611 | -ExceptionObject $languageChosen ` 2612 | -ErrorId InvalidCulture ` 2613 | -ErrorCategory InvalidData 2614 | } 2615 | } 2616 | } 2617 | 2618 | if (-not [string]::IsNullOrWhiteSpace($imagePath)) 2619 | { 2620 | $mountDrive = New-MountDrive 2621 | 2622 | Write-Verbose "Mounting $imagePath to $mountDrive" 2623 | 2624 | $id = Write-Progress -ParentId 1 -Activity "Getting packages information" 2625 | if (-not $id) 2626 | { 2627 | $id = 1 2628 | } 2629 | 2630 | Write-Progress -Activity "Mounting $imagePath to $mountDrive" -PercentComplete 0 -Id $id 2631 | 2632 | Mount-WindowsImage -ImagePath $imagePath -Index 1 -Path $mountDrive 2633 | 2634 | Write-Verbose "Done Mounting" 2635 | 2636 | # Now we can try to find the packages 2637 | try 2638 | { 2639 | # Get all the available packages on the mountdrive 2640 | $packages = Get-WindowsPackage -Path $mountDrive 2641 | Write-Verbose "Finished getting packages from $mountDrive with $($packages.Count) packages" 2642 | $count = 0 2643 | 2644 | Write-Progress -Activity "Getting packages information from $mountDrive" -PercentComplete 5 -Id $id 2645 | 2646 | $packagesToBeReturned = New-Object 'System.Collections.Generic.List[string]' 2647 | 2648 | $availablePackages = $packages.PackageName.ToLower() 2649 | 2650 | # check for packages that match the query 2651 | foreach ($fullyQualifiedName in $availablePackages) 2652 | { 2653 | if (Test-PackageWithSearchQuery -fullyQualifiedName $fullyQualifiedName -requiredVersion $RequiredVersion -Name $Name ` 2654 | -minimumVersion $MinimumVersion -maximumVersion $MaximumVersion -Culture $languageChosen -wildCardPattern $wildcardPattern) 2655 | { 2656 | $packagesToBeReturned.Add($fullyQualifiedName) 2657 | } 2658 | } 2659 | 2660 | $fileKey = Get-FileKey -filePath $imagePath 2661 | 2662 | $packageDictionary = @{} 2663 | 2664 | # try to get the cache if it exists, otherwise create one 2665 | if (-not $script:imagePathCache.ContainsKey($fileKey)) 2666 | { 2667 | $script:imagePathCache.Add($fileKey, $packageDictionary) 2668 | } 2669 | 2670 | $packageDictionary = $script:imagePathCache[$fileKey] 2671 | 2672 | foreach ($fullyQualifiedName in $availablePackages) 2673 | { 2674 | if (-not $packageDictionary.ContainsKey($fullyQualifiedName)) 2675 | { 2676 | $packageDictionary[$fullyQualifiedName] = $null 2677 | } 2678 | } 2679 | 2680 | # Before we get more details, we will clump together base and language pack if they have same name and version 2681 | if ($packagesToBeReturned.Count -gt 0) 2682 | { 2683 | $packagesToBeReturned = Filter-Packages $packagesToBeReturned 2684 | } 2685 | 2686 | foreach ($package in $packagesToBeReturned) 2687 | { 2688 | # scale the percent from 1 to 80 to account for the initial and final step of mounting and dismounting 2689 | $percentComplete = (($count*80/$packages.Count) + 10) -as [int] 2690 | $count += 1 2691 | 2692 | Write-Progress -Activity ` 2693 | "Getting package information for $package in $mountDrive" ` 2694 | -PercentComplete $percentComplete ` 2695 | -Id $id 2696 | 2697 | # store the information in cache if it's not there or if user uses force 2698 | if ((-not $packageDictionary.ContainsKey($package)) -or ($null -eq $packageDictionary[$package]) -or $force) 2699 | { 2700 | Write-Debug "Getting information for package $package and storing it in cache" 2701 | # store the information in cache 2702 | $packageDictionary[$package.ToLower()] = Get-WindowsPackage -PackageName $package -Path $mountDrive 2703 | } 2704 | 2705 | Write-Output (New-SoftwareIdentityPackage $packageDictionary[$package.ToLower()] -src $imagePath) 2706 | } 2707 | 2708 | # Get the list of packages that are in the cache but not in the latest list we have 2709 | $packageToBeRemoved = @() 2710 | foreach ($pkg in $packageDictionary.GetEnumerator()) 2711 | { 2712 | if (-not $availablePackages.Contains($pkg.Name)) 2713 | { 2714 | $packageToBeRemoved += $pkg.Name 2715 | } 2716 | } 2717 | 2718 | # Remove packages in this list from the cache 2719 | foreach ($pkg in $packageToBeRemoved) 2720 | { 2721 | if ($packageDictionary.ContainsKey($pkg)) 2722 | { 2723 | $packageDictionary.Remove($pkg) 2724 | } 2725 | } 2726 | } 2727 | finally 2728 | { 2729 | Write-Progress -Activity "Unmounting image from $mountDrive" -PercentComplete 90 -Id $id 2730 | # Unmount and delete directory 2731 | Remove-MountDrive $mountDrive 2732 | Write-Progress -Completed -Id $id -Activity "Completed" 2733 | } 2734 | } 2735 | else 2736 | { 2737 | $count = 0; 2738 | $id = Write-Progress -ParentId 1 -Activity "Getting packages information" 2739 | if (-not $id) 2740 | { 2741 | $id = 1 2742 | } 2743 | 2744 | Write-Progress -Activity "Getting available packages on the system" -PercentComplete 0 -Id $id 2745 | # getting the packages on the current operating system 2746 | # getting basic information about all the packages online 2747 | $packages = Get-WindowsPackage -Online 2748 | 2749 | try 2750 | { 2751 | $packagesToBeReturned = New-Object 'System.Collections.Generic.List[string]' 2752 | $availablePackages = $packages.PackageName.ToLower() 2753 | 2754 | # Get the list of packages that match what the user input 2755 | foreach ($fullyQualifiedName in $availablePackages) 2756 | { 2757 | if (Test-PackageWithSearchQuery -fullyQualifiedName $fullyQualifiedName -requiredVersion $RequiredVersion -Name $Name ` 2758 | -minimumVersion $MinimumVersion -maximumVersion $MaximumVersion -Culture $languageChosen -wildCardPattern $wildCardPattern) 2759 | { 2760 | # Store the whole name instead of just the name without language or version 2761 | $packagesToBeReturned.Add($fullyQualifiedName) 2762 | } 2763 | 2764 | if (-not ($script:onlinePackageCache.ContainsKey($fullyQualifiedName))) 2765 | { 2766 | $script:onlinePackageCache[$fullyQualifiedName] = $null 2767 | } 2768 | } 2769 | 2770 | # nothing matched! 2771 | if ($packagesToBeReturned.Count -gt 0) 2772 | { 2773 | # Before we get more details, we will clump together base and language pack if they have same name and version 2774 | $packagesToBeReturned = Filter-Packages $packagesToBeReturned 2775 | } 2776 | 2777 | # Only update the list of packages that the user gives 2778 | foreach ($package in $packagesToBeReturned) 2779 | { 2780 | $percentComplete = ($count*90/$packages.Count + 10) -as [int] 2781 | Write-Progress -Activity "Getting package information for $($package)" -PercentComplete $percentComplete -Id $id 2782 | $count += 1; 2783 | 2784 | # store the information in cache if it's not there or if user uses force 2785 | if ((-not $script:onlinePackageCache.ContainsKey($package)) -or ($null -eq $script:onlinePackageCache[$package]) -or $force) 2786 | { 2787 | Write-Debug "Getting information for package $package and storing it in cache" 2788 | # store the information in cache 2789 | $script:onlinePackageCache[$package.ToLower()] = Get-WindowsPackage -Online -PackageName $package 2790 | } 2791 | 2792 | if ($script:onlinePackageCache.ContainsKey($package)) 2793 | { 2794 | # convert package to swid and return 2795 | Write-Output (New-SoftwareIdentityPackage $script:onlinePackageCache[$package] -src "Local Machine") 2796 | } 2797 | } 2798 | 2799 | # Get the list of packages that are in the cache but not in the latest list we have 2800 | $packageToBeRemoved = @() 2801 | foreach ($pkg in $script:onlinePackageCache.GetEnumerator()) 2802 | { 2803 | if (-not $availablePackages.Contains($pkg.Name)) 2804 | { 2805 | $packageToBeRemoved += $pkg.Name 2806 | } 2807 | } 2808 | 2809 | # Remove packages in this list from the cache 2810 | foreach ($pkg in $packageToBeRemoved) 2811 | { 2812 | if ($script:onlinePackageCache.ContainsKey($pkg)) 2813 | { 2814 | $script:onlinePackageCache.Remove($pkg) 2815 | } 2816 | } 2817 | } 2818 | finally 2819 | { 2820 | Write-Progress -Completed -Id $id -Activity "Completed" 2821 | } 2822 | } 2823 | } 2824 | 2825 | function Uninstall-Package 2826 | { 2827 | [CmdletBinding()] 2828 | param 2829 | ( 2830 | [Parameter(Mandatory=$true)] 2831 | [ValidateNotNullOrEmpty()] 2832 | [string] 2833 | $fastPackageReference 2834 | ) 2835 | 2836 | 2837 | Write-Verbose $fastPackageReference 2838 | 2839 | # path to the offline nano image 2840 | $imagePath = $null 2841 | 2842 | $options = $request.Options 2843 | 2844 | $force = $false 2845 | 2846 | $languageChosen = $null 2847 | 2848 | # check out what options the users give us 2849 | if($options) 2850 | { 2851 | foreach( $o in $options.Keys ) 2852 | { 2853 | Write-Debug ("OPTION dictionary: {0} => {1}" -f ($o, $options[$o]) ) 2854 | } 2855 | 2856 | if($options.ContainsKey('Force')) 2857 | { 2858 | $force = $options['Force'] 2859 | } 2860 | 2861 | if ($options.ContainsKey("FromVhd")) 2862 | { 2863 | $imagePath = $options['FromVhd'] 2864 | } 2865 | 2866 | if ($options.ContainsKey("Culture")) 2867 | { 2868 | $languageChosen = $options['Culture'] 2869 | } 2870 | } 2871 | 2872 | # if image path is supplied and it points to non existing file, returns 2873 | if (-not [string]::IsNullOrWhiteSpace($imagePath) -and (-not ([System.IO.File]::Exists($ImagePath)))) 2874 | { 2875 | ThrowError -CallerPSCmdlet $PSCmdlet ` 2876 | -ExceptionName System.ArgumentException ` 2877 | -ExceptionMessage "$ImagePath does not exist" ` 2878 | -ExceptionObject $imagePath ` 2879 | -ErrorId "InvalidImagePath" ` 2880 | -ErrorCategory InvalidData 2881 | 2882 | return 2883 | } 2884 | 2885 | [string[]] $splitterArray = @("$separator") 2886 | 2887 | [string[]] $resultArray = $fastPackageReference.Split($splitterArray, [System.StringSplitOptions]::None) 2888 | 2889 | $packageId = $resultArray[4] 2890 | 2891 | $basePackage = $null 2892 | 2893 | if ($null -eq $languageChosen) { 2894 | Write-Verbose "No language chosen, removing base too" 2895 | 2896 | $packageFragments = $packageId.Split("~") 2897 | 2898 | $packageFragments[3] = "" 2899 | 2900 | $basePackage = [string]::Join("~", $packageFragments) 2901 | 2902 | Write-Debug "New package id is $packageId and the base package is $basePackage" 2903 | } 2904 | 2905 | if (-not [string]::IsNullOrWhiteSpace($imagePath)) { 2906 | # removing from vhd 2907 | $mountDrive = New-MountDrive 2908 | 2909 | Write-Verbose "Mounting $imagePath to $mountDrive" 2910 | 2911 | $null = Mount-WindowsImage -ImagePath $imagePath -Index 1 -Path $mountDrive 2912 | 2913 | $success = $false 2914 | 2915 | try { 2916 | Write-Verbose "Removing $packageId from $mountDrive" 2917 | 2918 | # time to update the cache since we remove this package 2919 | $fileKey = Get-FileKey -filePath $imagePath 2920 | 2921 | if ($script:imagePathCache.ContainsKey($fileKey)) { 2922 | $packageDictionary = $script:imagePathCache[$fileKey] 2923 | 2924 | if ($null -ne $packageDictionary) { 2925 | if ($packageDictionary.ContainsKey($packageId)) { 2926 | Remove-WindowsPackage -PackageName $packageId -Path $mountDrive | Out-Null 2927 | $packageDictionary.Remove($packageId) 2928 | } 2929 | } 2930 | else { 2931 | # nothing in cache 2932 | Remove-WindowsPackage -PackageName $packageId -Path $mountDrive | Out-Null 2933 | } 2934 | } 2935 | 2936 | 2937 | if (-not ([string]::IsNullOrWhiteSpace($basePackage))) 2938 | { 2939 | Remove-WindowsPackage -PackageName $basePackage -Path $mountDrive | Out-Null 2940 | } 2941 | 2942 | if ($script:imagePathCache.ContainsKey($fileKey)) { 2943 | $packageDictionary = $script:imagePathCache[$fileKey] 2944 | 2945 | if ($null -ne $packageDictionary) 2946 | { 2947 | if ($packageDictionary.ContainsKey($packageId)) 2948 | { 2949 | $packageDictionary.Remove($packageId) 2950 | } 2951 | 2952 | if ((-not [string]::IsNullOrWhiteSpace($basePackage)) -and $packageDictionary.ContainsKey($basePackage)) 2953 | { 2954 | $packageDictionary.Remove($basePackage) 2955 | } 2956 | } 2957 | } 2958 | 2959 | $success = $true 2960 | } 2961 | catch { 2962 | $success = $false 2963 | } 2964 | finally { 2965 | # unmount 2966 | if ($null -ne $mountDrive) 2967 | { 2968 | Write-Verbose "Unmounting mountdrive $mountDrive" 2969 | Remove-MountDrive $mountDrive -discard (-not $success) 2970 | } 2971 | } 2972 | } 2973 | else { 2974 | Write-Verbose "Uninstalling $packageId online" 2975 | 2976 | $messages = $null 2977 | 2978 | if ($script:onlinePackageCache.ContainsKey($packageId)) { 2979 | # removing online 2980 | $messages = Remove-WindowsPackage -PackageName $packageId -Online -NoRestart -WarningAction Ignore 2981 | $script:onlinePackageCache.Remove($packageId) 2982 | } 2983 | 2984 | $restart = $messages -ne $null -and $messages.RestartNeeded 2985 | 2986 | if (-not [string]::IsNullOrWhiteSpace($basePackage)) 2987 | { 2988 | if ($script:onlinePackageCache.ContainsKey($basePackage)) { 2989 | $messages = Remove-WindowsPackage -PackageName $basePackage -Online -NoRestart -WarningAction Ignore 2990 | $script:onlinePackageCache.Remove($basePackage) 2991 | $restart = $restart -or ($messages -ne $null -and $messages.RestartNeeded) 2992 | } 2993 | } 2994 | 2995 | if ($restart) 2996 | { 2997 | Write-Warning "Restart is needed to complete installation" 2998 | } 2999 | } 3000 | } 3001 | 3002 | #endregion OneGet 3003 | 3004 | #region OneGet Helpers 3005 | 3006 | # This is to display long name 3007 | function Get-Feature 3008 | { 3009 | Write-Output -InputObject (New-Feature -Name "DisplayLongName") 3010 | } 3011 | 3012 | function Get-DynamicOptions 3013 | { 3014 | param 3015 | ( 3016 | [Microsoft.PackageManagement.MetaProvider.PowerShell.OptionCategory] 3017 | $category 3018 | ) 3019 | 3020 | switch($category) 3021 | { 3022 | # This is for dynamic options used by install/uninstall and get-packages 3023 | Install 3024 | { 3025 | # Provides path to image 3026 | Write-Output -InputObject (New-DynamicOption -Category $category -Name "ToVhd" -ExpectedType File -IsRequired $false) 3027 | Write-Output -InputObject (New-DynamicOption -Category $category -Name "FromVhd" -ExpectedType File -IsRequired $false) 3028 | Write-Output -InputObject (New-DynamicOption -Category $Category -Name "DisplayCulture" -ExpectedType Switch -IsRequired $false) 3029 | Write-Output -InputObject (New-DynamicOption -Category $category -Name "Culture" -ExpectedType String -IsRequired $false) 3030 | } 3031 | Package 3032 | { 3033 | # Switch to display culture 3034 | Write-Output -InputObject (New-DynamicOption -Category $Category -Name "DisplayCulture" -ExpectedType Switch -IsRequired $false) 3035 | # Provides path to image 3036 | Write-Output -InputObject (New-DynamicOption -Category $category -Name "ImagePath" -ExpectedType String -IsRequired $false) 3037 | Write-Output -InputObject (New-DynamicOption -Category $category -Name "Culture" -ExpectedType String -IsRequired $false) 3038 | } 3039 | Source 3040 | { 3041 | Write-Output -InputObject (New-DynamicOption -Category $Category -Name "Default" -ExpectedType Switch -IsRequired $false) 3042 | } 3043 | } 3044 | } 3045 | 3046 | function Initialize-Provider 3047 | { 3048 | write-debug "In $script:providerName - Initialize-Provider" 3049 | } 3050 | 3051 | function Get-PackageProviderName 3052 | { 3053 | return $script:providerName 3054 | } 3055 | 3056 | function New-SoftwareIdentityFromWindowsPackageItemInfo 3057 | { 3058 | [Cmdletbinding()] 3059 | param( 3060 | [PSCustomObject] 3061 | $package 3062 | ) 3063 | 3064 | $details = @{} 3065 | $Culture = $package.Culture 3066 | 3067 | $fastPackageReference = $package.Name + 3068 | $separator + $package.version + 3069 | $separator + $package.Source + 3070 | $separator + $Culture + 3071 | $separator + $package.Sku + 3072 | $separator + $package.NanoServerVersion 3073 | 3074 | $Name = [System.IO.Path]::GetFileNameWithoutExtension($package.Name) 3075 | 3076 | $deps = (new-Object -TypeName System.Collections.ArrayList) 3077 | 3078 | foreach( $dep in $package.Dependencies ) 3079 | { 3080 | # Add each dependency and say it's from this provider. 3081 | $newDep = New-Dependency -ProviderName $script:providerName ` 3082 | -PackageName $dep.Name ` 3083 | -Version $dep.Version 3084 | $deps.Add( $newDep ) 3085 | } 3086 | 3087 | $details["Sku"] = $package.Sku 3088 | $details["NanoServerVersion"] = $package.NanoServerVersion 3089 | 3090 | $params = @{FastPackageReference = $fastPackageReference; 3091 | Name = $Name; 3092 | Version = $package.version.ToString(); 3093 | versionScheme = "MultiPartNumeric"; 3094 | Source = $package.Source; 3095 | Summary = $package.Description; 3096 | Details = $details; 3097 | Culture = $Culture; 3098 | Dependencies = $deps; 3099 | } 3100 | 3101 | try 3102 | { 3103 | New-SoftwareIdentity @params 3104 | } 3105 | catch 3106 | { 3107 | # throw error because older version of packagemanagement does not have culture key 3108 | $params.Remove("Culture") 3109 | New-SoftwareIdentity @params 3110 | } 3111 | } 3112 | 3113 | # this function is used by get-installedpackage 3114 | function New-SoftwareIdentityPackage 3115 | { 3116 | [CmdletBinding()] 3117 | param( 3118 | [Parameter(Mandatory=$true)] 3119 | [Microsoft.Dism.Commands.AdvancedPackageObject] 3120 | $package, 3121 | 3122 | $src="", 3123 | 3124 | $InstallLocation="" 3125 | ) 3126 | 3127 | $details = @{} 3128 | 3129 | $details.Add("Applicable", $package.Applicable) 3130 | 3131 | if ($null -ne $package.InstallTime) 3132 | { 3133 | $details.Add("InstallTime", $package.InstallTime) 3134 | } 3135 | 3136 | if ($null -ne $package.CompletelyOfflineCapable) 3137 | { 3138 | $details.Add("CompletelyOfflineCapable", $package.CompletelyOfflineCapable) 3139 | } 3140 | 3141 | if ($null -ne $package.PackageState) 3142 | { 3143 | $details.Add("PackageState", $package.PackageState) 3144 | } 3145 | 3146 | if ($null -ne $package.RestartRequired) 3147 | { 3148 | $details.Add("RestartRequired", $package.RestartRequired) 3149 | } 3150 | 3151 | if (-not [string]::IsNullOrWhiteSpace($package.ReleaseType)) 3152 | { 3153 | $details.Add("ReleaseType", $package.ReleaseType) 3154 | } 3155 | 3156 | if ([string]::IsNullOrWhiteSpace($Package.ProductVersion)) { 3157 | $version = "0.0" 3158 | } 3159 | else { 3160 | $version = $Package.ProductVersion 3161 | } 3162 | 3163 | # format is name~publickeytoken~architecture~language~version 3164 | 3165 | $packageNameFractions = $Package.PackageName.Split('~') 3166 | 3167 | if (-not [string]::IsNullOrWhiteSpace($packageNameFractions[0])) 3168 | { 3169 | $name = $packageNameFractions[0] 3170 | } 3171 | else 3172 | { 3173 | $name = $package.PackageName 3174 | } 3175 | 3176 | # DISM team has a workaround where they add Feature in the name. We should remove that. 3177 | # THIS IS A TEMPORARY FIX 3178 | if ($name -like "*Feature-Package*") 3179 | { 3180 | $name = $name -replace "Feature-Package","Package" 3181 | } 3182 | 3183 | if (-not [string]::IsNullOrWhiteSpace($packageNameFractions[1])) 3184 | { 3185 | $details.Add("publickey", $packageNameFractions[1]) 3186 | } 3187 | 3188 | if (-not [string]::IsNullOrWhiteSpace($packageNameFractions[2])) 3189 | { 3190 | $details.Add("architecture", $packageNameFractions[2]) 3191 | } 3192 | 3193 | $Culture = $packageNameFractions[3] 3194 | 3195 | if (-not [string]::IsNullOrWhiteSpace($packageNameFractions[4])) 3196 | { 3197 | $version = $packageNameFractions[4] 3198 | } 3199 | 3200 | $fastPackageReference = $name + $separator + $version + $separator + $InstallLocation + $separator + $Culture + $separator + $package.PackageName 3201 | 3202 | $params = @{FastPackageReference = $fastPackageReference; 3203 | Name = $name; 3204 | Version = $version; 3205 | versionScheme = "MultiPartNumeric"; 3206 | Source = $src; 3207 | Details = $details; 3208 | Culture = $Culture; 3209 | TagId = $Package.PackageName; 3210 | } 3211 | 3212 | try 3213 | { 3214 | New-SoftwareIdentity @params 3215 | } 3216 | catch 3217 | { 3218 | # throw error because older version of packagemanagement does not have culture key 3219 | $params.Remove("Culture") 3220 | $params.Remove("TagId") 3221 | New-SoftwareIdentity @params 3222 | } 3223 | } 3224 | 3225 | function Install-CabOfflineFromPath 3226 | { 3227 | [CmdletBinding()] 3228 | param 3229 | ( 3230 | [string]$mountDrive, 3231 | 3232 | [string[]]$packagePaths 3233 | ) 3234 | 3235 | $discard = $false 3236 | 3237 | $id = Write-Progress -ParentId 1 -Activity "Installing packages" 3238 | 3239 | if (-not $id) 3240 | { 3241 | $id = 1 3242 | } 3243 | 3244 | # Now we can try to install the package 3245 | try 3246 | { 3247 | $count = 0 3248 | 3249 | foreach ($packagePath in $packagePaths) 3250 | { 3251 | $percentComplete = ($count*100/$packagePaths.Count) -as [int] 3252 | 3253 | $count += 1 3254 | 3255 | Write-Progress -Activity ` 3256 | "Installing package $packagePath" ` 3257 | -PercentComplete $percentComplete ` 3258 | -Id $id 3259 | 3260 | Write-Verbose "Adding $packagePath to $mountDrive" 3261 | Add-WindowsPackage -PackagePath $packagePath -Path $mountDrive -NoRestart -WarningAction Ignore | Out-Null 3262 | } 3263 | } 3264 | catch 3265 | { 3266 | $discard = $true 3267 | ThrowError -CallerPSCmdlet $PSCmdlet ` 3268 | -ExceptionName $_.Exception.GetType().FullName ` 3269 | -ExceptionMessage $_.Exception.Message ` 3270 | -ExceptionObject $RequiredVersion ` 3271 | -ErrorId FailedToInstall ` 3272 | -ErrorCategory InvalidOperation 3273 | } 3274 | finally 3275 | { 3276 | Write-Progress -Completed -Id $id -Activity "Completed" 3277 | } 3278 | 3279 | # returns back whether we have successfully installed or not 3280 | return (-not $discard) 3281 | } 3282 | 3283 | function Install-Online 3284 | { 3285 | [CmdletBinding()] 3286 | param 3287 | ( 3288 | $packagePaths, 3289 | [ref]$restartNeeded 3290 | ) 3291 | 3292 | $rollBack = $false 3293 | 3294 | $count = 0; 3295 | Write-Verbose "Installing $($packagePaths.Count) packages online" 3296 | $id = Write-Progress -ParentId 1 -Activity "Installing packages online" 3297 | if (-not $id) 3298 | { 3299 | $id = 1 3300 | } 3301 | 3302 | try 3303 | { 3304 | # first package of each pair is base, second is language 3305 | 3306 | foreach ($packageTuple in $packagePaths) 3307 | { 3308 | $packagePath = $packageTuple.Item1 3309 | 3310 | $messages = $null 3311 | 3312 | $restart = $false 3313 | 3314 | $percentComplete = $count*100/$packagePaths.Count -as [int] 3315 | 3316 | for($i = 0; $i -lt 2; $i += 1) 3317 | { 3318 | # valid base path 3319 | if (-not [string]::IsNullOrWhiteSpace($packagePath)) 3320 | { 3321 | Write-Progress -Activity "Installing package $($packagePath)" -PercentComplete $percentComplete -Id $id 3322 | 3323 | try 3324 | { 3325 | $messages = Add-WindowsPackage -PackagePath $packagePath -Online -NoRestart -WarningAction Ignore -ErrorAction Ignore 3326 | 3327 | if ($messages -ne $null -and $messages.RestartNeeded) 3328 | { 3329 | $restart = $true 3330 | } 3331 | } 3332 | catch { } 3333 | } 3334 | 3335 | # now install the language, even if the base fails, sometimes language will succeed 3336 | $packagePath = $packageTuple.Item2 3337 | 3338 | if (-not [string]::IsNullOrWhiteSpace($packagePath)) 3339 | { 3340 | Write-Progress -Activity "Installing package $($packagePath)" -PercentComplete $percentComplete -Id $id 3341 | 3342 | $hasError = $false 3343 | 3344 | # first try 3345 | if ($i -eq 0) 3346 | { 3347 | # try catch for the first time we install 3348 | try 3349 | { 3350 | # don't try catch here because if this fails, that is it 3351 | $messages = Add-WindowsPackage -PackagePath $packagePath -Online -NoRestart -WarningAction Ignore 3352 | 3353 | # restart or not 3354 | if (-not $restart -and $messages -ne $null -and $messages.RestartNeeded) 3355 | { 3356 | $restart = $true 3357 | } 3358 | 3359 | if ($restart) 3360 | { 3361 | $restartNeeded.Value = $true 3362 | } 3363 | } 3364 | catch { $hasError = $true } 3365 | 3366 | # no error, break out 3367 | if (-not $hasError) 3368 | { 3369 | break 3370 | } 3371 | } 3372 | else 3373 | { 3374 | Write-Verbose "Trying to install $packagePath for a second time" 3375 | 3376 | # don't try catch here because if this fails, that is it 3377 | $messages = Add-WindowsPackage -PackagePath $packagePath -Online -NoRestart -WarningAction Ignore 3378 | 3379 | # restart or not 3380 | if (-not $restart -and $messages -ne $null -and $messages.RestartNeeded) 3381 | { 3382 | $restart = $true 3383 | } 3384 | 3385 | if ($restart) 3386 | { 3387 | $restartNeeded.Value = $true 3388 | } 3389 | } 3390 | } 3391 | } 3392 | 3393 | $count += 1 3394 | } 3395 | } 3396 | catch 3397 | { 3398 | $rollBack = $true 3399 | ThrowError -CallerPSCmdlet $PSCmdlet ` 3400 | -ExceptionName $_.Exception.GetType().FullName ` 3401 | -ExceptionMessage $_.Exception.Message ` 3402 | -ExceptionObject $RequiredVersion ` 3403 | -ErrorId FailedToInstall ` 3404 | -ErrorCategory InvalidOperation 3405 | } 3406 | finally 3407 | { 3408 | Write-Progress -Completed -Id $id -Activity "Completed" 3409 | } 3410 | 3411 | # returns whether we installed 3412 | return (-not $rollBack) 3413 | } 3414 | 3415 | function New-MountDrive 3416 | { 3417 | # getting packages from an offline image 3418 | # Mount to directory 3419 | while ($true) 3420 | { 3421 | $randomName = [System.IO.Path]::GetRandomFileName() 3422 | $mountDrive = "$env:LOCALAPPDATA\NanoServerPackageProvider\MountDirectories\$randomName" 3423 | 3424 | if (Test-Path $mountDrive) 3425 | { 3426 | # We should create a directory that hasn't existed before 3427 | continue; 3428 | } 3429 | else 3430 | { 3431 | $null = mkdir $mountDrive 3432 | return $mountDrive 3433 | } 3434 | } 3435 | } 3436 | 3437 | function Remove-MountDrive([string]$mountDrive, [bool]$discard) 3438 | { 3439 | Write-Verbose "Dismounting $mountDrive" 3440 | 3441 | # Discard won't save anything we did to the image 3442 | if ($discard) 3443 | { 3444 | $null = Dismount-WindowsImage -Path $mountDrive -Discard 3445 | } 3446 | else 3447 | { 3448 | # save will saves packages that we add to the image 3449 | $null = Dismount-WindowsImage -Path $mountDrive -Save 3450 | } 3451 | 3452 | Write-Verbose "Deleting $mountDrive" 3453 | Remove-Item -Path $mountDrive -Recurse -Force 3454 | } 3455 | 3456 | # Given a fully qualified name of a package with the format name~publickeytoken~architecture~language~version 3457 | # checks whether this matches the search query 3458 | function Test-PackageWithSearchQuery 3459 | { 3460 | [CmdletBinding()] 3461 | param 3462 | ( 3463 | [Parameter(ParameterSetName="FullyQualifiedName")] 3464 | [string]$fullyQualifiedName, 3465 | 3466 | [Parameter(ParameterSetName="WindowsPackage")] 3467 | [PSCustomObject]$WindowsPackage, 3468 | 3469 | [string]$requiredVersion, 3470 | 3471 | [string]$minimumVersion, 3472 | 3473 | [string]$maximumVersion, 3474 | 3475 | [string]$name, 3476 | 3477 | [string]$Culture, 3478 | 3479 | [System.Management.Automation.WildcardPattern]$wildCardPattern 3480 | ) 3481 | 3482 | if ($null -eq $WindowsPackage) 3483 | { 3484 | # Split up the whole name since the name has language version and packagename in it 3485 | # format is name~publickeytoken~architecture~language~version 3486 | # now we want the package name to have en-us at the end if package is not base 3487 | $packageNameFractions = $fullyQualifiedName.Split('~') 3488 | $packageName = $packageNameFractions[0] 3489 | $packageLanguage = $packageNameFractions[3] 3490 | $version = $packageNameFractions[4] 3491 | 3492 | # DISM team has a workaround where they add Feature in the name. We should remove that. 3493 | # THIS IS A TEMPORARY FIX 3494 | if ($packageName -like "*Feature-Package") 3495 | { 3496 | $packageName = $packageName -replace "Feature-Package", "Package" 3497 | } 3498 | } 3499 | else 3500 | { 3501 | $packageName = $WindowsPackage.Name 3502 | $packageLanguage = $WindowsPackage.Culture 3503 | $version = $WindowsPackage.version.ToString() 3504 | } 3505 | 3506 | # there is a chance user supplies * 3507 | if (-not [string]::IsNullOrWhiteSpace($packageLanguage)) 3508 | { 3509 | $packageNameWithLanguage = "$packageName" + "_" + "$packageLanguage" 3510 | } 3511 | 3512 | if ($null -ne $wildCardPattern) 3513 | { 3514 | # matching already ignore case 3515 | if (-not $wildCardPattern.IsMatch($packageName)) 3516 | { 3517 | # we proceed if wildcard match _ 3518 | if (-not [string]::IsNullOrWhiteSpace($packageLanguage)) 3519 | { 3520 | if (-not $wildCardPattern.IsMatch($packageLanguage)) 3521 | { 3522 | return $false 3523 | } 3524 | } 3525 | else 3526 | { 3527 | return $false 3528 | } 3529 | } 3530 | } 3531 | else 3532 | { 3533 | # no wildcard so check for name if we are given a name 3534 | # eq operation is case insensitive 3535 | if (-not [string]::IsNullOrWhiteSpace($name) -and $name -ne $packageName) 3536 | { 3537 | # there is a chance user supplies _ 3538 | if (-not [string]::IsNullOrWhiteSpace($packageLanguage)) 3539 | { 3540 | # we proceed if name match _ 3541 | if ($name -ne $packageNameWithLanguage) 3542 | { 3543 | return $false 3544 | } 3545 | } 3546 | else 3547 | { 3548 | return $false 3549 | } 3550 | } 3551 | } 3552 | 3553 | # now we check language if the user providers it 3554 | if (-not [string]::IsNullOrWhiteSpace($Culture)) 3555 | { 3556 | # if base, then packageLanguage needs to be null 3557 | if ($Culture -eq 'Base') 3558 | { 3559 | $Culture = '' 3560 | } 3561 | 3562 | if ($packageLanguage -ne $Culture) 3563 | { 3564 | return $false 3565 | } 3566 | } 3567 | 3568 | # normalize versions 3569 | $convertedVersion = Convert-Version $version 3570 | 3571 | # fails to normalize 3572 | if ($null -eq $convertedVersion) 3573 | { 3574 | return $false 3575 | } 3576 | 3577 | # now we check whether version is matched 3578 | if (-not [string]::IsNullOrWhiteSpace($RequiredVersion)) 3579 | { 3580 | $convertedRequiredVersion = Convert-Version $RequiredVersion 3581 | 3582 | # fails if conversion fails or version does not match 3583 | if (($null -eq $convertedRequiredVersion) -or ($convertedRequiredVersion -ne $convertedVersion)) 3584 | { 3585 | return $false 3586 | } 3587 | } 3588 | 3589 | # packagemanagement will make sure requiredversion is not used with either min or max so we don't have to worry about that 3590 | if (-not [string]::IsNullOrWhiteSpace($MinimumVersion)) 3591 | { 3592 | $convertedMinimumVersion = Convert-Version $MinimumVersion 3593 | 3594 | # the converted version should be greater or equal to min version, not the other way round 3595 | if (($null -eq $convertedMinimumVersion) -or ($convertedMinimumVersion -gt $convertedVersion)) 3596 | { 3597 | return $false 3598 | } 3599 | } 3600 | 3601 | if (-not [string]::IsNullOrWhiteSpace($MaximumVersion)) 3602 | { 3603 | $convertedMaximumVersion = Convert-Version $MaximumVersion 3604 | 3605 | # converted version should be the same or less than max version 3606 | if (($null -eq $convertedMaximumVersion) -or ($convertedMaximumVersion -lt $convertedVersion)) 3607 | { 3608 | return $false 3609 | } 3610 | } 3611 | 3612 | # reached here means the package satisfied the search query 3613 | return $true 3614 | } 3615 | 3616 | # Given the path of a file, returns a simple key 3617 | # This function assumes that the user test that filePath exists 3618 | function Get-FileKey([string]$filePath) 3619 | { 3620 | $info = Get-ChildItem $filePath 3621 | return ($filePath + $separator + $info.Length + $separator + $info.CreationTime.ToShortDateString()) 3622 | } 3623 | 3624 | function Convert-Version([string]$version) 3625 | { 3626 | if ([string]::IsNullOrWhiteSpace($version)) 3627 | { 3628 | return $null; 3629 | } 3630 | 3631 | # not supporting semver here. let's try to normalize the versions 3632 | if ($version.StartsWith(".")) 3633 | { 3634 | # add leading zeros 3635 | $version = "0" + $version 3636 | } 3637 | 3638 | # let's see how many parts are we given with the version 3639 | $parts = $version.Split(".").Count 3640 | 3641 | # add .0 dependending number of parts since we need 4 parts 3642 | while ($parts -lt 4) 3643 | { 3644 | $version = $version + ".0" 3645 | $parts += 1 3646 | } 3647 | 3648 | [version]$convertedVersion = $null 3649 | 3650 | # try to convert 3651 | if ([version]::TryParse($version, [ref]$convertedVersion)) 3652 | { 3653 | return $convertedVersion 3654 | } 3655 | 3656 | return $null; 3657 | } 3658 | 3659 | function Filter-Packages ([string[]]$packagesToBeReturned) 3660 | { 3661 | $helperDictionary = @{} 3662 | foreach ($package in $packagesToBeReturned) 3663 | { 3664 | # Split up the whole name since the name has language version and packagename in it 3665 | # format is name~publickeytoken~architecture~language~version 3666 | # now we want the package name to have en-us at the end if package is not base 3667 | $packageNameFractions = $package.Split('~') 3668 | $packageName = $packageNameFractions[0] 3669 | $packageLanguage = $packageNameFractions[3] 3670 | $version = $packageNameFractions[4] 3671 | 3672 | # use name and version as key 3673 | $key = $packageName + "~" + $version 3674 | 3675 | # haven't encountered this before 3676 | if (-not $helperDictionary.ContainsKey($key)) 3677 | { 3678 | $helperDictionary[$key] = @() 3679 | } 3680 | 3681 | $helperDictionary[$key] += $package 3682 | } 3683 | 3684 | $result = @() 3685 | 3686 | foreach ($packageArray in $helperDictionary.Values) 3687 | { 3688 | if ($null -eq $packageArray) 3689 | { 3690 | continue 3691 | } 3692 | 3693 | # only 1 member, then return that 3694 | if ($packageArray.Count -eq 1) 3695 | { 3696 | $result += $packageArray[0] 3697 | continue 3698 | } 3699 | 3700 | # otherwise, only returns the 1 with language 3701 | foreach ($possiblePackage in $packageArray) 3702 | { 3703 | $packageNameFractions = $possiblePackage.Split('~') 3704 | $packageName = $packageNameFractions[0] 3705 | $packageLanguage = $packageNameFractions[3] 3706 | $version = $packageNameFractions[4] 3707 | 3708 | if ([string]::IsNullOrWhiteSpace($packageLanguage)) 3709 | { 3710 | continue 3711 | } 3712 | 3713 | $result += $possiblePackage 3714 | } 3715 | } 3716 | 3717 | # group according to name 3718 | $groupedName = $result | Group-Object -Property {$_.Split('~')[0]} 3719 | foreach ($groupResult in $groupedName) 3720 | { 3721 | $groupResult.Group | Sort-Object -Property {Convert-Version $_.Split('~')[4]} -Descending 3722 | } 3723 | } 3724 | 3725 | # Utility to throw an errorrecord 3726 | function ThrowError 3727 | { 3728 | param 3729 | ( 3730 | [parameter(Mandatory = $true)] 3731 | [ValidateNotNullOrEmpty()] 3732 | [System.Management.Automation.PSCmdlet] 3733 | $CallerPSCmdlet, 3734 | 3735 | [parameter(Mandatory = $true)] 3736 | [ValidateNotNullOrEmpty()] 3737 | [System.String] 3738 | $ExceptionName, 3739 | 3740 | [parameter(Mandatory = $true)] 3741 | [ValidateNotNullOrEmpty()] 3742 | [System.String] 3743 | $ExceptionMessage, 3744 | 3745 | [System.Object] 3746 | $ExceptionObject, 3747 | 3748 | [parameter(Mandatory = $true)] 3749 | [ValidateNotNullOrEmpty()] 3750 | [System.String] 3751 | $ErrorId, 3752 | 3753 | [parameter(Mandatory = $true)] 3754 | [ValidateNotNull()] 3755 | [System.Management.Automation.ErrorCategory] 3756 | $ErrorCategory 3757 | ) 3758 | 3759 | $exception = New-Object $ExceptionName $ExceptionMessage; 3760 | $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject 3761 | $CallerPSCmdlet.ThrowTerminatingError($errorRecord) 3762 | } 3763 | 3764 | #endregion OneGet Helpers -------------------------------------------------------------------------------- /NanoServerPackage/SaveHTTPItemUsingBITS.psm1: -------------------------------------------------------------------------------- 1 | function Save-HTTPItemUsingBitsTransfer 2 | { 3 | [CmdletBinding()] 4 | param( 5 | [Parameter(Mandatory=$true)] 6 | $Uri, 7 | [Parameter(Mandatory=$true)] 8 | $Destination, 9 | [switch] 10 | $noProgress 11 | ) 12 | 13 | begin 14 | { 15 | $fullUri = [Uri]$Uri 16 | if (($fullUri.Scheme -ne 'http') -and ($fullUri.Scheme -ne 'https')) 17 | { 18 | throw "Uri: $uri is not supported. Only http or https schema are supported." 19 | } 20 | } 21 | 22 | end 23 | { 24 | $jstate = $null 25 | [bool] $isTransferCompleted = $false 26 | try 27 | { 28 | $mycurrentPath = $script:MyInvocation.MyCommand.Path 29 | $myCurrentDirectory = Split-Path $mycurrentPath 30 | $bitsCommandPath = join-path $myCurrentDirectory "BitsOnNano.exe" 31 | $jobNameTemp = "SH{0}" -f (get-date).Ticks 32 | $output = & $bitsCommandPath -Start-Transfer -DisplayName $jobNameTemp -Source $Uri -Destination $Destination 33 | $le = $lastexitcode 34 | 35 | if (-not $noProgress) { 36 | $id = Write-Progress -ParentId 1 -Activity "Downloading from $Uri" 37 | } 38 | 39 | if($null -eq $id) 40 | { 41 | $id = 2 42 | } 43 | 44 | do 45 | { 46 | $jname,$jid,$jstate,$jbytesTransferred,$jbytesTotal,$null = $output -split ":" 47 | 48 | if ( (@("BG_JOB_STATE_ERROR", "BG_JOB_STATE_TRANSIENT_ERROR", "BG_JOB_STATE_CANCELLED") -contains $jstate) -or ($le)) 49 | { 50 | & $bitsCommandPath -Stop-Transfer -ID $jid | Out-Null 51 | 52 | throw "Save-HTTPItem: Bits Transfer failed. Job State: $jstate ExitCode = $le" 53 | } 54 | 55 | if (@("BG_JOB_STATE_TRANSFERRING") -contains $jstate) 56 | { 57 | $percentComplete = ($jbytesTransferred / $jbytesTotal) * 100 58 | $status = "Downloaded {0}MB of total {1}MB" -f ($jbytesTransferred/1mb),($jbytesTotal/1mb) 59 | if (-not $noProgress) { 60 | $null = Write-Progress -Activity "Downloading from $Uri" -PercentComplete $percentComplete -Id $id 61 | } 62 | } 63 | elseif (@("BG_JOB_STATE_TRANSFERRED") -contains $jstate) 64 | { 65 | & $bitsCommandPath -Remove-Transfer -ID $jid | Out-Null 66 | $isTransferCompleted = $true 67 | break; 68 | } 69 | elseif (@("BG_JOB_STATE_QUEUED") -contains $jstate) 70 | { 71 | if (-not $noProgress) { 72 | $null = Write-Progress -Activity "QUEUED" -PercentComplete 0 -Id $id 73 | } 74 | } 75 | elseif (@("BG_JOB_STATE_CONNECTING") -contains $jstate) 76 | { 77 | if (-not $noProgress) { 78 | $null = Write-Progress -Activity "CONNECTING" -PercentComplete 0 -Id $id 79 | } 80 | } 81 | elseif (@("BG_JOB_STATE_ACKNOWLEDGED") -contains $jstate) 82 | { 83 | if (-not $noProgress) { 84 | $null = Write-Progress -Activity "ACKNOWLEDGED" -PercentComplete 0 -Id $id 85 | } 86 | } 87 | 88 | Start-Sleep -Seconds 1 89 | $output = & $bitsCommandPath -Get-TransferStatus -ID $jid 90 | $le = $lastExitCode 91 | }while($true); 92 | } 93 | finally 94 | { 95 | #"Calling finally: jstate:$jstate isTC:$isTransferCompleted" 96 | if (-not $noProgress) { 97 | $null = Write-Progress -Completed -Activity "Downloading from $Uri" -Id $id 98 | } 99 | 100 | if ((-not $jstate) -and (-not $isTransferCompleted)) 101 | { 102 | "CleanUp:" 103 | & $bitsCommandPath -Stop-Transfer -ID $jid | Out-Null 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /NanoServerPackage/Test/Comprehensive.tests.ps1: -------------------------------------------------------------------------------- 1 |  2 | # you need to modify 3 | # 1. $vhdPath to match your Nano Vm 4 | 5 | $vhdPath = "C:\test\rtmRefreshStdEdition.vhd" 6 | 7 | 8 | $providerName = "NanoServerPackage" 9 | $commonPackages = @( 10 | "Microsoft-NanoServer-Defender-Package", 11 | "Microsoft-NanoServer-ShieldedVM-Package", 12 | "Microsoft-NanoServer-Compute-Package", 13 | "Microsoft-NanoServer-SecureStartup-Package", 14 | "Microsoft-NanoServer-Storage-Package", 15 | "Microsoft-NanoServer-OEM-Drivers-Package", 16 | "Microsoft-NanoServer-DSC-Package", 17 | "Microsoft-NanoServer-DNS-Package", 18 | "Microsoft-NanoServer-IIS-Package", 19 | "Microsoft-NanoServer-DCB-Package", 20 | "Microsoft-NanoServer-FailoverCluster-Package", 21 | "Microsoft-NanoServer-Host-Package", 22 | "Microsoft-NanoServer-Guest-Package", 23 | "Microsoft-NanoServer-Containers-Package", 24 | "Microsoft-NanoServer-SCVMM-Package", 25 | "Microsoft-NanoServer-SCVMM-Compute-Package", 26 | "Microsoft-NanoServer-SoftwareInventoryLogging-Package" 27 | ) 28 | $packagesForServerDataCenter = @( 29 | "Microsoft-NanoServer-ShieldedVM-Package") 30 | $allPackages = $commonPackages + $packagesForServerDataCenter 31 | $cultures = ("cs-cz", "de-de", "en-us", "es-es", "fr-fr", "hu-hu", "it-it", "ja-jp", "ko-kr", "nl-nl", "pl-pl", "pt-br", "pt-pt", "ru-ru", "sv-se", "tr-tr", "zh-cn", "zh-tw") 32 | 33 | 34 | Describe "Find 18 packages" { 35 | It "Find all 18 packages" { 36 | foreach ($package in $allPackages) { 37 | foreach ($culture in $cultures) { 38 | $desiredPackage = Find-NanoServerPackage -Name $package -Culture $culture -Verbose 39 | $desiredPackage.Name | should match $package 40 | $desiredPackage.Culture | should match $culture 41 | } 42 | } 43 | } 44 | } 45 | 46 | Describe "Save 18 packages" { 47 | $savePackagePath = "$env:TMP\NanoServerPackageTest" 48 | md $savePackagePath 49 | 50 | It "Save all 18 packages" { 51 | foreach ($package in $allPackages) { 52 | Write-Host "Saving package $package" 53 | 54 | foreach ($culture in $cultures) { 55 | $desiredPackage = Save-NanoServerPackage -Name $package -Culture $culture -Path $savePackagePath -Verbose 56 | 57 | $desiredPackage.Name | should match $package 58 | $desiredPackage.Culture | should match $culture 59 | 60 | (Get-ChildItem $savePackagePath).Count | should be 2 61 | 62 | Remove-Item "$savePackagePath\*.cab" 63 | } 64 | } 65 | } 66 | } 67 | 68 | Describe "Install 18 packages" { 69 | It "Install all common packages" { 70 | 71 | # Install all of them 72 | foreach ($package in $commonPackages) { 73 | Write-Host "Installing package $package" 74 | Install-NanoServerPackage -Name $package 75 | 76 | $installedPackage = Get-Package -ProviderName $providerName -Name $package -Verbose 77 | $installedPackage.Name -match $package | should be $true 78 | } 79 | } 80 | 81 | It "Install server data center packages" { 82 | foreach ($package in $packagesForServerDataCenter) { 83 | Write-Host "Installing package $package" 84 | Install-NanoServerPackage -Name $package 85 | 86 | $installedPackage = Get-Package -ProviderName $providerName -Name $package -Verbose 87 | $installedPackage.Name -match $package | should be $true 88 | } 89 | } 90 | 91 | It "Install all common packages to offline image" { 92 | 93 | # Install all of them 94 | foreach ($package in $commonPackages) { 95 | Write-Host "Installing package $package to vhd $vhdPath" 96 | Install-NanoServerPackage -Name $package -ToVhd $vhdPath -Verbose 97 | 98 | $installedPackage = Get-Package -ProviderName $providerName -Name $package -FromVhd $vhdPath 99 | $installedPackage.Name -match $package | should be $true 100 | } 101 | } 102 | 103 | It "Install server data center packages to offline image" { 104 | 105 | # Install all of them 106 | foreach ($package in $packagesForServerDataCenter) { 107 | Write-Host "Installing package $package to vhd $vhdPath" 108 | Install-NanoServerPackage -Name $package -ToVhd $vhdPath -Verbose 109 | 110 | $installedPackage = Get-Package -ProviderName $providerName -Name $package -FromVhd $vhdPath 111 | $installedPackage.Name -match $package | should be $true 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /NanoServerPackage/Test/NanoServerPackage.Find.Tests.ps1: -------------------------------------------------------------------------------- 1 | # only have 1 version for now :( 2 | $minVersion = "10.0.14300.1000" 3 | $maxVersion = "10.0.14393.1000" 4 | $requiredVersion = "10.0.14393.0" 5 | $totalPackages="17" 6 | $computePackage = "Microsoft-NanoServer-Compute-Package" 7 | 8 | Describe "Find-NanoServerPackage Stand-Alone" { 9 | 10 | BeforeAll { 11 | Import-packageprovider -force -name NanoServerPackage 12 | Import-module NanoServerPackage -force 13 | 14 | $cultures = ("cs-cz", "de-de", "en-us", "es-es", "fr-fr", "hu-hu", "it-it", "ja-jp", "ko-kr", "nl-nl", "pl-pl", "pt-br", "pt-pt", "ru-ru", "sv-se", "tr-tr", "zh-cn", "zh-tw") 15 | $names = ("containers", "nanoserver-compute", "defender", "dcb") 16 | } 17 | 18 | AfterAll { 19 | "Finished running the Find-NanoServerPackage Stand-Alone tests" 20 | } 21 | 22 | It "Find NanoServerPackage No Params" { 23 | 24 | $command = "Find-NanoServerPackage" 25 | $results = Invoke-Expression $command 26 | #Conformed that we have a total of 17 packages and each has 18 lang 27 | $results.count | should be $totalPackages 28 | } 29 | 30 | It "Find NanoServerPackage Name" { 31 | 32 | $name = $names | Get-Random 33 | $nameWithWildCards = "*$name*" 34 | $results = @() 35 | $results += (Find-NanoServerPackage -Name $nameWithWildCards) 36 | $results.count | should be 1 37 | 38 | foreach($result in $results) 39 | { 40 | $result.name | should match $name 41 | } 42 | } 43 | 44 | It "Find NanoServerPackage Bad Name" { 45 | $results = @() 46 | $results += (Find-NanoServerPackage -Name containers -ErrorAction SilentlyContinue) 47 | $results.count | should be 0 48 | } 49 | 50 | It "Find NanoServerPackage Minimum Version" { 51 | $command = Find-NanoServerPackage -MinimumVersion 10.0.10586.103 -name $computePackage 52 | $command.Name | should match $computePackage 53 | } 54 | 55 | It "Find NanoServerPackage Maximum Version" { 56 | Find-NanoServerPackage -MaximumVersion 10.0.10586.105 -name $computePackage -ErrorAction SilentlyContinue | should throw 57 | } 58 | 59 | It "Find NanoServerPackage Name, Minimum Version" { 60 | 61 | $results = @() 62 | $name = $names | Get-Random 63 | $nameWithWildCards = "*$name*" 64 | $results += (Find-NanoServerPackage -Name $nameWithWildCards -MinimumVersion $minVersion) 65 | $results.count | should be 1 66 | 67 | foreach($result in $results) 68 | { 69 | $result.name | should match $name 70 | #$result.Version | should be less than or equal to $minVersion 71 | } 72 | } 73 | 74 | It "Find NanoServerPackage Name, Maximum Version" { 75 | 76 | $name = $names | Get-Random 77 | $nameWithWildCards = "*$name*" 78 | $results = @() 79 | $results += (Find-NanoServerPackage -Name $nameWithWildCards -MaximumVersion $maxVersion) 80 | $results.count | should be 1 81 | 82 | foreach($result in $results) 83 | { 84 | $result.name | should match $name 85 | #$result.Version | should be less than or equal to $minVersion 86 | } 87 | } 88 | 89 | It "Find NanoServerPackage Name, Minimum-Maximum Version" { 90 | 91 | $name = $names | Get-Random 92 | $nameWithWildCards = "*$name*" 93 | $results = @() 94 | $results += (Find-NanoServerPackage -Name $nameWithWildCards -MinimumVersion $minVersion -MaximumVersion $maxVersion) 95 | $results.count | should be 1 96 | } 97 | 98 | It "Find NanoServerPackage All Versions No Name" { 99 | 100 | $results = @() 101 | $results += (Find-NanoServerPackage -AllVersions) 102 | $results.count | should be $totalPackages 103 | } 104 | 105 | It "Find NanoServerPackage All Versions with Name" { 106 | 107 | $results = @() 108 | $name = $names | Get-Random 109 | $nameWithWildCards = "*$name*" 110 | $results += (Find-NanoServerPackage -Name $nameWithWildCards -AllVersions) 111 | $results.count | should be 1 112 | 113 | foreach($result in $results) 114 | { 115 | $result.name | should match $name 116 | #$result.Version | should be less than or equal to $minVersion 117 | } 118 | } 119 | 120 | It "Find NanoServerPackage Required Version" { 121 | $results = @() 122 | $results += (Find-NanoServerPackage -RequiredVersion $requiredVersion) 123 | $results.count | should be $totalPackages 124 | 125 | foreach($result in $results) 126 | { 127 | $result.Version | should be $requiredVersion 128 | } 129 | } 130 | 131 | It "Find NanoServerPackage Name, Required Version" { 132 | 133 | $name = $names | Get-Random 134 | $nameWithWildCards = "*$name*" 135 | $results = @() 136 | $results += (Find-NanoServerPackage -Name $nameWithWildCards -RequiredVersion $requiredVersion) 137 | $results.count | should be 1 138 | 139 | foreach($result in $results) 140 | { 141 | $result.name | should match $name 142 | $result.Version | should be $requiredVersion 143 | } 144 | } 145 | 146 | It "Find NanoServerPackage Culture" { 147 | 148 | $culture = $cultures | Get-Random 149 | $command = "Find-NanoServerPackage -Culture $culture" 150 | $results = Invoke-Expression $command 151 | $results.count | should be $totalPackages 152 | 153 | foreach($result in $results) 154 | { 155 | $result.Culture | should be $culture 156 | } 157 | } 158 | 159 | It "Find NanoServerPackage Name, Culture" { 160 | 161 | $culture = $cultures | Get-Random 162 | $name = $names | Get-Random 163 | $namewithWildCards = "*$name*" 164 | $results = @() 165 | $results += (Find-NanoServerPackage -Name $namewithWildCards -Culture $culture) 166 | $results.count | should be 1 167 | 168 | foreach($result in $results) 169 | { 170 | $result.Culture | should be $culture 171 | $result.Name | should match $name 172 | } 173 | } 174 | 175 | It "Find NanoServerPackage Name, Minimum Version, Culture" { 176 | 177 | $culture = $cultures | Get-Random 178 | $name = $names | Get-Random 179 | $namewithWildCards = "*$name*" 180 | $results = @() 181 | $results += (Find-NanoServerPackage -Name $namewithWildCards -Culture $culture -MinimumVersion $minVersion) 182 | $results.count | should be 1 183 | 184 | foreach($result in $results) 185 | { 186 | $result.Culture | should be $culture 187 | $result.Name | should match $name 188 | #$result.Version | should be greater than or equal $minVersion 189 | } 190 | } 191 | 192 | It "Find NanoServerPackage Name, Maximum Version, Culture" { 193 | 194 | $culture = $cultures | Get-Random 195 | $name = $names | Get-Random 196 | $namewithWildCards = "*$name*" 197 | $results = @() 198 | $results += (Find-NanoServerPackage -Name $namewithWildCards -Culture $culture -MaximumVersion $maxVersion) 199 | $results.count | should be 1 200 | 201 | foreach($result in $results) 202 | { 203 | $result.Culture | should be $culture 204 | $result.Name | should match $name 205 | #$result.Version | should be less than or equal $maxVersion 206 | } 207 | } 208 | 209 | It "Find NanoServerPackage Name, Minimum Version, Maximum Version, Culture" { 210 | 211 | $culture = $cultures | Get-Random 212 | $name = $names | Get-Random 213 | $results = @() 214 | $namewithWildCards = "*$name*" 215 | $results += (Find-NanoServerPackage -Name $namewithWildCards -Culture $culture -MinimumVersion $minVersion -MaximumVersion $maxVersion) 216 | $results.count | should be 1 217 | 218 | foreach($result in $results) 219 | { 220 | $result.Culture | should be $culture 221 | $result.Name | should match $name 222 | #$result.Version | should be greater than or equal $minVersion 223 | #$result.Version | should be less than or equal $maxVersion 224 | } 225 | } 226 | 227 | It "Find NanoServerPackage Name, AllVersions, Culture" { 228 | 229 | $culture = $cultures | Get-Random 230 | $name = $names | Get-Random 231 | $namewithWildCards = "*$name*" 232 | $results = @() 233 | $results += (Find-NanoServerPackage -Name $namewithWildCards -Culture $culture -AllVersions) 234 | $results.count | should be 1 235 | 236 | foreach($result in $results) 237 | { 238 | $result.Culture | should be $culture 239 | $result.Name | should match $name 240 | } 241 | } 242 | 243 | It "Find NanoServerPackage Name, RequiredVersion, Culture" { 244 | 245 | $culture = $cultures | Get-Random 246 | $name = $names | Get-Random 247 | $namewithWildCards = "*$name*" 248 | $results = @() 249 | $results += (Find-NanoServerPackage -Name $namewithWildCards -Culture $culture -RequiredVersion $requiredVersion) 250 | $results.count | should be 1 251 | 252 | foreach($result in $results) 253 | { 254 | $result.Culture | should be $culture 255 | $result.name | should match $name 256 | $result.Version | should be $requiredVersion 257 | } 258 | } 259 | 260 | It "Find NanoServerPackage with Dependencies" { 261 | $scvmmCompute = Find-NanoServerPackage *scvmm-compute* -RequiredVersion $requiredVersion 262 | $scvmmCompute.Dependencies[0] | should match "nanoserverpackage:Microsoft-NanoServer-SCVMM-Package/$requiredVersion" 263 | $scvmmCompute.Dependencies[1] | should match "nanoserverpackage:Microsoft-NanoServer-Compute-Package/$requiredVersion" 264 | } 265 | 266 | } 267 | 268 | Describe "NanoServerPackage OneGet" { 269 | 270 | BeforeAll { 271 | Import-packageprovider -force -name NanoServerPackage 272 | Import-module NanoServerPackage -force 273 | 274 | $cultures = ("cs-cz", "de-de", "en-us", "es-es", "fr-fr", "hu-hu", "it-it", "ja-jp", "ko-kr", "nl-nl", "pl-pl", "pt-br", "pt-pt", "ru-ru", "sv-se", "tr-tr", "zh-cn", "zh-tw") 275 | $names = ("containers", "nanoserver-compute", "defender", "dcb") 276 | } 277 | 278 | AfterAll { 279 | "Finished running the Find-NanoServerPackage Stand-Alone tests" 280 | } 281 | 282 | It "Find NanoServerPackage No Params" { 283 | 284 | $command = "Find-Package -ProviderName NanoServerPackage" 285 | $results = Invoke-Expression $command 286 | $results.count | should be $totalPackages 287 | } 288 | 289 | It "Find NanoServerPackage Name" { 290 | 291 | $name = $names | Get-Random 292 | $nameWithWildCards = "*$name*" 293 | $results = @() 294 | $results += (Find-Package -ProviderName NanoServerPackage -Name $nameWithWildCards) 295 | $results.count | should be 1 296 | 297 | foreach($result in $results) 298 | { 299 | $result.name | should match $name 300 | } 301 | } 302 | <# 303 | It "Find NanoServerPackage Bad Name" { 304 | $command = "Find-Package -ProviderName NanoServerPackage -Name containers" 305 | {Invoke-Expression $command} | should throw 306 | } 307 | #> 308 | It "Find NanoServerPackage Minimum Version" { 309 | $command = Find-NanoServerPackage -MinimumVersion 10.0.10586.103 -name $computePackage 310 | $command.Name | should match $computePackage 311 | } 312 | 313 | It "Find NanoServerPackage Maximum Version" { 314 | Find-NanoServerPackage -MaximumVersion 10.0.10586.105 -name $computePackage -ErrorAction SilentlyContinue | should throw 315 | } 316 | 317 | It "Find NanoServerPackage Name, Minimum Version" { 318 | 319 | $results = @() 320 | $name = $names | Get-Random 321 | $nameWithWildCards = "*$name*" 322 | $results += (Find-Package -ProviderName NanoServerPackage -Name $nameWithWildCards -MinimumVersion $minVersion) 323 | $results.count | should be 1 324 | 325 | foreach($result in $results) 326 | { 327 | $result.name | should match $name 328 | #$result.Version | should be less than or equal to $minVersion 329 | } 330 | } 331 | 332 | It "Find NanoServerPackage Name, Maximum Version" { 333 | 334 | $name = $names | Get-Random 335 | $nameWithWildCards = "*$name*" 336 | $results = @() 337 | $results += (Find-Package -ProviderName NanoServerPackage -Name $nameWithWildCards -MaximumVersion $maxVersion) 338 | $results.count | should be 1 339 | 340 | foreach($result in $results) 341 | { 342 | $result.name | should match $name 343 | #$result.Version | should be less than or equal to $minVersion 344 | } 345 | } 346 | 347 | It "Find NanoServerPackage Name, Minimum-Maximum Version" { 348 | 349 | $name = $names | Get-Random 350 | $nameWithWildCards = "*$name*" 351 | $results = @() 352 | $results += (Find-Package -ProviderName NanoServerPackage -Name $nameWithWildCards -MinimumVersion $minVersion -MaximumVersion $maxVersion) 353 | $results.count | should be 1 354 | } 355 | 356 | It "Find NanoServerPackage All Versions No Name" { 357 | 358 | $results = @() 359 | $results += (Find-Package -ProviderName NanoServerPackage -AllVersions) 360 | $results.count | should be $totalPackages 361 | } 362 | 363 | It "Find NanoServerPackage All Versions with Name" { 364 | 365 | $results = @() 366 | $name = "container" 367 | $nameWithWildCards = "*$name*" 368 | $results += (Find-Package -ProviderName NanoServerPackage -Name $nameWithWildCards -AllVersions) 369 | $results.count | should be 1 370 | 371 | foreach($result in $results) 372 | { 373 | $result.name | should match $name 374 | #$result.Version | should be less than or equal to $minVersion 375 | } 376 | } 377 | 378 | It "Find NanoServerPackage Required Version" { 379 | $results = @() 380 | $results += (Find-Package -ProviderName NanoServerPackage -RequiredVersion $requiredVersion) 381 | $results.count | should be $totalPackages 382 | 383 | foreach($result in $results) 384 | { 385 | $result.Version | should be $requiredVersion 386 | } 387 | } 388 | 389 | It "Find NanoServerPackage Name, Required Version" { 390 | 391 | $name = $names | Get-Random 392 | $nameWithWildCards = "*$name*" 393 | $results = @() 394 | $results += (Find-Package -ProviderName NanoServerPackage -Name $nameWithWildCards -RequiredVersion $requiredVersion) 395 | $results.count | should be 1 396 | 397 | foreach($result in $results) 398 | { 399 | $result.name | should match $name 400 | $result.Version | should be $requiredVersion 401 | } 402 | } 403 | 404 | It "Find NanoServerPackage Culture" { 405 | 406 | $culture = $cultures | Get-Random 407 | $results = @() 408 | $results += (Find-Package -ProviderName NanoServerPackage -Culture $culture) 409 | $results.count | should be $totalPackages 410 | 411 | foreach($result in $results) 412 | { 413 | $result.Culture | should be $culture 414 | } 415 | } 416 | 417 | It "Find NanoServerPackage Name, Culture" { 418 | 419 | $culture = $cultures | Get-Random 420 | $name = $names | Get-Random 421 | $namewithWildCards = "*$name*" 422 | $results = @() 423 | $results += (Find-Package -ProviderName NanoServerPackage -Name $namewithWildCards -Culture $culture) 424 | $results.count | should be 1 425 | 426 | foreach($result in $results) 427 | { 428 | $result.Culture | should be $culture 429 | $result.Name | should match $name 430 | } 431 | } 432 | 433 | It "Find NanoServerPackage Name, Minimum Version, Culture" { 434 | 435 | $culture = $cultures | Get-Random 436 | $name = $names | Get-Random 437 | $namewithWildCards = "*$name*" 438 | $results = @() 439 | $results += (Find-Package -ProviderName NanoServerPackage -Name $namewithWildCards -Culture $culture -MinimumVersion $minVersion) 440 | $results.count | should be 1 441 | 442 | foreach($result in $results) 443 | { 444 | $result.Culture | should be $culture 445 | $result.Name | should match $name 446 | #$result.Version | should be greater than or equal $minVersion 447 | } 448 | } 449 | 450 | It "Find NanoServerPackage Name, Maximum Version, Culture" { 451 | 452 | $culture = $cultures | Get-Random 453 | $results = @() 454 | $name = $names | Get-Random 455 | $namewithWildCards = "*$name*" 456 | $results += (Find-Package -ProviderName NanoServerPackage -Name $namewithWildCards -Culture $culture -MaximumVersion $maxVersion) 457 | $results.count | should be 1 458 | 459 | foreach($result in $results) 460 | { 461 | $result.Culture | should be $culture 462 | $result.Name | should match $name 463 | #$result.Version | should be less than or equal $maxVersion 464 | } 465 | } 466 | 467 | It "Find NanoServerPackage Name, Minimum Version, Maximum Version, Culture" { 468 | 469 | $culture = $cultures | Get-Random 470 | $results = @() 471 | $name = $names | Get-Random 472 | $namewithWildCards = "*$name*" 473 | $results += (Find-Package -ProviderName NanoServerPackage -Name $namewithWildCards -Culture $culture -MinimumVersion $minVersion -MaximumVersion $maxVersion) 474 | $results.count | should be 1 475 | 476 | foreach($result in $results) 477 | { 478 | $result.Culture | should be $culture 479 | $result.Name | should match $name 480 | #$result.Version | should be greater than or equal $minVersion 481 | #$result.Version | should be less than or equal $maxVersion 482 | } 483 | } 484 | 485 | It "Find NanoServerPackage Name, AllVersions, Culture" { 486 | 487 | $culture = $cultures | Get-Random 488 | $name = "containers" 489 | $namewithWildCards = "*$name*" 490 | $results = @() 491 | $results += (Find-Package -ProviderName NanoServerPackage -Name $namewithWildCards -Culture $culture -AllVersions) 492 | $results.count | should be 1 493 | 494 | foreach($result in $results) 495 | { 496 | $result.Culture | should be $culture 497 | $result.Name | should match $name 498 | } 499 | } 500 | 501 | It "Find NanoServerPackage Name, RequiredVersion, Culture" { 502 | 503 | $culture = $cultures | Get-Random 504 | $name = $names | Get-Random 505 | $namewithWildCards = "*$name*" 506 | $results = @() 507 | $results += (Find-Package -ProviderName NanoServerPackage -Name $namewithWildCards -Culture $culture -RequiredVersion $requiredVersion) 508 | $results.count | should be 1 509 | 510 | foreach($result in $results) 511 | { 512 | $result.Culture | should be $culture 513 | $result.name | should match $name 514 | $result.Version | should be $requiredVersion 515 | } 516 | } 517 | 518 | It "Find NanoServerPackage with Dependencies" {$scvmmComputePackages = (Find-Package *scvmm-compute* -RequiredVersion $requiredVersion -ProviderName NanoServerPackage -IncludeDependencies) 519 | $scvmmComputePackages.Count | should be 3 520 | 521 | $scvmmComputePackages.Name -contains "microsoft-nanoserver-compute-package" | should be $true 522 | $scvmmComputePackages.Name -contains "microsoft-nanoserver-scvmm-package" | should be $true 523 | $scvmmComputePackages.Name -contains "microsoft-nanoserver-scvmm-compute-package" | should be $true 524 | } 525 | } 526 | 527 | -------------------------------------------------------------------------------- /NanoServerPackage/Test/NanoServerPackage.Install.Tests.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # 3 | # you need to modify 4 | # 1. $vhdPath to match your Nano Vm 5 | # 2. $requiredVersion needs to be updated if there are new packages published 6 | # 7 | # 8 | 9 | $vhdPath = "C:\test\rtmRefreshStdEdition.vhd" 10 | $culture = (Get-Culture).Name 11 | $dcbPackage = "Microsoft-NanoServer-DCB-Package" 12 | $computePackage = "Microsoft-NanoServer-Compute-Package" 13 | $containersPackage = "Microsoft-NanoServer-Containers-Package" 14 | $providerName = "NanoServerPackage" 15 | $requiredVersion = "10.0.14393.0" 16 | 17 | Describe "Install-NanoServerPackage Stand-Alone" { 18 | It "ERROR: Install with no name" { 19 | { Install-NanoServerPackage -Name '' } | should throw 20 | } 21 | 22 | It "ERROR: Install with wrong culture" { 23 | { Install-NanoServerPackage -Name $dcbPackage -Culture wrong } | should throw 24 | } 25 | 26 | It "ERROR: Install unknown packages" { 27 | { Install-NanoServerPackage -Name 'wrong package name' } | should throw 28 | } 29 | 30 | It "Install DCB package" { 31 | $package = Install-NanoServerPackage -Name $dcbPackage -Force 32 | 33 | $package.Name | should match $dcbPackage 34 | $package.Culture | should match $culture 35 | 36 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture 37 | $getPackage.Name | should match $package.Name 38 | $getPackage.Culture | should match $package.Culture 39 | } 40 | 41 | It "Install DCB package to vhd" { 42 | $package = Install-NanoServerPackage -Name $dcbPackage -ToVhd $vhdPath -Force 43 | 44 | $package.Name | should match $dcbPackage 45 | $package.Culture | should match $culture 46 | 47 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -FromVhd $vhdPath 48 | $getPackage.Name | should match $package.Name 49 | $getPackage.Culture | should match $package.Culture 50 | } 51 | 52 | It "Install compute package with wrong culture" { 53 | $locale="it-it" 54 | $package = Install-NanoServerPackage -Name $dcbPackage -Culture $locale -Force 55 | $package.Name | should match $dcbPackage 56 | $package.Culture | should match $locale 57 | } 58 | 59 | It "Install compute package with correct culture" { 60 | $package = Install-NanoServerPackage -Name $computePackage -Culture $culture -Force 61 | 62 | $package.Name | should match $computePackage 63 | $package.Culture | should match $culture 64 | 65 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture 66 | $getPackage.Name | should match $package.Name 67 | $getPackage.Culture | should match $package.Culture 68 | } 69 | 70 | It "Install compute package with correct culture to vhd" { 71 | $package = Install-NanoServerPackage -Name $computePackage -Culture $culture -ToVhd $vhdPath -Force 72 | 73 | $package.Name | should match $computePackage 74 | $package.Culture | should match $culture 75 | 76 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -FromVhd $vhdPath 77 | $getPackage.Name | should match $package.Name 78 | $getPackage.Culture | should match $package.Culture 79 | } 80 | 81 | It "Install compute package by piping from find" { 82 | $package = (Find-NanoServerPackage -Name *nanoserver-compute* | Install-NanoServerPackage -Force) 83 | 84 | $package.Name | should match $computePackage 85 | $package.Culture | should match $culture 86 | 87 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture 88 | $getPackage.Name | should match $package.Name 89 | $getPackage.Culture | should match $package.Culture 90 | } 91 | 92 | It "Install containers package with required version" { 93 | $package = Install-NanoServerPackage -Name $containersPackage -Culture $culture -RequiredVersion $requiredVersion -Force 94 | 95 | $package.Name | should match $containersPackage 96 | $package.Culture | should match $culture 97 | $package.Version.ToString() | should match $requiredVersion 98 | 99 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -RequiredVersion $requiredVersion 100 | $getPackage.Name | should match $package.Name 101 | $getPackage.Culture | should match $package.Culture 102 | $getPackage.Version.ToString() | should match $requiredVersion 103 | } 104 | 105 | It "Install containers package with required version to vhd" { 106 | $package = Install-NanoServerPackage -Name $containersPackage -Culture $culture -RequiredVersion $requiredVersion -ToVhd $vhdPath -Force 107 | 108 | $package.Name | should match $containersPackage 109 | $package.Culture | should match $culture 110 | $package.Version | should match $requiredVersion 111 | 112 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -RequiredVersion $requiredVersion -FromVhd $vhdPath 113 | $getPackage.Name | should match $package.Name 114 | $getPackage.Culture | should match $package.Culture 115 | } 116 | 117 | It "Install multiple packages to VHD" { 118 | $packages = Install-NanoServerPackage -Name $containersPackage,$computePackage -Culture $culture -Force 119 | 120 | $packages.Count | should be 2 121 | $packages.Name -contains $containersPackage | should be $true 122 | $packages.Name -contains $computePackage | should be $true 123 | } 124 | 125 | It "Install multiple packages to VHD" { 126 | $packages = Install-NanoServerPackage -Name $containersPackage,$computePackage -Culture $culture -Force -ToVhd $vhdPath 127 | 128 | $packages.Count | should be 2 129 | $packages.Name -contains $containersPackage | should be $true 130 | $packages.Name -contains $computePackage | should be $true 131 | } 132 | 133 | } 134 | 135 | Describe "Install-NanoServerPackage With OneGet" { 136 | It "ERROR: Install with wildcard name" { 137 | $Error.Clear() 138 | Install-Package -Name '*' -ProviderName $providerName -Force -ErrorAction SilentlyContinue 139 | $Error[0].FullyQualifiedErrorId | should match 'WildCardCharsAreNotSupported,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage' 140 | } 141 | 142 | It "ERROR: Install with wrong culture" { 143 | $Error.Clear() 144 | Install-Package -Name $dcbPackage -Culture wrong -ProviderName $providerName -Force -ErrorAction SilentlyContinue 145 | $Error[0].FullyQualifiedErrorId | should match 'NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage' 146 | } 147 | 148 | It "ERROR: Install unknown packages" { 149 | $Error.Clear() 150 | Install-Package -ProviderName $providerName -Name 'wrong package name' -Force -ErrorAction SilentlyContinue 151 | $Error[0].FullyQualifiedErrorId | should match 'NoMatchFoundForCriteria,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage' 152 | } 153 | 154 | It "Install DCB package" { 155 | $package = Install-Package -ProviderName $providerName -Name $dcbPackage -Force 156 | 157 | $package.Name | should match $dcbPackage 158 | $package.Culture | should match $culture 159 | 160 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture 161 | $getPackage.Name | should match $package.Name 162 | $getPackage.Culture | should match $package.Culture 163 | } 164 | 165 | It "Install DCB package to vhd" { 166 | $package = Install-Package -ProviderName $providerName -Name $dcbPackage -ToVhd $vhdPath -Force 167 | 168 | $package.Name | should match $dcbPackage 169 | $package.Culture | should match $culture 170 | 171 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -FromVhd $vhdPath 172 | $getPackage.Name | should match $package.Name 173 | $getPackage.Culture | should match $package.Culture 174 | } 175 | 176 | It "Install compute package with wrong culture" { 177 | $locale="it-it" 178 | $package = Install-Package -ProviderName $providerName -Name $dcbPackage -Culture $locale -Force -ErrorAction SilentlyContinue 179 | $package.Culture | should match $locale 180 | } 181 | 182 | It "Install compute package with correct culture" { 183 | $package = Install-Package -ProviderName $providerName -Name $computePackage -Culture $culture -Force 184 | 185 | $package.Name | should match $computePackage 186 | $package.Culture | should match $culture 187 | 188 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture 189 | $getPackage.Name | should match $package.Name 190 | $getPackage.Culture | should match $package.Culture 191 | } 192 | 193 | It "Install compute package with correct culture to vhd" { 194 | $package = Install-Package -ProviderName $providerName -Name $computePackage -Culture $culture -ToVhd $vhdPath -Force 195 | 196 | $package.Name | should match $computePackage 197 | $package.Culture | should match $culture 198 | 199 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -FromVhd $vhdPath 200 | $getPackage.Name | should match $package.Name 201 | $getPackage.Culture | should match $package.Culture 202 | } 203 | 204 | It "Install compute package by piping from find" { 205 | $package = (Find-Package -ProviderName $providerName -Name $computePackage | Install-Package -Force) 206 | 207 | $package.Name | should match $computePackage 208 | $package.Culture | should match $culture 209 | 210 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture 211 | $getPackage.Name | should match $package.Name 212 | $getPackage.Culture | should match $package.Culture 213 | } 214 | 215 | It "Install containers package with required version" { 216 | $package = Install-Package -ProviderName $providerName -Name $containersPackage -Culture $culture -RequiredVersion $requiredVersion -Force 217 | 218 | $package.Name | should match $containersPackage 219 | $package.Culture | should match $culture 220 | $package.Version.ToString() | should match $requiredVersion 221 | 222 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -RequiredVersion $requiredVersion 223 | $getPackage.Name | should match $package.Name 224 | $getPackage.Culture | should match $package.Culture 225 | $getPackage.Version.ToString() | should match $requiredVersion 226 | } 227 | 228 | It "Install containers package with required version to vhd" { 229 | $package = Install-Package $containersPackage -ProviderName $providerName -Culture $culture -RequiredVersion $requiredVersion -ToVhd $vhdPath -Force 230 | 231 | $package.Name | should match $containersPackage 232 | $package.Culture | should match $culture 233 | $package.Version | should match $requiredVersion 234 | 235 | $getPackage = Get-Package -ProviderName $providerName -Name $package.Name -Culture $package.Culture -RequiredVersion $requiredVersion -FromVhd $vhdPath 236 | $getPackage.Name | should match $package.Name 237 | $getPackage.Culture | should match $package.Culture 238 | } 239 | 240 | It "Install multiple packages to VHD" { 241 | $packages = Install-Package -ProviderName $providerName -Name $containersPackage,$computePackage -Culture $culture -Force 242 | 243 | $packages.Count | should be 2 244 | $packages.Name -contains $containersPackage | should be $true 245 | $packages.Name -contains $computePackage | should be $true 246 | } 247 | 248 | It "Install multiple packages to VHD" { 249 | $packages = Install-Package -ProviderName $providerName -Name $containersPackage,$computePackage -Culture $culture -Force -ToVhd $vhdPath 250 | 251 | $packages.Count | should be 2 252 | $packages.Name -contains $containersPackage | should be $true 253 | $packages.Name -contains $computePackage | should be $true 254 | } 255 | } -------------------------------------------------------------------------------- /NanoServerPackage/Test/NanoServerPackage.Save.Tests.ps1: -------------------------------------------------------------------------------- 1 | #$here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | #$sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Save.Tests.ps1", ".psm1") 3 | #. "$here\..\$sut" 4 | $minVersion = "10.0.14300.1000" 5 | $maxVersion = "10.0.14393.1000" 6 | $requiredVersion = "10.0.14393.0" 7 | 8 | Describe "Save-WindowsPackage Stand-Alone" { 9 | 10 | BeforeAll { 11 | 12 | $pathToSaveWithWildCards = "$env:LOCALAPPDATA\N*Serv*age\Save\" 13 | $pathToSave = "$env:LOCALAPPDATA\NanoServerPackage\Save\" 14 | $badPath = "C:\DoesNotExist" 15 | $cultures = ("cs-cz", "de-de", "en-us", "es-es", "fr-fr", "hu-hu", "it-it", "ja-jp", "ko-kr", "nl-nl", "pl-pl", "pt-br", "pt-pt", "ru-ru", "sv-se", "tr-tr", "zh-cn", "zh-tw") 16 | $names = ("microsoft-NanoServEr-containers-package", "microsoft-NanoServEr-compute-package", "microsoft-NanoServEr-dcb-package") 17 | 18 | Import-packageprovider -force -name NanoServerPackage 19 | Import-module NanoServerPackage -force 20 | 21 | if(-not (Test-Path $pathToSave)) 22 | { 23 | mkdir $pathToSave 24 | } 25 | 26 | # Remove all cab files under the save folder 27 | Remove-Item $pathToSave\*.cab 28 | } 29 | 30 | AfterAll { 31 | if(Test-Path $pathToSave) 32 | { 33 | rmdir $pathToSave -Force -Recurse 34 | } 35 | 36 | "Finished running the Find-NanoServerPackage Stand-Alone tests" 37 | } 38 | 39 | It "Save-NanoServerPackage Name, Path" { 40 | $name = $names | Get-Random 41 | $version = $requiredVersion 42 | 43 | $results = @() 44 | $results += (Save-NanoServerPackage -Name $name -Path $pathToSaveWithWildCards) 45 | 46 | $results.count | should be 1 47 | $results[0].name | should be $name 48 | $results[0].version | should be $version 49 | $results[0].culture | should be "en-us" 50 | 51 | $outputs = Get-ChildItem $pathToSave -Name *.cab 52 | 53 | foreach($output in $outputs) 54 | { 55 | $output | should match $name 56 | $output | should match $version 57 | } 58 | 59 | Remove-Item $pathToSave\*.cab 60 | } 61 | 62 | It "Save-NanoServerPackage Name, Path, Force" { 63 | $name = $names | Get-Random 64 | 65 | $SavePath = "$TestDrive\SaveFolder" 66 | (Test-Path $SavePath) | should be $false 67 | 68 | $results = @() 69 | $results += (Save-NanoServerPackage -Name $name -Path $SavePath -Force) 70 | 71 | $results.count | should be 1 72 | $results[0].name | should be $name 73 | 74 | $outputs = Get-ChildItem $SavePath -Name *.cab 75 | 76 | foreach($output in $outputs) 77 | { 78 | $output | should match $name 79 | } 80 | 81 | Remove-Item $SavePath -Recurse -Force 82 | } 83 | 84 | It "Save-NanoServerPackage Name, Path, Culture" { 85 | $name = $names | Get-Random 86 | $culture = $cultures | Get-Random 87 | $version = $requiredVersion 88 | 89 | $results = @() 90 | $results += (Save-NanoServerPackage -Name $name -Path $pathToSaveWithWildCards -Culture $culture) 91 | 92 | $results.count | should be 1 93 | $results[0].name | should be $name 94 | $results[0].version | should be $version 95 | $results[0].culture | should be $culture 96 | 97 | $outputs = Get-ChildItem $pathToSave -Name *.cab 98 | 99 | foreach($output in $outputs) 100 | { 101 | $output | should match $name 102 | $output | should match $version 103 | } 104 | 105 | Remove-Item $pathToSave\*.cab 106 | } 107 | 108 | It "Save-NanoServerPackage Name, Path, Culture, Minimum Version" { 109 | $name = $names | Get-Random 110 | $culture = $cultures | Get-Random 111 | $version = $requiredVersion 112 | 113 | $results = @() 114 | $results += (Save-NanoServerPackage -Name $name -Path $pathToSaveWithWildCards -Culture $culture -MinimumVersion $minVersion) 115 | 116 | $results.count | should be 1 117 | $results[0].name | should be $name 118 | $results[0].version | should be $version 119 | $results[0].culture | should be $culture 120 | 121 | $outputs = Get-ChildItem $pathToSave -Name *.cab 122 | 123 | foreach($output in $outputs) 124 | { 125 | $output | should match $name 126 | $output | should match $version 127 | } 128 | 129 | Remove-Item $pathToSave\*.cab 130 | } 131 | 132 | It "Save-NanoServerPackage Name, Path, Culture, Maximum Version" { 133 | $name = $names | Get-Random 134 | $culture = $cultures | Get-Random 135 | $version = $requiredVersion 136 | 137 | $results = @() 138 | $results += (Save-NanoServerPackage -Name $name -Path $pathToSaveWithWildCards -Culture $culture -MaximumVersion $maxVersion) 139 | 140 | $results.count | should be 1 141 | $results[0].name | should be $name 142 | $results[0].version | should be $version 143 | $results[0].culture | should be $culture 144 | 145 | $outputs = Get-ChildItem $pathToSave -Name *.cab 146 | 147 | foreach($output in $outputs) 148 | { 149 | $output | should match $name 150 | $output | should match $version 151 | } 152 | 153 | Remove-Item $pathToSave\*.cab 154 | } 155 | 156 | It "Save-NanoServerPackage Name, Path, Culture, Required Version" { 157 | $name = $names | Get-Random 158 | $culture = $cultures | Get-Random 159 | $version = $requiredVersion 160 | 161 | $results = @() 162 | $results += (Save-NanoServerPackage -Name $name -Path $pathToSaveWithWildCards -Culture $culture -RequiredVersion $requiredVersion) 163 | 164 | $results.count | should be 1 165 | $results[0].name | should be $name 166 | $results[0].version | should be $version 167 | $results[0].culture | should be $culture 168 | 169 | $outputs = Get-ChildItem $pathToSave -Name *.cab 170 | 171 | foreach($output in $outputs) 172 | { 173 | $output | should match $name 174 | $output | should match $version 175 | } 176 | 177 | Remove-Item $pathToSave\*.cab 178 | } 179 | 180 | It "Save-NanoServerPackage With Dependencies" { 181 | try { 182 | $name = "Microsoft-NanoServer-SCVMM-Compute-Package" 183 | $culture = $cultures | Get-Random 184 | $version = $requiredVersion 185 | 186 | $results = @() 187 | $results += (Save-NanoServerPackage -Name $name -Path $pathToSaveWithWildCards -Culture $culture -RequiredVersion $requiredVersion -Force) 188 | 189 | $results.count | should be 3 190 | $results.name -contains $name | should be $true 191 | $results.name -contains "Microsoft-NanoServer-Compute-Package" | should be $true 192 | $results.name -contains "Microsoft-NanoServer-SCVMM-Package" | should be $true 193 | $results[0].culture | should match $culture 194 | $results[1].culture | should match $culture 195 | $results[2].culture | should match $culture 196 | } 197 | finally { 198 | Remove-Item $pathToSave\*.cab 199 | } 200 | } 201 | 202 | } 203 | 204 | Describe "Save-NanoServerPackage One-Get" { 205 | 206 | BeforeAll { 207 | 208 | $pathToSave = "$env:LOCALAPPDATA\NanoServerPackageProvider\Save\" 209 | $badPath = "C:\DoesNotExist" 210 | $cultures = ("cs-cz", "de-de", "en-us", "es-es", "fr-fr", "hu-hu", "it-it", "ja-jp", "ko-kr", "nl-nl", "pl-pl", "pt-br", "pt-pt", "ru-ru", "sv-se", "tr-tr", "zh-cn", "zh-tw") 211 | $names = ("microsoft-NanoServEr-containers-package", "microsoft-NanoServEr-compute-package", "microsoft-NanoServEr-dcb-package") 212 | 213 | Import-packageprovider -force -name NanoServerPackage 214 | Import-module NanoServerPackage -force 215 | 216 | if(-not (Test-Path $pathToSave)) 217 | { 218 | mkdir $pathToSave 219 | } 220 | 221 | # Remove all cab files under the save folder 222 | Remove-Item $pathToSave\*.cab 223 | } 224 | 225 | AfterAll { 226 | if(Test-Path $pathToSave) 227 | { 228 | rmdir $pathToSave -Force 229 | } 230 | 231 | "Finished running the Find-NanoServerPackage Stand-Alone tests" 232 | } 233 | 234 | It "Save-NanoServerPackage Name, Path" { 235 | $name = $names | Get-Random 236 | $version = $requiredVersion 237 | 238 | $results = @() 239 | $results += (Save-Package -ProviderName NanoServerPackage -Name $name -Path $pathToSave) 240 | 241 | $results.count | should be 1 242 | $results[0].name | should be $name 243 | $results[0].version | should be $version 244 | $results[0].culture | should be "en-us" 245 | 246 | $outputs = Get-ChildItem $pathToSave -Name *.cab 247 | 248 | foreach($output in $outputs) 249 | { 250 | $output | should match $name 251 | $output | should match $version 252 | } 253 | 254 | Remove-Item $pathToSave\*.cab 255 | } 256 | 257 | It "Save-NanoServerPackage Name, Path, Culture" { 258 | $name = $names | Get-Random 259 | $culture = $cultures | Get-Random 260 | $version = $requiredVersion 261 | 262 | $results = @() 263 | $results += (Save-Package -ProviderName NanoServerPackage $name -Path $pathToSave -Culture $culture) 264 | 265 | $results.count | should be 1 266 | $results[0].name | should be $name 267 | $results[0].version | should be $version 268 | $results[0].culture | should be $culture 269 | 270 | $outputs = Get-ChildItem $pathToSave -Name *.cab 271 | 272 | foreach($output in $outputs) 273 | { 274 | $output | should match $name 275 | $output | should match $version 276 | } 277 | 278 | Remove-Item $pathToSave\*.cab 279 | } 280 | 281 | It "Save-NanoServerPackage Name, Path, Culture, Minimum Version" { 282 | $name = $names | Get-Random 283 | $culture = $cultures | Get-Random 284 | $version = $requiredVersion 285 | 286 | $results = @() 287 | $results += (Save-Package -ProviderName NanoServerPackage -Name $name -Path $pathToSave -Culture $culture -MinimumVersion $minVersion) 288 | 289 | $results.count | should be 1 290 | $results[0].name | should be $name 291 | $results[0].version | should be $version 292 | $results[0].culture | should be $culture 293 | 294 | $outputs = Get-ChildItem $pathToSave -Name *.cab 295 | 296 | foreach($output in $outputs) 297 | { 298 | $output | should match $name 299 | $output | should match $version 300 | } 301 | 302 | Remove-Item $pathToSave\*.cab 303 | } 304 | 305 | It "Save-NanoServerPackage Name, Path, Culture, Maximum Version" { 306 | $name = $names | Get-Random 307 | $culture = $cultures | Get-Random 308 | $version = $requiredVersion 309 | 310 | $results = @() 311 | $results += (Save-Package -ProviderName NanoServerPackage -Name $name -Path $pathToSave -Culture $culture -MaximumVersion $maxVersion) 312 | 313 | $results.count | should be 1 314 | $results[0].name | should be $name 315 | $results[0].version | should be $version 316 | $results[0].culture | should be $culture 317 | 318 | $outputs = Get-ChildItem $pathToSave -Name *.cab 319 | 320 | foreach($output in $outputs) 321 | { 322 | $output | should match $name 323 | $output | should match $version 324 | } 325 | 326 | Remove-Item $pathToSave\*.cab 327 | } 328 | 329 | It "Save-NanoServerPackage Name, Path, Culture, Required Version" { 330 | $name = $names | Get-Random 331 | $culture = $cultures | Get-Random 332 | $version = $requiredVersion 333 | 334 | $results = @() 335 | $results += (Save-Package -ProviderName NanoServerPackage -Name $name -Path $pathToSave -Culture $culture -RequiredVersion $requiredVersion) 336 | 337 | $results.count | should be 1 338 | $results[0].name | should be $name 339 | $results[0].version | should be $version 340 | $results[0].culture | should be $culture 341 | 342 | $outputs = Get-ChildItem $pathToSave -Name *.cab 343 | 344 | foreach($output in $outputs) 345 | { 346 | $output | should match $name 347 | $output | should match $version 348 | } 349 | 350 | Remove-Item $pathToSave\*.cab 351 | } 352 | 353 | It "Save-NanoServerPackage With Dependencies" { 354 | try { 355 | $name = "Microsoft-NanoServer-SCVMM-Compute-Package" 356 | $culture = $cultures | Get-Random 357 | $version = $requiredVersion 358 | 359 | $results = @() 360 | $results += (Save-Package -ProviderName NanoServerPackage -Name $name -Path $pathToSave -Culture $culture -RequiredVersion $requiredVersion -Force) 361 | 362 | $results.count | should be 3 363 | $results.name -contains $name | should be $true 364 | $results.name -contains "Microsoft-NanoServer-Compute-Package" | should be $true 365 | $results.name -contains "Microsoft-NanoServer-SCVMM-Package" | should be $true 366 | $results[0].culture | should match $culture 367 | $results[1].culture | should match $culture 368 | $results[2].culture | should match $culture 369 | } 370 | finally { 371 | Remove-Item $pathToSave\*.cab 372 | } 373 | } 374 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # NanoServerPackage Provider 3 | A PackageManagement (aka OneGet) provider to find and install Optional Windows Packages (Windows feature and role) for Nano Server. For general information about these packages, please refer to the guide on Deploy Nano Server. 4 | 5 | ##### Note 6 | The public version 0.1.1.0 of NanoServerPackage Provider from the PowerShellGallery.com only supports Nano Server with Technical Preview 5 (TP5) version, i.e. 10.0.14300.1000, that is public in April 2016, and vice versa. It DOES NOT support Nano Server WS2016 GA version 10.0.14393.0. Please update your Nano Server to WS2016 GA release and install the latest version of the provider. 7 | 8 | ## Installing the provider 9 | 10 | You can find and install Nano Server packages from the online package repository by using the NanoServerPackage provider of the PackageManagement PowerShell module. To install this provider, use these cmdlets: 11 | 12 | ```powershell 13 | Install-PackageProvider NanoServerPackage 14 | Import-PackageProvider NanoServerPackage 15 | ``` 16 | 17 | >`NOTE` If you experience errors when running Install-PackageProvider, check that you have [installed the latest cumulative update](https://technet.microsoft.com/en-us/windows-server-docs/get-started/update-nano-server) ([KB3206632](https://support.microsoft.com/kb/3206632) or later), or use the following steps to install the latest version of the provider: 18 | ``` 19 | Save-Module -Path "$env:programfiles\WindowsPowerShell\Modules\" -Name NanoServerPackage -minimumVersion 1.0.1.0 20 | Import-PackageProvider NanoServerPackage 21 | ``` 22 | To install the TP5 version of the provider 0.1.1.0 that works for TP5 OS version 10.0.14300.1000, use the following steps. 23 | ``` 24 | # Your Nano Server OS version is 10.0.14300.1000 25 | Install-PackageProvider NanoServerPackage -requiredVersion 0.1.1.0 26 | Import-PackageProvider NanoServerPackage 27 | ``` 28 | 29 | There are two sets of cmdlets provided in NanoServerPackage provider. The first set is specific to the provider: 30 | ``` 31 | Find-NanoServerPackage 32 | Save-NanoServerPackage 33 | Install-NanoServerPackage 34 | ``` 35 | 36 | The second set is generic PackageManagement cmdlets: 37 | ``` 38 | Find-Package 39 | Save-Package 40 | Install-Package 41 | Get-Package 42 | ``` 43 | 44 | The provider needs to be loaded in PowerShell before you can use any of the cmdlets. You can load the provider by running ```Import-PackageProvider NanoServerPackage```. 45 | 46 | ## Searching for Windows Packages 47 | 48 | Both ```Find-NanoServerPackage``` and ```Find-Package``` search and return a list of Windows Packages available in the online repository. You may want to provide ```-ProviderName NanoServerPackage``` to ```Find-Package``` so that OneGet will not use other providers. In addition, when using the generic OneGet cmdlet, you can also use a ```-DisplayCulture``` switch so the culture of the packages will be displayed. 49 | 50 | ##### Example 1 51 | Find the latest version of any Windows Packages that match a given name. Wildcard is also accepted 52 | ``` 53 | Find-NanoServerPackage -Name Microsoft-NanoServer-Containers-Package 54 | Find-NanoServerPackage -Name *Containers* 55 | ``` 56 | OR 57 | ``` 58 | Find-Package -ProviderName NanoServerPackage -Name Microsoft-NanoServerPackage-Containers-Package -DisplayCulture 59 | Find-Package -ProviderName NanoServerPackage -Name *Containers* -DisplayCulture 60 | ``` 61 | 62 | ##### Example 2 63 | 64 | Find the latest version of all available Windows Packages. The available cultures for each package are also displayed 65 | ``` 66 | Find-NanoServerPackage 67 | ``` 68 | OR 69 | ``` 70 | Find-Package -ProviderName NanoServerPackage -DisplayCulture 71 | ``` 72 | 73 | ##### Example 3 74 | 75 | Find the latest version of all available Windows Packages with culture en-us 76 | ``` 77 | Find-NanoServerPackage -Culture en-us 78 | ``` 79 | OR 80 | ``` 81 | Find-Package -ProviderName NanoServerPackage -Culture en-us -DisplayCulture 82 | ``` 83 | 84 | 85 | ##### Example 4 86 | Find Windows Packages that have a certain version using ```-RequiredVersion``` 87 | ``` 88 | Find-NanoServerPackage -RequiredVersion 10.0.14393.0 89 | ``` 90 | OR 91 | ``` 92 | Find-Package -ProviderName NanoServerPackage -RequiredVersion 10.0.14393.0 -DisplayCulture 93 | ``` 94 | 95 | ##### Example 5 96 | Find Windows Packages within a certain version range using ```-MinimumVersion``` and ```-MaximumVersion```. The latest version of a package that satisfies the version range will be returned. For example, if the minimum version specified is 5.0, and the package have version 1.0, 5.0, 6.0 and 7.0, then the 7.0 version of the package will be returned. 97 | ``` 98 | Find-NanoServerPackage -MinimumVersion 10.0 99 | ``` 100 | OR 101 | ``` 102 | Find-Package -ProviderName NanoServerPackage -MinimumVersion 10.0 -DisplayCulture 103 | ``` 104 | 105 | ##### Example 6 106 | Find all available versions of a Windows Package using ```-AllVersions``` switch. This switch can also be used with ```-MinimumVersion``` and ```-MaximumVersion``` but not with ```-RequiredVersion```. 107 | ``` 108 | Find-NanoServerPackage *Containers* -AllVersions 109 | Find-NanoServerPackage *Containers* -AllVersions -MinimumVersion 10.0 110 | ``` 111 | OR 112 | ``` 113 | Find-Package *Containers* -ProviderName NanoServerPackage -AllVersions -DisplayCulture 114 | Find-Package *Containers* -ProviderName NanoServerPackage -AllVersions -DisplayCulture -MinimumVersion 10.0 115 | ``` 116 | 117 | ## Installing Windows Packages Online or Offline 118 | 119 | >`NOTE` 120 | > If you install an optional Nano Server package from media or online repository, it won't have recent security fixes included. To avoid a version mismatch between the optional packages and base operating system, you should install the [latest cumulative update](https://technet.microsoft.com/windows-server-docs/get-started/update-nano-server) immediately after installing any optional packages and **before** restarting the server. 121 | 122 | You can install a Windows Package (including its dependency packages, if any) using either ```Install-NanoServerPackage``` or ```Install-Package```. If you want to install the package to an offline NanoServer image, you can specify the path to the offline image with ```-ToVhd``` parameter. Otherwise, the cmdlets will install the package to the local machine. 123 | 124 | Both cmdlets accept pipeline result from the search cmdlets. The culture of the package has to match the culture of the machine you are installing it to for the package to work properly. The cmdlets have auto-detection logic that will determine the suitable culture. 125 | 126 | ##### Example 1 127 | Installing the latest version of the Containers package to the local machine 128 | ``` 129 | Install-NanoServerPackage -Name Microsoft-NanoServer-Containers-Package 130 | ``` 131 | OR 132 | ``` 133 | Install-Package -ProviderName NanoServerPackage -Name Microsoft-NanoServer-Containers-Package -DisplayCulture 134 | ``` 135 | 136 | ##### Example 2 137 | Install a package that depends on other packages. In this case, the dependency packages will be installed as well. 138 | ``` 139 | Find-NanoServerPackage *scvmm-compute* | install-package 140 | ``` 141 | 142 | ##### Example 3 143 | Install the latest version of the DCB package to an offline NanoServer image. 144 | ``` 145 | Install-NanoServerPackage -Name Microsoft-NanoServer-DCB-Package -ToVhd C:\OfflineVhd.vhd 146 | ``` 147 | OR 148 | ``` 149 | Install-Package -Name Microsoft-NanoServer-DCB-Package -ToVhd C:\OfflineVhd.vhd -ProviderName NanoServerPackage -DisplayCulture 150 | ``` 151 | 152 | ##### Example 4 153 | Install the Containers package by piping the result from the search cmdlets. Please do not specify ```-ProviderName``` on the ```Install-Package``` cmdlet if you use it this way. 154 | ``` 155 | Find-NanoServerPackage *Containers* | Install-NanoServerPackage 156 | ``` 157 | OR 158 | ``` 159 | Find-Package -ProviderName NanoServerPackage *Containers* | Install-Package -DisplayCulture 160 | ``` 161 | 162 | ## Dowloading Windows Packages 163 | You can download a Windows Package (including its dependency packages, if any) without installing it by using ```Save-NanoServerPackage``` or ```Save-Package``` cmdlets. Both cmdlets accept pipeline result from the search cmdlets. These cmdlets will download both the base package and the language package. If you do not specify the ```-Culture``` parameter, the culture of the local machine will be used. 164 | 165 | ##### Example 1 166 | Download and save the Containers package to a directory that matches the wildcard path using the culture of the local machine. 167 | ``` 168 | Save-NanoServerPackage Microsoft-NanoServer-Containers-Package -Path C:\ 169 | ``` 170 | OR 171 | ``` 172 | Save-Package -ProviderName NanoServerPackage Microsoft-NanoServer-Containers-Package -Path C:\ -DisplayCulture 173 | ``` 174 | 175 | ##### Example 2 176 | Download and save version 10.0.14393.0 of the Containers package with de-de culture to the current directory. 177 | ``` 178 | Save-NanoServerPackage Microsoft-NanoServer-Containers-Package -Path .\ -Culture de-de -RequiredVersion 10.0.14393.0 179 | ``` 180 | OR 181 | ``` 182 | Save-Package -ProviderName NanoServerPackage Microsoft-NanoServer-Containers-Package -Path .\ -Culture de-de -RequiredVersion 10.0.14393.0 -DisplayCulture 183 | ``` 184 | 185 | ##### Example 3 186 | Download and save the it-it culture of the Shielded VM package to the current directory by piping the results from the search cmdlets. The dependency package will be downloaded as well. 187 | ``` 188 | Find-NanoServerPackage -Name *shielded* -Culture it-it | Save-NanoServerPackage -Path .\ 189 | ``` 190 | OR 191 | ``` 192 | Find-Package -ProviderName NanoServerPackage *shielded* -Culture it-it | Save-Package -Path .\ -DisplayCulture 193 | ``` 194 | 195 | ## Inventory what Windows Packages are installed 196 | You can search for installed packages on your local NanoServer machine or an offline NanoServer image using ```Get-Package```. 197 | 198 | ##### Example 1 199 | Search for all Windows Packages installed on the local machine. 200 | ``` 201 | Get-Package -ProviderName NanoServerPackage -DisplayCulture 202 | ``` 203 | 204 | ##### Example 2 205 | Search for all Windows Packages installed on an offline NanoServer image. 206 | ``` 207 | Get-Package -ProviderName NanoServerPackage -FromVhd C:\OfflineVhd.vhd -DisplayCulture 208 | ``` 209 | 210 | ## Version 211 | 1.0.1.0 212 | 213 | ## Version History 214 | #### 1.0.1.0 215 | Public release for NanoServerPackage Provider that works for Nano Server WS2016 GA version 216 | #### 0.1.1.0 217 | Initial public release for Nano Package Provider that works for TP5 Nano Server 218 | 219 | ### Dependencies 220 | This module has no dependencies 221 | 222 | ## Known Issues 223 | 1. This provider does not support PowerShell Direct session. 224 | 2. Using the NanoServerPackage provider 1.0.1.0 to search for packages fails in Windows Containers. As a workaround, you may use the NanoServerPackage provider on another machine to download the packages, then copy and DISM install them in the container. 225 | 226 | ## Fixed issues in v1.0.1.0 227 | 1. In v0.1.1.0, you cannot install Microsoft-NanoServer-IIS-Package and Microsoft-NanoServer-SCVMM-Package online. There are two workarounds: 228 | 229 | i. Install another package that will require reboot such as Microsoft-NanoServer-Storage-Package first and without rebooting, install the required package. 230 | 231 | ii. Install these packages offline using -ToVhd 232 | 233 | 2. In v0.1.1.0, you might see an error as shown below while installing certain packages. This is mainly because this provider does not support discovering and installing dependencies. For these cases, refer to guide on Getting Started with Nano Server to identify the dependencies. 234 | ``` 235 | install-package : Add-WindowsPackage failed. Error code = 0x800f0922 236 | + CategoryInfo : InvalidOperation: (System.String[]:String) [Install-Package], Exception 237 | + FullyQualifiedErrorId : FailedToInstall,Install-PackageHelper,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage 238 | ``` 239 | --------------------------------------------------------------------------------