diff --git a/UnitySetup/DSCResources/xUnityLicense/xUnityLicense.psm1 b/UnitySetup/DSCResources/xUnityLicense/xUnityLicense.psm1 new file mode 100644 index 0000000..f082a66 --- /dev/null +++ b/UnitySetup/DSCResources/xUnityLicense/xUnityLicense.psm1 @@ -0,0 +1,101 @@ +function Get-TargetResource { + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [System.Management.Automation.PSCredential] + $Credential, + + [System.Management.Automation.PSCredential] + $Serial, + + [System.String] + $UnityVersion, + + [System.String] + $Name + ) + + @{ 'Licenses' = (Get-UnityLicense) } +} + + +function Set-TargetResource { + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Credential, + + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = 'Present', + + [parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Serial, + + [System.String] + $UnityVersion, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + if ( Test-TargetResource @PSBoundParameters ) { return } + + $unityArgs = @{ + 'Credential' = $Credential + 'Wait' = $true + } + + if ( $UnityVersion ) { $unityArgs['Version'] = $UnityVersion } + if ( $Ensure -eq 'Present' ) { $unityArgs['Serial'] = $Serial.Password } + else { $unityArgs['ReturnLicense'] = $true } + + Start-UnityEditor @unityArgs -Verbose +} + + +function Test-TargetResource { + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [System.Management.Automation.PSCredential] + $Credential, + + [ValidateSet("Present", "Absent")] + [System.String] + $Ensure = 'Present', + + [parameter(Mandatory = $true)] + [System.Management.Automation.PSCredential] + $Serial, + + [System.String] + $UnityVersion, + + [parameter(Mandatory = $true)] + [System.String] + $Name + ) + + foreach ( $license in (Get-UnityLicense -Serial $Serial.Password) ) { + Write-Verbose "Found license: $license" + + $currentSerial = [System.Net.NetworkCredential]::new($null, $license.Serial).Password + $passedSerial = $Serial.GetNetworkCredential().Password + if ( $currentSerial -ne $passedSerial ) { continue } + + return $Ensure -eq 'Present' + } + + return $Ensure -eq 'Absent' +} + + +Export-ModuleMember -Function *-TargetResource + diff --git a/UnitySetup/DSCResources/xUnityLicense/xUnityLicense.schema.mof b/UnitySetup/DSCResources/xUnityLicense/xUnityLicense.schema.mof new file mode 100644 index 0000000..3394e96 --- /dev/null +++ b/UnitySetup/DSCResources/xUnityLicense/xUnityLicense.schema.mof @@ -0,0 +1,11 @@ + +[ClassVersion("1.0.0.0"), FriendlyName("xUnityLicense")] +class xUnityLicense : OMI_BaseResource +{ + [Key] string Name; + [Required, EmbeddedInstance("MSFT_Credential")] String Credential; + [Required, EmbeddedInstance("MSFT_Credential")] String Serial; + [Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] string Ensure; + [Write] string UnityVersion; +}; + diff --git a/UnitySetup/Examples/Sample_xUnity.ps1 b/UnitySetup/Examples/Sample_xUnity.ps1 new file mode 100644 index 0000000..460dff3 --- /dev/null +++ b/UnitySetup/Examples/Sample_xUnity.ps1 @@ -0,0 +1,43 @@ +<# + Create a custom configuration by passing in necessary values +#> +Configuration Sample_xUnitySetupInstance { + param + ( + [System.String] + $Versions = '2017.4.2f2' + + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [System.String[]] + $Components = @('Windows', 'Mac', 'Linux', 'Metro', 'iOS', 'Android'), + + [PSCredential] + $UnityCredential, + + [PSCredential] + $UnitySerial + ) + + Import-DscResource -ModuleName UnitySetup + + Node 'localhost' { + + xUnitySetupInstance Unity { + Versions = $Versions + Components = $Components + Ensure = $Ensure + DependsOn = if( $Ensure -eq 'Absent' ) { '[xUnityLicense]UnityLicense' } + } + + xUnityLicense UnityLicense { + Name = 'UL01' + Credential = $UnityCredential + Serial = $UnitySerial + Ensure = $Ensure + DependsOn = if( $Ensure -eq 'Present' ) { '[xUnitySetupInstance]Unity' } + } + } +} \ No newline at end of file diff --git a/UnitySetup/Examples/Sample_xUnitySetupInstance.ps1 b/UnitySetup/Examples/Sample_xUnitySetupInstance.ps1 deleted file mode 100644 index 7280942..0000000 --- a/UnitySetup/Examples/Sample_xUnitySetupInstance.ps1 +++ /dev/null @@ -1,28 +0,0 @@ -<# - Create a custom configuration by passing in necessary values -#> -Configuration Sample_xUnitySetupInstance { - param - ( - [System.String] - $Versions = '2017.3.0f3,2018.1.0b9', - - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [System.String[]] - $Components = @('Setup', 'Linux', 'Metro', 'Mac', 'iOS') - ) - - Import-DscResource -ModuleName UnitySetup - - Node 'localhost' { - - xUnitySetupInstance Unity { - Versions = $Versions - Components = $Components - Ensure = $Ensure - } - } -} \ No newline at end of file diff --git a/UnitySetup/Examples/Sample_xUnitySetupInstance_Install.ps1 b/UnitySetup/Examples/Sample_xUnitySetupInstance_Install.ps1 deleted file mode 100644 index 7763dc3..0000000 --- a/UnitySetup/Examples/Sample_xUnitySetupInstance_Install.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -<# - Install multiple versions of Unity and several components -#> -Configuration Sample_xUnitySetupInstance_Install { - - Import-DscResource -ModuleName UnitySetup - - Node 'localhost' { - - xUnitySetupInstance Unity { - Versions = '2017.3.1f1,2018.1.0b9' - Components = 'Setup', 'Mac', 'Linux', 'Metro', 'iOS' - Ensure = 'Present' - } - } -} \ No newline at end of file diff --git a/UnitySetup/Examples/Sample_xUnitySetupInstance_Uninstall.ps1 b/UnitySetup/Examples/Sample_xUnitySetupInstance_Uninstall.ps1 deleted file mode 100644 index c5f0764..0000000 --- a/UnitySetup/Examples/Sample_xUnitySetupInstance_Uninstall.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -<# - Uninstall multiple versions of Unity -#> -Configuration Sample_xUnitySetupInstance_Install { - - Import-DscResource -ModuleName UnitySetup - - Node 'localhost' { - - xUnitySetupInstance Unity { - Versions = '2017.3.1f1,2018.1.0b9' - Ensure = 'Absent' - } - } -} \ No newline at end of file diff --git a/UnitySetup/Examples/Sample_xUnity_Install.ps1 b/UnitySetup/Examples/Sample_xUnity_Install.ps1 new file mode 100644 index 0000000..9bfdff5 --- /dev/null +++ b/UnitySetup/Examples/Sample_xUnity_Install.ps1 @@ -0,0 +1,30 @@ +<# + Install multiple versions of Unity and several components +#> +Configuration Sample_xUnitySetupInstance_Install { + + param( + [PSCredential]$UnityCredential, + [PSCredential]$UnitySerial + ) + + Import-DscResource -ModuleName UnitySetup + + Node 'localhost' { + + xUnitySetupInstance Unity { + Versions = '2017.4.2f2' + Components = 'Windows', 'Mac', 'Linux', 'Metro', 'iOS', 'Android' + Ensure = 'Present' + } + + xUnityLicense UnityLicense { + Name = 'UL01' + Credential = $UnityCredential + Serial = $UnitySerial + Ensure = 'Present' + UnityVersion = '2017.4.2f2' + DependsOn = '[xUnitySetupInstance]Unity' + } + } +} \ No newline at end of file diff --git a/UnitySetup/Examples/Sample_xUnity_Uninstall.ps1 b/UnitySetup/Examples/Sample_xUnity_Uninstall.ps1 new file mode 100644 index 0000000..cb08487 --- /dev/null +++ b/UnitySetup/Examples/Sample_xUnity_Uninstall.ps1 @@ -0,0 +1,29 @@ +<# + Uninstall multiple versions of Unity +#> +Configuration Sample_xUnitySetupInstance_Install { + + param( + [PSCredential]$UnityCredential, + [PSCredential]$UnitySerial + ) + + Import-DscResource -ModuleName UnitySetup + + Node 'localhost' { + + xUnitySetupInstance Unity { + Versions = '2017.4.2f2' + Ensure = 'Absent' + DependsOn = '[xUnityLicense]UnityLicense' + } + + xUnityLicense UnityLicense { + Name = 'UL01' + Credential = $UnityCredential + Serial = $UnitySerial + Ensure = 'Absent' + UnityVersion = '2017.4.2f2' + } + } +} \ No newline at end of file diff --git a/UnitySetup/UnitySetup.psd1 b/UnitySetup/UnitySetup.psd1 index e037934..ad94d75 100644 --- a/UnitySetup/UnitySetup.psd1 +++ b/UnitySetup/UnitySetup.psd1 @@ -82,7 +82,8 @@ 'Select-UnitySetupInstance', 'Uninstall-UnitySetupInstance', 'Start-UnityEditor', - 'ConvertTo-UnitySetupComponent' + 'ConvertTo-UnitySetupComponent', + 'Get-UnityLicense' ) # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. diff --git a/UnitySetup/UnitySetup.psm1 b/UnitySetup/UnitySetup.psm1 index 94bdc06..4db4e4d 100644 --- a/UnitySetup/UnitySetup.psm1 +++ b/UnitySetup/UnitySetup.psm1 @@ -710,6 +710,12 @@ function Get-UnityProjectInstance { The platform build target for the Unity Editor to start in. .PARAMETER AcceptAPIUpdate Accept the API Updater automatically. Implies BatchMode unless explicitly specified by the user. +.PARAMETER Credential + What user name and password should be used by Unity for activation? +.PARAMETER Serial + What serial should be used by Unity for activation? Implies BatchMode and Quit if they're not supplied by the User. +.PARAMETER ReturnLicense + Unity should return the current license it's been activated with. Implies Quit if not supplied by the User. .PARAMETER BatchMode Should the Unity Editor start in batch mode? .PARAMETER Quit @@ -768,6 +774,14 @@ function Start-UnityEditor { [parameter(Mandatory = $false)] [switch]$AcceptAPIUpdate, [parameter(Mandatory = $false)] + [pscredential]$Credential, + [parameter(Mandatory = $false)] + [securestring]$Serial, + [parameter(Mandatory = $false)] + [switch]$ReturnLicense, + [parameter(Mandatory = $false)] + [switch]$ForceFree, + [parameter(Mandatory = $false)] [switch]$BatchMode, [parameter(Mandatory = $false)] [switch]$Quit, @@ -835,11 +849,20 @@ function Start-UnityEditor { } $sharedArgs = @() + if ( $ReturnLicense ) { + if ( -not $PSBoundParameters.ContainsKey('BatchMode') ) { $BatchMode = $true } + if ( -not $PSBoundParameters.ContainsKey('Quit') ) { $Quit = $true } + + $sharedArgs += '-returnLicense' + } + if ( $Serial ) { + if ( -not $PSBoundParameters.ContainsKey('BatchMode') ) { $BatchMode = $true } + if ( -not $PSBoundParameters.ContainsKey('Quit') ) { $Quit = $true } + } if ( $AcceptAPIUpdate ) { $sharedArgs += '-accept-apiupdate' if ( -not $PSBoundParameters.ContainsKey('BatchMode')) { $BatchMode = $true } } - if ( $CreateProject ) { $sharedArgs += "-createProject", $CreateProject } if ( $ExecuteMethod ) { $sharedArgs += "-executeMethod", $ExecuteMethod } if ( $OutputPath ) { $sharedArgs += "-buildOutput", $OutputPath } @@ -849,6 +872,8 @@ function Start-UnityEditor { if ( $Quit ) { $sharedArgs += "-quit" } if ( $ExportPackage ) { $sharedArgs += "-exportPackage", "$ExportPackage" } if ( $ImportPackage ) { $sharedArgs += "-importPackage", "$ImportPackage" } + if ( $Credential ) { $sharedArgs += '-username', $Credential.UserName } + if ( $ForceFree) { $sharedArgs += '-force-free' } $instanceArgs = @() foreach ( $p in $projectInstances ) { @@ -925,18 +950,27 @@ function Start-UnityEditor { Write-Verbose "Redirecting standard output to $($setProcessArgs['RedirectStandardOutput'])" Write-Verbose "Redirecting standard error to $($setProcessArgs['RedirectStandardError'])" - if ($unityArgs -and $unityArgs.Length -gt 0) { - $setProcessArgs['ArgumentList'] = $unityArgs - } + $actionString = "$editor $unityArgs" + if( $Credential ) { $actionString += " -password (hidden)"} + if( $Serial ) { $actionString += " -serial (hidden)"} - if (-not $PSCmdlet.ShouldProcess("$editor $unityArgs", "Start-Process")) { + if (-not $PSCmdlet.ShouldProcess($actionString, "Start-Process")) { continue } + # Defered till after potential display by ShouldProcess + if ( $Credential ) { $unityArgs += '-password', $Credential.GetNetworkCredential().Password } + if ( $Serial ) { $unityArgs += '-serial', [System.Net.NetworkCredential]::new($null, $Serial).Password } + + if ($unityArgs -and $unityArgs.Length -gt 0) { + $setProcessArgs['ArgumentList'] = $unityArgs + } + $process = Start-Process @setProcessArgs if ( $Wait ) { if ( $process.ExitCode -ne 0 ) { if ( $LogFile -and (Test-Path $LogFile -Type Leaf) ) { + Write-Verbose "Writing $LogFile to Information stream Tagged as 'Logs'" Get-Content $LogFile | ForEach-Object { Write-Information -MessageData $_ -Tags 'Logs' } } @@ -949,6 +983,42 @@ function Start-UnityEditor { } } +function ConvertTo-DateTime { + param([string] $Text) + + if( -not $text -or $text.Length -eq 0 ) { [DateTime]::MaxValue } + else { [DateTime]$Text } +} + +function Get-UnityLicense +{ + [CmdletBinding()] + param([SecureString]$Serial, [UnityVersion]$UnityVersion) + + $licenseFiles = Get-ChildItem "C:\ProgramData\Unity\Unity_*.ulf" + foreach ( $licenseFile in $licenseFiles ) { + Write-Verbose "Discovered License File at $licenseFile" + $doc = [xml](Get-Content "$licenseFile") + $devBytes = [System.Convert]::FromBase64String($doc.root.License.DeveloperData.Value) + + # The first four bytes look like a count so skip that to pull out the serial string + $licenseSerial = [String]::new($devBytes[4..($devBytes.Length - 1)]) + if( $Serial -and [System.Net.NetworkCredential]::new($null, $Serial).Password -ne $licenseSerial ) { continue; } + + $license = $doc.root.License + [PSCustomObject]@{ + 'LicenseVersion' = $license.LicenseVersion.Value + 'Serial' = ConvertTo-SecureString $licenseSerial -AsPlainText -Force + 'UnityVersion' = [UnityVersion]$license.ClientProvidedVersion.Value + 'DisplaySerial' = $license.SerialMasked.Value + 'ActivationDate' = ConvertTo-DateTime $license.InitialActivationDate.Value + 'StartDate' = ConvertTo-DateTime $license.StartDate.Value + 'StopDate' = ConvertTo-DateTime $license.StopDate.Value + 'UpdateDate' = ConvertTo-DateTime $license.UpdateDate.Value + } + } +} + @( @{ 'Name' = 'gusi'; 'Value' = 'Get-UnitySetupInstance' }, @{ 'Name' = 'gupi'; 'Value' = 'Get-UnityProjectInstance' },