├── LICENSE
├── PDP
├── InAppProductDescription.xml
├── InAppProductDescription.xsd
└── ProductDescription.xml
├── StoreBroker.nuspec
├── AzureService
├── RESTProxyContent
│ ├── bin
│ │ └── StartupTasks
│ │ │ ├── TimeZone
│ │ │ ├── EnsureTimeZone.cmd
│ │ │ └── Ensure-TimeZone.ps1
│ │ │ ├── RemoteDesktop
│ │ │ ├── EnsureRemoteDesktopEnabled.cmd
│ │ │ └── Ensure-RemoteDesktopEnabled.ps1
│ │ │ ├── LocalAdmin
│ │ │ ├── EnsureLocalAdmin.cmd
│ │ │ └── Ensure-LocalAdmin.ps1
│ │ │ ├── DomainJoined
│ │ │ ├── EnsureDomainJoined.cmd
│ │ │ └── Ensure-DomainJoined.ps1
│ │ │ ├── FixAuth
│ │ │ └── FixAuthentication.cmd
│ │ │ └── Helpers
│ │ │ ├── GroupManagement.ps1
│ │ │ └── Encryption.ps1
│ └── diagnostics.wadcfgx
├── Profiles
│ ├── StoreBrokerProxyStaging.azurePubxml
│ └── StoreBrokerProxyProduction.azurePubxml
├── ServiceConfiguration.Local.cscfg
├── ServiceConfiguration.Cloud.cscfg
├── AzureService.ccproj
└── ServiceDefinition.csdef
├── StoreBroker.sln
├── SECURITY.md
├── StoreBroker.pssproj
├── Tests
└── PackageTool.Tests.ps1
├── .gitignore
├── README.md
├── StoreBroker
├── StoreBroker.psd1
├── IapConfigTemplate.json
├── NugetTools.ps1
└── AppConfigTemplate.json
├── Documentation
├── GOVERNANCE.md
└── PDP.md
├── CONTRIBUTING.md
└── Extensions
└── ConvertFrom-ExistingIapSubmission.ps1
/LICENSE:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/StoreBroker/HEAD/LICENSE
--------------------------------------------------------------------------------
/PDP/InAppProductDescription.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 | My Localized App Add-on IAP
11 |
12 |
13 |
14 |
15 |
16 | This add-on will provide you with 200 extra coins.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/StoreBroker.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Microsoft.Windows.StoreBroker
5 | $version$
6 | Microsoft
7 | microsoft,nugetstorebrokerteam
8 | https://aka.ms/StoreBroker_License
9 | https://aka.ms/StoreBroker
10 | http://go.microsoft.com/fwlink/?LinkID=288890
11 | true
12 | A PowerShell module that leverages the Windows Store Submission API to allow easy automation of application submissions to the Windows Store.
13 | © Microsoft Corporation. All rights reserved.
14 | microsoft windows store broker storebroker submission api powershell posh module
15 | en-US
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/TimeZone/EnsureTimeZone.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | :: Log the startup date and time.
4 | SET timehour=%time:~0,2%
5 | SET timestamp=%date:~-4,4%%date:~-10,2%%date:~-7,2%-%timehour: =0%%time:~3,2%
6 | SET startuptasklogsubdir=TimeZone
7 | SET startuptasklogdir=%PathToStartupLogs%\%startuptasklogsubdir%\
8 | SET startuptasklog=%startuptasklogdir%\startup-%timestamp%.txt
9 |
10 | IF NOT EXIST %startuptasklogdir% MKDIR %startuptasklogdir%
11 |
12 | IF "%ComputeEmulatorRunning%" == "true" (
13 | ECHO Detected running in an emulator. Skipping setting the time zone. 2>&1
14 | ) ELSE (
15 | ECHO Starting up %0. >> "%startuptasklog%" 2>&1
16 |
17 | :: Call the file, redirecting all output to the StartupLog.txt log file.
18 | powershell -executionpolicy unrestricted ". %0\..\Ensure-TimeZone.ps1; Ensure-TimeZone -TimeZoneId '%TimeZone%' -Verbose" >> "%startuptasklog%" 2>&1
19 | )
20 |
21 | :: Log the completion of file/
22 | ECHO Returned to %0. >> "%startuptasklog%" 2>&1
23 |
24 | IF %ERRORLEVEL% EQU 0 (
25 | :: No errors occurred. Exit normally.
26 | ECHO Done >> "%startuptasklog%"
27 | EXIT /B 0
28 | ) ELSE (
29 | :: Log the error.
30 | ECHO An error occurred. The ERRORLEVEL = %ERRORLEVEL%. >> "%startuptasklog%" 2>&1
31 | ECHO Done >> "%startuptasklog%"
32 | EXIT /B %ERRORLEVEL%
33 | )
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/RemoteDesktop/EnsureRemoteDesktopEnabled.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | :: Log the startup date and time.
4 | SET timehour=%time:~0,2%
5 | SET timestamp=%date:~-4,4%%date:~-10,2%%date:~-7,2%-%timehour: =0%%time:~3,2%
6 | SET startuptasklogsubdir=RemoteDesktop
7 | SET startuptasklogdir=%PathToStartupLogs%\%startuptasklogsubdir%\
8 | SET startuptasklog=%startuptasklogdir%\startup-%timestamp%.txt
9 |
10 | IF NOT EXIST %startuptasklogdir% MKDIR %startuptasklogdir%
11 |
12 | IF "%ComputeEmulatorRunning%" == "true" (
13 | ECHO Detected running in an emulator. Skipping enabling remote desktop. 2>&1
14 | ) ELSE (
15 | ECHO Starting up %0. >> "%startuptasklog%" 2>&1
16 |
17 | :: Call the file, redirecting all output to the StartupLog.txt log file.
18 | powershell -executionpolicy unrestricted ". %0\..\Ensure-RemoteDesktopEnabled.ps1; Ensure-RemoteDesktopEnabled -Verbose" >> "%startuptasklog%" 2>&1
19 | )
20 |
21 | :: Log the completion of file/
22 | ECHO Returned to %0. >> "%startuptasklog%" 2>&1
23 |
24 | IF %ERRORLEVEL% EQU 0 (
25 | :: No errors occurred. Exit normally.
26 | ECHO Done >> "%startuptasklog%"
27 | EXIT /B 0
28 | ) ELSE (
29 | :: Log the error.
30 | ECHO An error occurred. The ERRORLEVEL = %ERRORLEVEL%. >> "%startuptasklog%" 2>&1
31 | ECHO Done >> "%startuptasklog%"
32 | EXIT /B %ERRORLEVEL%
33 | )
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/LocalAdmin/EnsureLocalAdmin.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | :: Log the startup date and time.
4 | SET timehour=%time:~0,2%
5 | SET timestamp=%date:~-4,4%%date:~-10,2%%date:~-7,2%-%timehour: =0%%time:~3,2%
6 | SET startuptasklogsubdir=EnsureLocalAdmin
7 | SET startuptasklogdir=%PathToStartupLogs%\%startuptasklogsubdir%\
8 | SET startuptasklog=%startuptasklogdir%\startup-%timestamp%.txt
9 |
10 | IF NOT EXIST %startuptasklogdir% MKDIR %startuptasklogdir%
11 |
12 | IF "%ComputeEmulatorRunning%" == "true" (
13 | ECHO Detected running in an emulator. Skipping admin creation. 2>&1
14 | ) ELSE (
15 | ECHO Starting up %0. >> "%startuptasklog%" 2>&1
16 |
17 | :: Call the file, redirecting all output to the StartupLog.txt log file.
18 | powershell -executionpolicy unrestricted ". %0\..\Ensure-LocalAdmin.ps1; Ensure-LocalAdmin -userName '%LOCAL_ADMIN_USERNAME%' -secret '%LOCAL_ADMIN_SECRET%' -certThumbprint '%LOCAL_ADMIN_CERT_THUMBPRINT%' -certStore '%LOCAL_ADMIN_CERT_STORE%' -Verbose" >> "%startuptasklog%" 2>&1
19 | )
20 |
21 | :: Log the completion of file/
22 | ECHO Returned to %0. >> "%startuptasklog%" 2>&1
23 |
24 | IF %ERRORLEVEL% EQU 0 (
25 | :: No errors occurred. Exit normally.
26 | ECHO Done >> "%startuptasklog%"
27 | EXIT /B 0
28 | ) ELSE (
29 | :: Log the error.
30 | ECHO An error occurred. The ERRORLEVEL = %ERRORLEVEL%. >> "%startuptasklog%" 2>&1
31 | ECHO Done >> "%startuptasklog%"
32 | EXIT /B %ERRORLEVEL%
33 | )
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/DomainJoined/EnsureDomainJoined.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | :: Log the startup date and time.
4 | SET timehour=%time:~0,2%
5 | SET timestamp=%date:~-4,4%%date:~-10,2%%date:~-7,2%-%timehour: =0%%time:~3,2%
6 | SET startuptasklogsubdir=EnsureDomainJoined
7 | SET startuptasklogdir=%PathToStartupLogs%\%startuptasklogsubdir%\
8 | SET startuptasklog=%startuptasklogdir%\startup-%timestamp%.txt
9 |
10 | IF NOT EXIST %startuptasklogdir% MKDIR %startuptasklogdir%
11 |
12 | IF "%ComputeEmulatorRunning%" == "true" (
13 | ECHO Detected running in an emulator. Skipping domain join. 2>&1
14 | ) ELSE (
15 | ECHO Starting up %0. >> "%startuptasklog%" 2>&1
16 |
17 | :: Call the file, redirecting all output to the StartupLog.txt log file.
18 | powershell -executionpolicy unrestricted ". %0\..\Ensure-DomainJoined.ps1; Ensure-DomainJoined -Domain '%DOMAIN_NAME%' -UserName '%DOMAIN_USERNAME%' -Secret '%DOMAIN_SECRET%' -CertThumbprint '%DOMAIN_CERT_THUMBPRINT%' -CertStore '%DOMAIN_CERT_STORE%' -Verbose" >> "%startuptasklog%" 2>&1
19 | )
20 |
21 | :: Log the completion of file/
22 | ECHO Returned to %0. >> "%startuptasklog%" 2>&1
23 |
24 | IF %ERRORLEVEL% EQU 0 (
25 | :: No errors occurred. Exit normally.
26 | ECHO Done >> "%startuptasklog%"
27 | EXIT /B 0
28 | ) ELSE (
29 | :: Log the error.
30 | ECHO An error occurred. The ERRORLEVEL = %ERRORLEVEL%. >> "%startuptasklog%" 2>&1
31 | ECHO Done >> "%startuptasklog%"
32 | EXIT /B %ERRORLEVEL%
33 | )
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/FixAuth/FixAuthentication.cmd:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | :: Log the startup date and time.
4 | SET timehour=%time:~0,2%
5 | SET timestamp=%date:~-4,4%%date:~-10,2%%date:~-7,2%-%timehour: =0%%time:~3,2%
6 | SET startuptasklogsubdir=FixAuth
7 | SET startuptasklogdir=%PathToStartupLogs%\%startuptasklogsubdir%\
8 | SET startuptasklog=%startuptasklogdir%\startup-%timestamp%.txt
9 |
10 | IF NOT EXIST %startuptasklogdir% MKDIR %startuptasklogdir%
11 |
12 | IF "%ComputeEmulatorRunning%" == "true" (
13 | ECHO Detected running in an emulator. Skipping the modification of IIS authentication modes. 2>&1
14 | ) ELSE (
15 | ECHO Starting up %0. >> "%startuptasklog%" 2>&1
16 |
17 | :: For some reason, the authentication changes applied to Web.Config are not working,
18 | :: so these changes need to be applied via appcmd instead.
19 | %systemroot%\system32\inetsrv\appcmd.exe set config -section:anonymousAuthentication -enabled:%ENABLE_ANONYMOUS_AUTHENTICATION%
20 | %systemroot%\system32\inetsrv\appcmd.exe set config -section:windowsAuthentication -enabled:%ENABLE_WINDOWS_AUTHENTICATION%
21 | )
22 |
23 | :: Log the completion of file/
24 | ECHO Returned to %0. >> "%startuptasklog%" 2>&1
25 |
26 | IF %ERRORLEVEL% EQU 0 (
27 | :: No errors occurred. Exit normally.
28 | ECHO Done >> "%startuptasklog%"
29 | EXIT /B 0
30 | ) ELSE (
31 | :: Log the error.
32 | ECHO An error occurred. The ERRORLEVEL = %ERRORLEVEL%. >> "%startuptasklog%" 2>&1
33 | ECHO Done >> "%startuptasklog%"
34 | EXIT /B %ERRORLEVEL%
35 | )
--------------------------------------------------------------------------------
/AzureService/Profiles/StoreBrokerProxyStaging.azurePubxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {"ServiceManagementEndpoint":"https:\/\/management.core.windows.net\/","ResourceManagementEndpoint":"https:\/\/management.azure.com\/","SubscriptionId":""}
5 | False
6 | StoreBrokerProxy
7 | DeleteAndCreate
8 | Staging
9 | True
10 | False
11 | False
12 | StoreBrokerProxy
13 | StoreBrokerProxy
14 | False
15 | False
16 | Cloud
17 | Release
18 | StoreBrokerProxy
19 | StoreBrokerProxy
20 | True
21 |
22 |
--------------------------------------------------------------------------------
/AzureService/Profiles/StoreBrokerProxyProduction.azurePubxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {"ServiceManagementEndpoint":"https:\/\/management.core.windows.net\/","ResourceManagementEndpoint":"https:\/\/management.azure.com\/","SubscriptionId":""}
5 | False
6 | StoreBrokerProxy
7 | DeleteAndCreate
8 | Production
9 | True
10 | False
11 | False
12 | StoreBrokerProxy
13 | StoreBrokerProxy
14 | False
15 | False
16 | Cloud
17 | Release
18 | StoreBrokerProxy
19 | StoreBrokerProxy
20 | True
21 |
22 |
--------------------------------------------------------------------------------
/AzureService/ServiceConfiguration.Local.cscfg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/AzureService/ServiceConfiguration.Cloud.cscfg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/StoreBroker.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "StoreBroker", "StoreBroker.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RESTProxy", "RESTProxy\RESTProxy.csproj", "{D6FAFB1D-3B56-4332-A76F-E132A1891169}"
9 | EndProject
10 | Project("{CC5FD16D-436D-48AD-A40C-5A424C6E3E79}") = "AzureService", "AzureService\AzureService.ccproj", "{EC8AA218-B675-4003-B2E0-685BD96F8609}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {D6FAFB1D-3B56-4332-A76F-E132A1891169}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {D6FAFB1D-3B56-4332-A76F-E132A1891169}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {D6FAFB1D-3B56-4332-A76F-E132A1891169}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {D6FAFB1D-3B56-4332-A76F-E132A1891169}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {EC8AA218-B675-4003-B2E0-685BD96F8609}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {EC8AA218-B675-4003-B2E0-685BD96F8609}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {EC8AA218-B675-4003-B2E0-685BD96F8609}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {EC8AA218-B675-4003-B2E0-685BD96F8609}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | EndGlobal
35 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/diagnostics.wadcfgx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | true
47 |
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/RemoteDesktop/Ensure-RemoteDesktopEnabled.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | function Ensure-RegistryKeyProperty
4 | {
5 | <#
6 | .SYNOPSIS
7 | Ensures the registry key property is set with the indicated value.
8 |
9 | .DESCRIPTION
10 | Ensures the registry key property is set with the indicated value.
11 |
12 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
13 |
14 | .PARAMETER RegistryKey
15 | The registry key that has the property that should be set.
16 |
17 | .PARAMETER Property
18 | The name of the property under RegistryKey to set the value for.
19 |
20 | .PARAMETER Value
21 | The new value for Property.
22 |
23 | .PARAMETER Type
24 | The type of registry value that is being set.
25 |
26 | .EXAMPLE
27 | Ensure-RegistryKeyProperty -RegistryKey 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Property 'fDenyTSConnections' -Value 0000
28 |
29 | Sets the DWORD property called fDenyTSConnections to the value 0 under the registry key
30 | SYSTEM\CurrentControlSet\Control\Terminal Server in HKEY_LOCAL_MACHINE.
31 | #>
32 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
33 | param(
34 | [Parameter(Mandatory)]
35 | [string] $RegistryKey,
36 |
37 | [Parameter(Mandatory)]
38 | [string] $Property,
39 |
40 | [Parameter(Mandatory)]
41 | [string] $Value,
42 |
43 | [Microsoft.Win32.RegistryValueKind] $Type = 'DWord'
44 | )
45 |
46 | process
47 | {
48 | try
49 | {
50 | Set-ItemProperty -Path $RegistryKey -Name $Property -Value $Value -Type $Type -ErrorAction Stop
51 | Write-Verbose "Set property `"$Property`" in registry key `"$RegistryKey`" to `"$Value`" with a type of `"$Type`""
52 | }
53 | catch
54 | {
55 | Write-Error "Unable to set property $Property in registry key $RegistryKey to $Value"
56 | throw $_
57 | }
58 | }
59 | }
60 |
61 | function Ensure-RemoteDesktopEnabled
62 | {
63 | <#
64 | .SYNOPSIS
65 | Ensures that Remote Desktop access is enabled. It will always write a value to enable it.
66 |
67 | .DESCRIPTION
68 | Ensures that Remote Desktop access is enabled. It will always write a value to enable it.
69 |
70 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
71 |
72 | .EXAMPLE
73 | Ensure-RemoteDesktopEnabled
74 |
75 | Writes the necessary value to the registry that will enable remote desktop access to
76 | users in the Remote Desktop Users group.
77 | #>
78 | [CmdletBinding()]
79 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
80 | param()
81 |
82 | process
83 | {
84 | Ensure-RegistryKeyProperty -RegistryKey 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Property 'fDenyTSConnections' -Value 0000
85 | }
86 | }
87 |
88 |
--------------------------------------------------------------------------------
/StoreBroker.pssproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | 2.0
6 | 6CAFC0C6-A428-4d30-A9F9-700E829FEA51
7 | Exe
8 | StoreBroker
9 | StoreBroker
10 | StoreBroker
11 |
12 |
13 |
14 |
15 | true
16 | full
17 | false
18 | bin\Debug\
19 | DEBUG;TRACE
20 | prompt
21 | 4
22 |
23 |
24 | pdbonly
25 | true
26 | bin\Release\
27 | TRACE
28 | prompt
29 | 4
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/Tests/PackageTool.Tests.ps1:
--------------------------------------------------------------------------------
1 | # Need to split-path $MyInvocation.MyCommand.Path twice and then add StoreBroker folder
2 | $sbModulePath = Join-Path ($MyInvocation.MyCommand.Path | Split-Path -Parent | Split-Path -Parent) "StoreBroker"
3 |
4 | Import-Module $sbModulePath
5 |
6 | InModuleScope StoreBroker {
7 |
8 | Describe "PackageTool" {
9 |
10 | Context "Remove-Comment" {
11 |
12 | It "does not change text with no comments" {
13 | $in = "This is a test string with no comments."
14 |
15 | $in | Remove-Comment | Should BeExactly $in
16 | }
17 |
18 | It "does not change multiple lines of text with no comments" {
19 | $in = @(
20 | "This is a",
21 | "test string split",
22 | "across multiple lines",
23 | "and with no comments"
24 | )
25 |
26 | $out = $in | Remove-Comment
27 |
28 | # Assert the collection size is the same
29 | $out.Count | Should Be $in.Count
30 |
31 | # Assert the content is the same
32 | foreach ($i in 0..($in.Count - 1))
33 | {
34 | $out[$i] | Should BeExactly $in[$i]
35 | }
36 | }
37 |
38 | It "removes comments from a single line of text" {
39 | $content = "This is valid content"
40 | $comment = "// this is comment content // and so is this"
41 | $in = $content + $comment
42 |
43 | $in | Remove-Comment | Should BeExactly $content
44 | }
45 |
46 | It "removes comments from multiple lines of text" {
47 | $content = @(
48 | "Here is some ",
49 | "example text ",
50 | "that occurs across",
51 | "multiple lines "
52 | )
53 |
54 | $comments = @(
55 | "// None of the text ",
56 | "// in this array",
57 | "//is considered",
58 | "// valid."
59 | )
60 |
61 | $in = @()
62 | foreach ($i in 0..($content.Count - 1))
63 | {
64 | $in += ($content[$i] + $comments[$i])
65 | }
66 |
67 | $out = $in | Remove-Comment
68 |
69 | # Assert the collection size is the same
70 | $out.Count | Should Be $content.Count
71 |
72 | # Assert the content is the same
73 | foreach ($i in 0..($in.Count-1))
74 | {
75 | $out[$i] | Should BeExactly $content[$i]
76 | }
77 | }
78 |
79 | It "removes empty strings and whitespace across multiple lines" {
80 | $in = @(
81 | "",
82 | " ",
83 | " "
84 | )
85 |
86 | $in | Remove-Comment | Should Be $null
87 | }
88 |
89 | It "returns nothing when all lines are comments" {
90 | $in = @(
91 | "// None of the text ",
92 | "// in this array",
93 | "//is considered",
94 | "// valid."
95 | )
96 |
97 | $in | Remove-Comment | Should Be $null
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/TimeZone/Ensure-TimeZone.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | function Get-TimeZone
4 | {
5 | <#
6 | .SYNOPSIS
7 | Gets the ID of the device's current timezone.
8 |
9 | .DESCRIPTION
10 | Gets the ID of the device's current timezone.
11 |
12 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
13 |
14 | .EXAMPLE
15 | Get-TimeZone
16 |
17 | Gets the ID of the device's current timezone.
18 |
19 | .OUTPUTS
20 | System.String - The ID of the timezone
21 | #>
22 | [CmdletBinding()]
23 | [OutputType([String])]
24 | param()
25 |
26 | [System.TimeZoneInfo]::ClearCachedData()
27 | $localTimeZone = [system.timezoneinfo]::Local
28 | $localTimeZone.Id
29 | }
30 |
31 | function Set-TimeZone
32 | {
33 | <#
34 | .SYNOPSIS
35 | Changes the device to use the specified timezone.
36 |
37 | .DESCRIPTION
38 | Changes the device to use the specified timezone.
39 |
40 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
41 |
42 | .PARAMETER TimeZoneId
43 | The ID of the timezone that should be used for this device.
44 |
45 | .EXAMPLE
46 | Set-TimeZone -TimeZoneId 'Pacific Standard Time'
47 |
48 | Sets the device to use the PST timezone.
49 | #>
50 | [CmdletBinding(SupportsShouldProcess)]
51 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", "", Justification="There doesn't appear to be a better way to change the timezone.")]
52 | param(
53 | [Parameter(
54 | Mandatory,
55 | ValueFromPipeline,
56 | Position=0)]
57 | [string] $TimeZoneId
58 | )
59 |
60 | if ($PSCmdlet.ShouldProcess($TimeZoneId, "tzutil.exe /s"))
61 | {
62 | Invoke-Expression "tzutil.exe /s ""$TimeZoneId""" -ErrorAction Stop
63 | }
64 | }
65 |
66 | function Ensure-TimeZone
67 | {
68 | <#
69 | .SYNOPSIS
70 | Updates the device's timezone to the specified timezone if it isn't already
71 | using it.
72 |
73 | .DESCRIPTION
74 | Updates the device's timezone to the specified timezone if it isn't already
75 | using it.
76 |
77 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
78 |
79 | .PARAMETER TimeZoneId
80 | The ID of the timezone that should be used for this device.
81 |
82 | .EXAMPLE
83 | Ensure-TimeZone -TimeZoneId 'Pacific Standard Time'
84 |
85 | Sets the device to use the PST timezone if it's not already in PST.
86 | #>
87 | [CmdletBinding()]
88 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
89 | param(
90 | [Parameter(
91 | Mandatory,
92 | ValueFromPipeline,
93 | Position=0)]
94 | [string] $TimeZoneId
95 | )
96 |
97 | try
98 | {
99 | Write-Verbose "Ensuring that the time zone id is set to ""$TimeZoneId""."
100 | $currentTimeZoneId = Get-TimeZone -ErrorAction Stop;
101 | Write-Verbose "Current time zone id is ""$currentTimeZoneId""."
102 |
103 | if ($currentTimeZoneId -ne $TimeZoneId)
104 | {
105 | Write-Verbose "Time zone ids do not match. Setting time zone to ""$timeZoneId""."
106 | Set-TimeZone $timeZoneId -ErrorAction Stop
107 | Write-Verbose "Successfully set the time zone to ""$timeZoneId""."
108 | }
109 | else
110 | {
111 | Write-Verbose "Current time zone matches expected time zone."
112 | }
113 | }
114 | catch
115 | {
116 | Write-Error "Unable to ensure the time zone is set properly."
117 | throw $_
118 | }
119 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### VisualStudio ###
2 | ## Ignore Visual Studio temporary files, build results, and
3 | ## files generated by popular Visual Studio add-ons.
4 |
5 | # User-specific files
6 | *.suo
7 | *.user
8 | *.userosscache
9 | *.sln.docstates
10 |
11 | # User-specific files (MonoDevelop/Xamarin Studio)
12 | *.userprefs
13 |
14 | # Build results
15 | [Dd]ebug/
16 | [Dd]ebugPublic/
17 | [Rr]elease/
18 | [Rr]eleases/
19 | x64/
20 | x86/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 | [Ll]og/
25 |
26 | # Visual Studio 2015 cache/options directory
27 | .vs/
28 | # Uncomment if you have tasks that create the project's static files in wwwroot
29 | #wwwroot/
30 |
31 | # MSTest test Results
32 | [Tt]est[Rr]esult*/
33 | [Bb]uild[Ll]og.*
34 |
35 | # NUNIT
36 | *.VisualState.xml
37 | TestResult.xml
38 |
39 | # Build Results of an ATL Project
40 | [Dd]ebugPS/
41 | [Rr]eleasePS/
42 | dlldata.c
43 |
44 | # DNX
45 | project.lock.json
46 | project.fragment.lock.json
47 | artifacts/
48 |
49 | *_i.c
50 | *_p.c
51 | *_i.h
52 | *.ilk
53 | *.meta
54 | *.obj
55 | *.pch
56 | *.pdb
57 | *.pgc
58 | *.pgd
59 | *.rsp
60 | *.sbr
61 | *.tlb
62 | *.tli
63 | *.tlh
64 | *.tmp
65 | *.tmp_proj
66 | *.log
67 | *.vspscc
68 | *.vssscc
69 | .builds
70 | *.pidb
71 | *.svclog
72 | *.scc
73 |
74 | # Visual C++ cache files
75 | ipch/
76 | *.aps
77 | *.ncb
78 | *.opendb
79 | *.opensdf
80 | *.sdf
81 | *.cachefile
82 | *.VC.db
83 | *.VC.VC.opendb
84 |
85 | # Visual Studio profiler
86 | *.psess
87 | *.vsp
88 | *.vspx
89 | *.sap
90 |
91 | # TFS 2012 Local Workspace
92 | $tf/
93 |
94 | # Guidance Automation Toolkit
95 | *.gpState
96 |
97 | # ReSharper is a .NET coding add-in
98 | _ReSharper*/
99 | *.[Rr]e[Ss]harper
100 | *.DotSettings.user
101 |
102 | # Web workbench (sass)
103 | .sass-cache/
104 |
105 | # DocProject is a documentation generator add-in
106 | DocProject/buildhelp/
107 | DocProject/Help/*.HxT
108 | DocProject/Help/*.HxC
109 | DocProject/Help/*.hhc
110 | DocProject/Help/*.hhk
111 | DocProject/Help/*.hhp
112 | DocProject/Help/Html2
113 | DocProject/Help/html
114 |
115 | # Click-Once directory
116 | publish/
117 |
118 | # Publish Web Output
119 | *.[Pp]ublish.xml
120 | *.azurePubxml
121 | # TODO: Comment the next line if you want to checkin your web deploy settings
122 | # but database connection strings (with potential passwords) will be unencrypted
123 | *.pubxml
124 | *.publishproj
125 |
126 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
127 | # checkin your Azure Web App publish settings, but sensitive information contained
128 | # in these scripts will be unencrypted
129 | PublishScripts/
130 |
131 | # NuGet Packages
132 | *.nupkg
133 | # The packages folder can be ignored because of Package Restore
134 | **/packages/*
135 | # except build/, which is used as an MSBuild target.
136 | !**/packages/build/
137 | # Uncomment if necessary however generally it will be regenerated when needed
138 | #!**/packages/repositories.config
139 | # NuGet v3's project.json files produces more ignoreable files
140 | *.nuget.props
141 | *.nuget.targets
142 |
143 | # Microsoft Azure Build Output
144 | csx/
145 | *.build.csdef
146 |
147 | # Microsoft Azure Emulator
148 | ecf/
149 | rcf/
150 |
151 | # Windows Store app package directories and files
152 | AppPackages/
153 | BundleArtifacts/
154 | Package.StoreAssociation.xml
155 | _pkginfo.txt
156 |
157 | # Visual Studio cache files
158 | # files ending in .cache can be ignored
159 | *.[Cc]ache
160 | # but keep track of directories ending in .cache
161 | !*.[Cc]ache/
162 |
163 | # Others
164 | ClientBin/
165 | ~$*
166 | *~
167 | *.dbmdl
168 | *.dbproj.schemaview
169 | *.pfx
170 | *.publishsettings
171 | node_modules/
172 | orleans.codegen.cs
173 |
174 | # RIA/Silverlight projects
175 | Generated_Code/
176 |
177 | # Backup & report files from converting an old project file
178 | # to a newer Visual Studio version. Backup files are not needed,
179 | # because we have git ;-)
180 | _UpgradeReport_Files/
181 | Backup*/
182 | UpgradeLog*.XML
183 | UpgradeLog*.htm
184 |
185 | # Node.js Tools for Visual Studio
186 | .ntvs_analysis.dat
187 |
188 | # Visual Studio 6 build log
189 | *.plg
190 |
191 | # Visual Studio 6 workspace options file
192 | *.opt
--------------------------------------------------------------------------------
/PDP/InAppProductDescription.xsd:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | Root element of InAppProductDescription document type. For content guidance on all required and optional fields in the product description, see http://go.microsoft.com/fwlink/p/?linkid=221134.
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Required, the title for the add-on listing. May not exceed 100 characters.
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Optional, the description for the add-on listing. May not exceed 200 characters.
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | Optional, the icon for the listing.
100 | The icon should be a 300 x 300 png file.
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/Helpers/GroupManagement.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | function Ensure-IsInGroup
4 | {
5 | <#
6 | .SYNOPSIS
7 | Ensures the user is in the specified group. If not, it will add them.
8 |
9 | .DESCRIPTION
10 | Ensures the user is in the specified group. If not, it will add them.
11 |
12 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
13 |
14 | .PARAMETER UserName
15 | The user that should be in Group
16 |
17 | .PARAMETER Group
18 | The group that UserName should be a member of.
19 |
20 | .PARAMETER Domain
21 | The domain for UserName (if relevant)
22 |
23 | .EXAMPLE
24 | Ensure-IsInGroup -UserName "myUser" -Group "Administrators"
25 |
26 | Checks to see if the user "myUser" is in the Administrators group. If not, adds them.
27 | #>
28 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
29 | param(
30 | [Parameter(Mandatory)]
31 | [string] $UserName,
32 |
33 | [Parameter(Mandatory)]
34 | [string] $Group,
35 |
36 | [string] $Domain = $null
37 | )
38 |
39 | process
40 | {
41 | Write-Verbose "Ensuring user [$UserName] is a member of the [$Group] group."
42 |
43 | # ADSI == Active Directory Service Interface
44 | $groupAdsi = [ADSI]"WinNT://${env:Computername}/$Group, group"
45 | $members = @($groupAdsi.psbase.Invoke("Members")) | ForEach-Object { ([ADSI]$_).InvokeGet("Name") }
46 |
47 | if ($members -notcontains $UserName)
48 | {
49 | Write-Verbose "User [$UserName] is not a member of the [$Group] group. They will be added."
50 |
51 | try
52 | {
53 | if ([string]::IsNullOrEmpty($Domain))
54 | {
55 | $groupAdsi.Add("WinNT://${env:Computername}/$UserName, user")
56 | }
57 | else
58 | {
59 | $groupAdsi.psbase.Invoke("Add", ([ADSI]"WinNT://$Domain/$UserName").path)
60 | }
61 |
62 | Write-Verbose "User [$UserName] was added to the [$Group] group."
63 | }
64 | catch
65 | {
66 | Write-Error "Unable to add user [$UserName] to the [$Group] group."
67 | throw $_
68 | }
69 | }
70 | else
71 | {
72 | Write-Verbose "User [$UserName] is already a member of the [$Group] group."
73 | }
74 |
75 | }
76 | }
77 |
78 | function Ensure-IsInAdministratorsGroup
79 | {
80 | <#
81 | .SYNOPSIS
82 | Ensures the user is in the Administrators group. If not, it will add them.
83 |
84 | .DESCRIPTION
85 | Ensures the user is in the Administrators group. If not, it will add them.
86 |
87 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
88 |
89 | .PARAMETER UserName
90 | The user that should be in Administrators group
91 |
92 | .PARAMETER Domain
93 | The domain for UserName (if relevant)
94 |
95 | .EXAMPLE
96 | Ensure-IsInAdministratorsGroup "myUser"
97 |
98 | Checks to see if the user "myUser" is in the Administrators group. If not, adds them.
99 | #>
100 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
101 | param(
102 | [Parameter(Mandatory)]
103 | [string] $UserName,
104 |
105 | [string] $Domain = $null
106 | )
107 |
108 | process
109 | {
110 | Ensure-IsInGroup -UserName $UserName -Group 'Administrators' -Domain $Domain
111 | }
112 | }
113 |
114 | function Ensure-IsInRemoteDesktopUsersGroup
115 | {
116 | <#
117 | .SYNOPSIS
118 | Ensures the user is in the Remote Desktop Users group. if not, it will add them.
119 |
120 | .DESCRIPTION
121 | Ensures the user is in the Remote Desktop Users group. if not, it will add them.
122 |
123 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
124 |
125 | .PARAMETER UserName
126 | The user that should be in Remote Desktop Users group
127 |
128 | .PARAMETER Domain
129 | The domain for UserName (if relevant)
130 |
131 | .EXAMPLE
132 | Ensure-IsInRemoteDesktopUsersGroup "myUser"
133 |
134 | Checks to see if the user "myUser" is in the Remote Desktop Users group. If not, adds them.
135 | #>
136 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
137 | param(
138 | [Parameter(Mandatory)]
139 | [string] $UserName,
140 |
141 | [string] $Domain = $null
142 | )
143 |
144 | process
145 | {
146 | Ensure-IsInGroup -UserName $UserName -Group 'Remote Desktop Users' -Domain $Domain
147 | }
148 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StoreBroker PowerShell Module
2 |
3 | [](https://www.powershellgallery.com/packages/StoreBroker)
4 | [](https://www.nuget.org/packages/Microsoft.Windows.StoreBroker/)
5 |
6 | #### Table of Contents
7 |
8 | * [Overview](#overview)
9 | * [Project Status](#project-status)
10 | * [Goals](#goals)
11 | * [Current Functionality](#current-functionality)
12 | * [Prerequisites](#prerequisites)
13 | * [What's New](#whats-new)
14 | * [Installation and Setup](#installation-and-setup)
15 | * [Usage](#usage)
16 | * [Developing and Contributing](#developing-and-contributing)
17 | * [Legal and Licensing](#legal-and-licensing)
18 | * [Governance](#governance)
19 | * [Code of Conduct](#code-of-conduct)
20 | * [Privacy Policy](#privacy-policy)
21 | * [Terms of Use](#terms-of-use)
22 |
23 | ----------
24 |
25 | ## Overview
26 |
27 | StoreBroker is a [PowerShell](https://microsoft.com/powershell) [module](https://technet.microsoft.com/en-us/library/dd901839.aspx)
28 | that provides command-line interaction and automation for Application and In-App Purchase
29 | submissions to the Windows Store via the
30 | [Windows Store Submission API](https://msdn.microsoft.com/windows/uwp/monetize/create-and-manage-submissions-using-windows-store-services).
31 |
32 | It's designed for all Windows developers that publish their apps into the Windows Store.
33 |
34 | Most Microsoft-published applications in the Windows Store have metadata (screenshots, captions,
35 | descriptions, feature lists, etc...) localized across 64 languages; updating all of these values
36 | manually through the [Developer Portal](https://dev.windows.com) can take hours (or days).
37 | StoreBroker is able to complete a full metadata update in a matter of minutes with just two
38 | commands, and that's just the start of what it can do.
39 |
40 | ### Project Status
41 |
42 | **Production**
43 |
44 | This module is **actively used within Microsoft** as the _primary way_ that our first-party
45 | application teams submit flight updates and public submission updates to the Windows Store,
46 | and is now available to the general developer community to leverage as well.
47 |
48 | ### Goals
49 |
50 | **StoreBroker** should ...
51 |
52 | ... have full support for exercising every aspect of the Windows Submission API.
53 |
54 | ... be easy to use, and consistent in its functionality.
55 |
56 | ... save developer time and decrease the friction with getting updates to the Windows Store.
57 |
58 | ### Current Functionality
59 |
60 | At a very high level, this is what you can do with StoreBroker:
61 |
62 | - App Submissions
63 | - Retrieve / Create / Update / Remove
64 |
65 | - Flights
66 | - Retrieve / Create / Update / Remove
67 |
68 | - Flight Submissions
69 | - Retrieve / Create / Update / Remove
70 |
71 | - In-App Products (IAP's)
72 | - Retrieve / Create / Update / Remove
73 |
74 | - In-App Product (IAP) Submissions
75 | - Retrieve / Create / Update / Remove
76 |
77 | - General
78 | - Submission Monitor with email support
79 |
80 | ### Prerequisites
81 |
82 | This module requires PowerShell [version 4](https://en.wikipedia.org/wiki/PowerShell#PowerShell_4.0)
83 | or higher.
84 |
85 | [More prerequisites](Documentation/SETUP.md#prerequisites) are covered in
86 | [SETUP.md](Documentation/SETUP.md#prerequisites).
87 |
88 | ----------
89 |
90 | ## What's New
91 |
92 | Check out [CHANGELOG.md](./CHANGELOG.md) to review the details of the current release as well as
93 | all past releases.
94 |
95 | ----------
96 |
97 | ## Installation and Setup
98 |
99 | Refer to [SETUP.md](Documentation/SETUP.md) for the Installation and Setup instructions.
100 |
101 | ----------
102 |
103 | ## Usage
104 |
105 | Refer to [USAGE.md](Documentation/USAGE.md) for usage information.
106 |
107 | ----------
108 |
109 | ## Developing and Contributing
110 |
111 | Please see the [Contribution Guide](CONTRIBUTING.md) for information on how to develop and
112 | contribute.
113 |
114 | If you have any problems, please consult [GitHub Issues](https://github.com/Microsoft/StoreBroker/issues),
115 | and the [FAQ](Documentation/USAGE.md#faq).
116 |
117 | If you do not see your problem captured, please file [feedback](CONTRIBUTING.md#feedback).
118 |
119 | ----------
120 |
121 | ## Legal and Licensing
122 |
123 | StoreBroker is licensed under the [MIT license](LICENSE).
124 |
125 | -------------------
126 |
127 | ## Governance
128 |
129 | Governance policy for the StoreBroker project is described [here](Documentation/GOVERNANCE.md).
130 |
131 | ----------
132 |
133 | ## Code of Conduct
134 |
135 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
136 | For more information see the [Code of conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
137 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions
138 | or comments.
139 |
140 | ----------
141 |
142 | ## Privacy Policy
143 |
144 | For more information, refer to Microsoft's [Privacy Policy](https://go.microsoft.com/fwlink/?LinkID=521839).
145 |
146 | ----------
147 |
148 | ## Terms of Use
149 |
150 | For more information, refer to Microsoft's [Terms of Use](https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx).
151 |
--------------------------------------------------------------------------------
/AzureService/AzureService.ccproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | 2.9
8 | ec8aa218-b675-4003-b2e0-685bd96f8609
9 | Library
10 | Properties
11 | AzureService
12 | AzureService
13 | True
14 | AzureService
15 | False
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | RESTProxy
43 | {d6fafb1d-3b56-4332-a76f-e132a1891169}
44 | True
45 | Web
46 | RESTProxy
47 | True
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | Content
72 |
73 |
74 | Content
75 |
76 |
77 | Content
78 |
79 |
80 | Content
81 |
82 |
83 | Content
84 |
85 |
86 | Content
87 |
88 |
89 | Content
90 |
91 |
92 | Content
93 |
94 |
95 | Content
96 |
97 |
98 | Content
99 |
100 |
101 | Content
102 |
103 |
104 |
105 |
106 | 10.0
107 | $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Windows Azure Tools\2.9\
108 |
109 |
110 |
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/LocalAdmin/Ensure-LocalAdmin.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | # Load the helper cmdlets
4 | . $PSScriptRoot\..\Helpers\Encryption.ps1
5 | . $PSScriptRoot\..\Helpers\GroupManagement.ps1
6 |
7 | function Ensure-LocalUserExists
8 | {
9 | <#
10 | .SYNOPSIS
11 | Ensures the local user exists. If it doesn't, it will create it.
12 |
13 | .DESCRIPTION
14 | Ensures the local user exists. If it doesn't, it will create it.
15 |
16 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
17 |
18 | .PARAMETER UserName
19 | The name of the local user to ensure exists.
20 |
21 | .PARAMETER Secret
22 | The Base64 encrypted password for UserName.
23 |
24 | .PARAMETER CertThumbprint
25 | The thumbprint (40-digit hex number) of the certificate to use to decode Secret.
26 |
27 | .PARAMETER CertStore
28 | The certificate store that the certificate with CertThumbrint can be found.
29 |
30 | .EXAMPLE
31 | Ensure-LocalUserExists -UserName "myUser" -Secret $encrypted -CertThumbrint 1234567890ABCDEF1234567890ABCDEF12345678
32 |
33 | Will ensure that the current computer has a local user named "myUser" with a password that
34 | is found after decrypting $encrypted using the certificate found with the specified
35 | thumbprint in the local computer certificate store.
36 | #>
37 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
38 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="Exists isn't plural in this instance.")]
39 | param(
40 | [Parameter(Mandatory)]
41 | [string] $UserName,
42 |
43 | [Parameter(Mandatory)]
44 | [string] $Secret,
45 |
46 | [Parameter(Mandatory)]
47 | [string] $CertThumbprint,
48 |
49 | [string] $CertStore = "cert:\LocalMachine\My"
50 | )
51 | process
52 | {
53 | Write-Verbose "Ensuring user [$UserName] exists"
54 |
55 | # ADSI == Active Directory Service Interface
56 | $computerAdsi = [ADSI]"WinNT://${env:Computername}"
57 | $localUsers = $computerAdsi.Children |
58 | Where-Object { $_.SchemaClassName -eq 'user' } |
59 | ForEach-Object { $_.name[0].ToString() }
60 |
61 | if ($localUsers -notcontains $UserName)
62 | {
63 | Write-Verbose "User [$UserName] does not exit. It will be created."
64 |
65 | try
66 | {
67 | Write-Verbose "Decrypting password."
68 | $password = Decrypt-Asymmetric -EncryptedBase64String $Secret -CertThumbprint $CertThumbprint -CertStore $CertStore -ErrorAction Stop
69 |
70 | Write-Verbose "Password decrypted."
71 | $user = $computerAdsi.Create("User", $UserName)
72 | $user.setPassword($password)
73 | $user.SetInfo();
74 | $user.description = "Local Admin created via Cloud Service startup script."
75 | $user.SetInfo();
76 |
77 | Write-Verbose "Created user [$UserName]"
78 | }
79 | catch
80 | {
81 | Write-Error "Unable to create user [$UserName]"
82 | Throw $_
83 | }
84 | }
85 | else
86 | {
87 | Write-Verbose "User [$UserName] already exists."
88 | }
89 | }
90 |
91 | }
92 |
93 | function Ensure-LocalAdmin
94 | {
95 | <#
96 | .SYNOPSIS
97 | Ensures the specified user exists and is an Administrator with RDP access.
98 |
99 | .DESCRIPTION
100 | Ensures the specified user exists and is an Administrator with RDP access.
101 |
102 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
103 |
104 | .PARAMETER UserName
105 | The name of the local user that should be an administrator with RDP access.
106 |
107 | .PARAMETER Secret
108 | The Base64 encrypted password for UserName.
109 |
110 | .PARAMETER CertThumbprint
111 | The thumbprint (40-digit hex number) of the certificate to use to decode Secret.
112 |
113 | .PARAMETER CertStore
114 | The certificate store that the certificate with CertThumbrint can be found.
115 |
116 | .EXAMPLE
117 | Ensure-LocalAdmin -UserName "myUser" -Secret $encrypted -CertThumbrint 1234567890ABCDEF1234567890ABCDEF12345678
118 |
119 | Will ensure that the current computer has a local user named "myUser" with a password that
120 | is found after decrypting $encrypted using the certificate found with the specified
121 | thumbprint in the local computer certificate store. That user will be a member of the
122 | Administrators group and the Remote Desktop Users group.
123 | #>
124 | [CmdletBinding()]
125 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
126 | param(
127 | [Parameter(Mandatory)]
128 | [string] $UserName,
129 |
130 | [Parameter(Mandatory)]
131 | [string] $Secret,
132 |
133 | [Parameter(Mandatory)]
134 | [string] $CertThumbprint,
135 |
136 | [string] $CertStore = "cert:\LocalMachine\My"
137 | )
138 |
139 | process
140 | {
141 | Ensure-LocalUserExists -UserName $UserName -Secret $Secret -CertThumbprint $CertThumbprint -CertStore $CertStore
142 | Ensure-IsInAdministratorsGroup -UserName $UserName
143 | Ensure-IsInRemoteDesktopUsersGroup -UserName $UserName
144 | }
145 | }
--------------------------------------------------------------------------------
/AzureService/ServiceDefinition.csdef:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/StoreBroker/StoreBroker.psd1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | @{
4 | GUID = '10d324f0-4333-4ef7-9e85-93b7fc83f5fb'
5 | Author = 'Microsoft Corporation'
6 | CompanyName = 'Microsoft Corporation'
7 | Copyright = 'Copyright (C) Microsoft Corporation. All rights reserved.'
8 |
9 | ModuleVersion = '1.21.24'
10 | Description = 'Provides command-line access to the Windows Store Submission REST API.'
11 |
12 | RootModule = 'StoreIngestionApi'
13 |
14 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
15 | NestedModules = @(
16 | 'Helpers.ps1',
17 | 'NugetTools.ps1',
18 | 'PackageTool.ps1',
19 | 'StoreIngestionApplicationApi.ps1',
20 | 'StoreIngestionIapApi.ps1',
21 | 'StoreIngestionFlightingApi.ps1',
22 | 'Telemetry.ps1')
23 |
24 | # Minimum version of the Windows PowerShell engine required by this module
25 | PowerShellVersion = '4.0'
26 |
27 | FunctionsToExport = @(
28 | 'Clear-StoreBrokerAuthentication',
29 | 'Complete-ApplicationFlightSubmission',
30 | 'Complete-ApplicationFlightSubmissionPackageRollout',
31 | 'Complete-ApplicationSubmission',
32 | 'Complete-ApplicationSubmissionPackageRollout',
33 | 'Complete-InAppProductSubmission',
34 | 'Format-Application',
35 | 'Format-ApplicationFlight',
36 | 'Format-ApplicationFlightSubmission',
37 | 'Format-ApplicationFlights',
38 | 'Format-ApplicationInAppProducts',
39 | 'Format-ApplicationSubmission',
40 | 'Format-Applications',
41 | 'Format-InAppProduct',
42 | 'Format-InAppProductSubmission',
43 | 'Format-InAppProducts',
44 | 'Get-Application',
45 | 'Get-ApplicationFlight',
46 | 'Get-ApplicationFlightSubmission',
47 | 'Get-ApplicationFlightSubmissionPackageRollout',
48 | 'Get-ApplicationFlightSubmissionStatus',
49 | 'Get-ApplicationFlights',
50 | 'Get-ApplicationInAppProducts',
51 | 'Get-ApplicationSubmission',
52 | 'Get-ApplicationSubmissionPackageRollout',
53 | 'Get-ApplicationSubmissionStatus',
54 | 'Get-Applications',
55 | 'Get-FlightGroups',
56 | 'Get-InAppProduct',
57 | 'Get-InAppProductSubmission',
58 | 'Get-InAppProductSubmissionStatus',
59 | 'Get-InAppProducts',
60 | 'Get-SubmissionPackage',
61 | 'Invoke-SBRestMethod',
62 | 'Invoke-SBRestMethodMultipleResult',
63 | 'Join-SubmissionPackage',
64 | 'New-ApplicationFlight',
65 | 'New-ApplicationFlightSubmission',
66 | 'New-ApplicationSubmission',
67 | 'New-InAppProductSubmission',
68 | 'New-InAppProductSubmissionPackage',
69 | 'New-InAppProduct',
70 | 'New-StoreBrokerConfigFile',
71 | 'New-StoreBrokerInAppProductConfigFile',
72 | 'New-SubmissionPackage',
73 | 'Open-DevPortal',
74 | 'Open-Store',
75 | 'Remove-ApplicationFlight',
76 | 'Remove-ApplicationFlightSubmission',
77 | 'Remove-ApplicationSubmission',
78 | 'Remove-InAppProduct',
79 | 'Remove-InAppProductSubmission',
80 | 'Set-ApplicationFlightSubmission',
81 | 'Set-ApplicationSubmission',
82 | 'Set-InAppProductSubmission',
83 | 'Set-StoreBrokerAuthentication',
84 | 'Set-SubmissionPackage',
85 | 'Start-ApplicationFlightSubmissionMonitor',
86 | 'Start-InAppProductSubmissionMonitor',
87 | 'Start-SubmissionMonitor',
88 | 'Stop-ApplicationFlightSubmissionPackageRollout',
89 | 'Stop-ApplicationSubmissionPackageRollout',
90 | 'Update-ApplicationFlightSubmission',
91 | 'Update-ApplicationFlightSubmissionPackageRollout',
92 | 'Update-ApplicationSubmission',
93 | 'Update-ApplicationSubmissionPackageRollout',
94 | 'Update-InAppProductSubmission')
95 |
96 | AliasesToExport = @(
97 | 'Commit-ApplicationFlightSubmission',
98 | 'Commit-ApplicationSubmission',
99 | 'Commit-IapSubmission',
100 | 'Commit-InAppProductSubmission',
101 | 'Complete-InAppProductSubmission',
102 | 'Finalize-ApplicationFlightSubmissionPackageRollout',
103 | 'Finalize-ApplicationSubmissionPackageRollout',
104 | 'Format-ApplicationIaps',
105 | 'Format-Iap',
106 | 'Format-IapSubmission',
107 | 'Format-Iaps',
108 | 'Get-ApplicationIaps',
109 | 'Get-Iap',
110 | 'Get-IapSubmission',
111 | 'Get-IapSubmissionStatus',
112 | 'Get-Iaps',
113 | 'Halt-ApplicationFlightSubmissionPackageRollout',
114 | 'Halt-ApplicationSubmissionPackageRollout',
115 | 'New-Iap',
116 | 'New-IapSubmission',
117 | 'New-IapSubmissionPackage',
118 | 'New-PackageToolConfigFile',
119 | 'New-StoreBrokerIapConfigFile',
120 | 'Remove-Iap',
121 | 'Remove-IapSubmission',
122 | 'Replace-ApplicationFlightSubmission',
123 | 'Replace-ApplicationSubmission',
124 | 'Replace-IapSubmission',
125 | 'Replace-InAppProductSubmission',
126 | 'Set-IapSubmission',
127 | 'Set-SubmissionPackage',
128 | 'Start-ApplicationSubmissionMonitor',
129 | 'Start-IapSubmissionMonitor',
130 | 'Update-IapSubmission',
131 | 'Upload-ApplicationSubmissionPackage',
132 | 'Upload-SubmissionPackage')
133 |
134 | #CmdletsToExport = '*'
135 |
136 | #VariablesToExport = '*'
137 |
138 | # Private data to pass to the module specified in RootModule/ModuleToProcess.
139 | PrivateData = @{
140 | # Hashtable with additional module metadata used by PowerShell.
141 | PSData = @{
142 | # Tags applied to this module. These help with module discovery in online galleries.
143 | Tags = @('Store', 'App', 'Submission')
144 |
145 | # A URL to the license for this module.
146 | LicenseUri = 'https://aka.ms/StoreBroker_License'
147 |
148 | # A URL to the main website for this project.
149 | ProjectUri = 'https://aka.ms/StoreBroker'
150 |
151 | # A URL to an icon representing this module.
152 | # IconUri = ''
153 |
154 | # ReleaseNotes of this module
155 | # ReleaseNotes = ''
156 | }
157 | }
158 |
159 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
160 | # DefaultCommandPrefix = 'SB'
161 |
162 | # Modules that must be imported into the global environment prior to importing this module
163 | # RequiredModules = @()
164 |
165 | # Assemblies that must be loaded prior to importing this module
166 | # RequiredAssemblies = @()
167 |
168 | # Script files (.ps1) that are run in the caller's environment prior to importing this module.
169 | # ScriptsToProcess = @()
170 |
171 | # List of all modules packaged with this module
172 | # ModuleList = @()
173 |
174 | # List of all files packaged with this module
175 | # FileList = @()
176 |
177 | # HelpInfo URI of this module
178 | # HelpInfoURI = ''
179 | }
180 |
--------------------------------------------------------------------------------
/Documentation/GOVERNANCE.md:
--------------------------------------------------------------------------------
1 | # StoreBroker PowerShell Module
2 | ## Governance
3 |
4 | ## Terms
5 |
6 | * [**StoreBroker Committee**](#storebroker-committee): A committee of project owners who are
7 | responsible for design decisions, and approving new maintainers/committee members.
8 |
9 | * **Project Leads**: Project Leads support the StoreBroker Committee, engineering teams, and
10 | community by working across Microsoft teams and leadership, and working through issues with
11 | other companies. The initial Project Leads for StoreBroker are
12 | Howard Wolosky ([HowardWolosky-MSFT](https://github.com/HowardWolosky-MSFT)) and
13 | Daniel Belcher ([DanBelcher-MSFT](https://github.com/DanBelcher-MSFT)).
14 |
15 | * [**Repository maintainer**](#repository-maintainers): An individual responsible for merging
16 | pull requests (PRs) into `master` when all requirements are met (code review, tests, docs,
17 | as applicable). Repository Maintainers are the only people with write permissions into `master`.
18 |
19 | * [**Area experts**](#area-experts): People who are experts for specific components or
20 | technologies (e.g. security, performance). Area experts are responsible for code reviews,
21 | issue triage, and providing their expertise to others.
22 |
23 | * **Corporation**: The Corporation owns the StoreBroker repository and, under extreme circumstances,
24 | reserves the right to dissolve or reform the StoreBroker Committee, the Project Leads, and the
25 | Corporate Maintainer. The Corporation for StoreBroker is Microsoft.
26 |
27 | * **Corporate Maintainer**: The Corporate Maintainer is an entity, person or set of persons, with
28 | the ability to veto decisions made by the StoreBroker Committee or any other collaborators on the
29 | StoreBroker project. This veto power will be used with restraint since it is intended that the
30 | community drive the project. The Corporate Maintainer is determined by the Corporation both
31 | initially and in continuation. The initial Corporate Maintainer for StoreBroker is
32 | Howard Wolosky ([HowardWolosky-MSFT](https://github.com/HowardWolosky-MSFT)).
33 |
34 | ## StoreBroker Committee
35 |
36 | The StoreBroker Committee and its members (aka Committee Members) are the primary caretakers of
37 | StoreBroker.
38 |
39 | ### Current Committee Members
40 |
41 | * Howard Wolosky ([@HowardWolosky](https://github.com/HowardWolosky))
42 | * Daniel Belcher ([@DanBelcher-MSFT](https://github.com/DanBelcher-MSFT)).
43 |
44 | ### Committee Member Responsibilities
45 |
46 | Committee Members are responsible for reviewing and approving proposed new features or design changes.
47 |
48 | #### Committee Member DOs and DON'Ts
49 |
50 | As a StoreBroker Committee Member:
51 |
52 | 1. **DO** reply to issues and pull requests with design opinions
53 | (this could include offering support for good work or exciting new features)
54 | 2. **DO** encourage healthy discussion about the direction of StoreBroker
55 | 3. **DO** raise "red flags" on PRs that require a larger design discussion
56 | 4. **DO** contribute to documentation and best practices
57 | 5. **DO** maintain a presence in the StoreBroker community outside of GitHub
58 | (Twitter, blogs, StackOverflow, Reddit, Hacker News, etc.)
59 | 6. **DO** heavily incorporate community feedback into the weight of your decisions
60 | 7. **DO** be polite and respectful to a wide variety of opinions and perspectives
61 | 8. **DO** make sure contributors are following the [contributor guidelines](../CONTRIBUTING.md)
62 | 9. **DON'T** constantly raise "red flags" for unimportant or minor problems to the point that the
63 | progress of the project is being slowed
64 | 10. **DON'T** offer up your opinions as the absolute opinion of the StoreBroker Committee.
65 | Members are encouraged to share their opinions, but they should be presented as such.
66 |
67 | ### StoreBroker Committee Membership
68 |
69 | The initial StoreBroker Committee consists of Microsoft employees.
70 | It is expected that over time, StoreBroker experts in the community will be made Committee Members.
71 | Membership is heavily dependent on the level of contribution and expertise:
72 | individuals who contribute in meaningful ways to the project will be recognized accordingly.
73 |
74 | At any point in time, a Committee Member can nominate a strong community member to join the Committee.
75 | Nominations should be submitted in the form of an `Issue` with the `committee nomination` label
76 | detailing why that individual is qualified and how they will contribute. After the `Issue` has
77 | been discussed, a unanimous vote will be required for the new Committee Member to be confirmed.
78 |
79 | ## Repository Maintainers
80 |
81 | Repository Maintainers are trusted stewards of the StoreBroker repository responsible for
82 | maintaining consistency and quality of the code.
83 |
84 | One of their primary responsibilities is merging pull requests after all requirements have been
85 | fulfilled.
86 |
87 | ## Area Experts
88 |
89 | Area Experts are people with knowledge of specific components or technologies in the StoreBroker
90 | domain. They are responsible for code reviews, issue triage, and providing their expertise to others.
91 |
92 | They have [write access](https://help.github.com/articles/repository-permission-levels-for-an-organization/)
93 | to the StoreBroker repository which gives them the power to:
94 |
95 | 1. `git push` to all branches *except* `master`.
96 | 2. Merge pull requests to all branches *except* `master` (though this should not be common
97 | given that `master` is the only long-living branch.
98 | 3. Assign labels, milestones, and people to [issues](https://guides.github.com/features/issues/).
99 |
100 | ### Area Expert Responsibilities
101 |
102 | If you are an Area Expert, you are expected to be actively involved in any development, design, or contributions in your area of expertise.
103 |
104 | If you are an Area Expert:
105 |
106 | 1. **DO** assign the correct labels
107 | 2. **DO** assign yourself to issues labeled with your area of expertise
108 | 3. **DO** code reviews for issues where you're assigned or in your areas of expertise.
109 | 4. **DO** reply to new issues and pull requests that are related to your area of expertise
110 | (while reviewing PRs, leave your comment even if everything looks good - a simple
111 | "Looks good to me" or "LGTM" will suffice, so that we know someone has already taken a look at it).
112 | 5. **DO** make sure contributors are following the [contributor guidelines](../CONTRIBUTING.md).
113 | 6. **DO** ask people to resend a pull request, if it doesn't target `master`.
114 | 7. **DO** ensure that contributors [write Pester tests](../CONTRIBUTING.md#testing) for all
115 | new/changed functionality when possible.
116 | 8. **DO** ensure that contributors write documentation for all new-/changed functionality
117 | 9. **DO** encourage contributors to refer to issues in their pull request description
118 | (e.g. `Resolves issue #123`).
119 | 10. **DO** encourage contributors to create meaningful titles for all PRs. Edit title if necessary.
120 | 11. **DO** verify that all contributors are following the [Coding Guidelines](../CONTRIBUTING.md#coding-guidelines).
121 | 12. **DON'T** create new features, new designs, or change behaviors without having a public
122 | discussion on the design within its Issue.
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/Helpers/Encryption.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | Function Encrypt-Asymmetric
4 | {
5 | <#
6 | .SYNOPSIS
7 | Encrypts a string using the public key on a certificate.
8 |
9 | .DESCRIPTION
10 | Encrypts a string using the public key on a certificate.
11 |
12 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
13 |
14 | .PARAMETER ClearText
15 | The text to encrypt.
16 |
17 | .PARAMETER PublicCertFilePath
18 | The path to the certificate file that has the public key to use for the encyption process.
19 |
20 | .EXAMPLE
21 | $encryptedText = Encrypt-Asymmetric "hello world" c:\certs\mycert.cer
22 |
23 | $encryptedText will contain the base64 encoded, encrypted version of
24 | the string "hello world", using the public key that is defined in the
25 | c:\certs\mycert.cer certificate.
26 |
27 | .OUTPUTS
28 | System.String - The encrypted string using Base64 encoding.
29 | #>
30 | [CmdletBinding()]
31 | [OutputType([string])]
32 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
33 | param(
34 | [Parameter(
35 | Mandatory=$true,
36 | Position=0)]
37 | [ValidateNotNullOrEmpty()]
38 | [string] $ClearText,
39 |
40 | [Parameter(
41 | Mandatory=$true,
42 | Position=1)]
43 | [ValidateNotNullOrEmpty()]
44 | [ValidateScript({Test-Path $_ -PathType Leaf})]
45 | [string] $PublicCertFilePath
46 | )
47 |
48 | # Encrypts a string with a public key
49 | $PublicCertFilePath = Resolve-Path -Path $PublicCertFilePath
50 | $publicCert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $PublicCertFilePath
51 | $byteArray = [System.Text.Encoding]::UTF8.GetBytes($ClearText)
52 | $encryptedByteArray = $publicCert.PublicKey.Key.Encrypt($byteArray,$true)
53 | $base64String = [Convert]::ToBase64String($encryptedByteArray)
54 |
55 | return $base64String
56 | }
57 |
58 |
59 | Function Decrypt-Asymmetric
60 | {
61 | <#
62 | .SYNOPSIS
63 | Decrypts a string using the private key of a certificate.
64 |
65 | .DESCRIPTION
66 | Decrypts a string using the private key of a certificate.
67 |
68 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
69 |
70 | .PARAMETER EncryptedBase64String
71 | The encrypted string to decrypt.
72 |
73 | .PARAMETER CertThumbprint
74 | The thumbprint (40-digit hex number) of the certificate to use.
75 | Used to find the correct certificate in the certificate store.
76 |
77 | .PARAMETER CertStore
78 | The certificate store to search for a certificate matching CertThumbprint
79 | to use for the decryption of EncryptedBase64String.
80 |
81 | .PARAMETER PrivateCertPath
82 | The path to the private certificate to use for decryption, to be used instead of searching
83 | for the certificate by thumbprint in a given certificate store.
84 |
85 | .PARAMETER PrivateCertPassword
86 | The password for the certificate defined by PrivateCertPath so that the decryption process
87 | can occur.
88 |
89 | .EXAMPLE
90 | $clearText = Decrypt-Asymmetric -EncryptedBase64String $encryptedText -CertThumbprint 1234567890ABCDEF1234567890ABCDEF12345678 -CertStore "cert:\LocalMachine\My"
91 |
92 | $clearText will contain the decrypted version of $decryptedText, by looking up the
93 | certificate with the specified thumbrint in the local computer's certificate store for
94 | the private key.
95 |
96 | .EXAMPLE
97 | $clearText = Decrypt-Asymmetric -EncryptedBase64String $encryptedText -PrivateCertPath "c:\cert\MyCert.pfx" -PrivateCertPassword "myPa$$word"
98 |
99 | $clearText will contain the decrypted version of $decryptedText, by applying the
100 | specified password to the specified certiticate for decryption to occur.
101 |
102 | .OUTPUTS
103 | System.String - The encrypted string using Base64 encoding.
104 | #>
105 | [CmdletBinding()]
106 | [OutputType([string])]
107 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
108 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="This is the explicit purpose of this method")]
109 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "", Justification="Caller would be dealing in a plain text password in this scenario.")]
110 | param(
111 | [Parameter(
112 | ParameterSetName='store',
113 | Position=0,
114 | Mandatory=$true)]
115 | [Parameter(
116 | ParameterSetName='file',
117 | Position=0,
118 | Mandatory=$true)]
119 | [ValidateNotNullOrEmpty()]
120 | [string] $EncryptedBase64String,
121 |
122 | [Parameter(
123 | ParameterSetName='store',
124 | Position=1,
125 | Mandatory=$true)]
126 | [ValidateNotNullOrEmpty()]
127 | [string] $CertThumbprint,
128 |
129 | [Parameter(
130 | ParameterSetName='store',
131 | Position=2,
132 | Mandatory=$false)]
133 | [ValidateNotNullOrEmpty()]
134 | [string] $CertStore = "cert:\LocalMachine\My",
135 |
136 | [Parameter(
137 | ParameterSetName='file',
138 | Position=1,
139 | Mandatory=$true)]
140 | [ValidateNotNullOrEmpty()]
141 | [string] $PrivateCertPath,
142 |
143 | [Parameter(
144 | ParameterSetName='file',
145 | Position=2,
146 | Mandatory=$true)]
147 | [ValidateNotNullOrEmpty()]
148 | [string] $PrivateCertPassword
149 | )
150 |
151 | # Decrypts cipher text using the private key
152 | # Assumes the certificate is in the LocalMachine\My (Personal) Store
153 | switch ($PSCmdlet.ParameterSetName)
154 | {
155 | 'store'
156 | {
157 | $cert = Get-ChildItem -Path $CertStore | Where-Object { $_.Thumbprint -eq $CertThumbprint }
158 | }
159 |
160 | 'file'
161 | {
162 | $PrivateCertPath = Resolve-Path -Path $PrivateCertPath
163 | $privateCertSecurePassword = $PrivateCertPassword | ConvertTo-SecureString -AsPlainText -Force
164 | $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList ($PrivateCertPath, $privateCertSecurePassword)
165 | }
166 | }
167 |
168 | if ($null -ne $cert)
169 | {
170 | $encryptedByteArray = [Convert]::FromBase64String($EncryptedBase64String)
171 | $clearText = [System.Text.Encoding]::UTF8.GetString(
172 | $cert.PrivateKey.Decrypt(
173 | $encryptedByteArray,
174 | $true))
175 | }
176 | else
177 | {
178 | switch ($PSCmdlet.ParameterSetName)
179 | {
180 | 'store'
181 | {
182 | Write-Error "Certificate with thumbprint `"$CertThumbprint`" in store `"$CertStore`" not found."
183 | }
184 |
185 | 'file'
186 | {
187 | Write-Error "Certificate file `"$PrivateCertPath`" not found."
188 | }
189 | }
190 |
191 | }
192 |
193 | return $clearText
194 | }
--------------------------------------------------------------------------------
/AzureService/RESTProxyContent/bin/StartupTasks/DomainJoined/Ensure-DomainJoined.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | # Load the helper cmdlets
4 | . $PSScriptRoot\..\Helpers\Encryption.ps1
5 | . $PSScriptRoot\..\Helpers\GroupManagement.ps1
6 |
7 | function Get-JoinedDomain
8 | {
9 | <#
10 | .SYNOPSIS
11 | Gets the domain that the computer is currently joined to.
12 |
13 | .DESCRIPTION
14 | Gets the domain that the computer is currently joined to.
15 |
16 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
17 |
18 | .PARAMETER Computer
19 | The name of the computer to check the domain join status of.
20 |
21 | .EXAMPLE
22 | Get-JoinedDomain "MyComputerName"
23 |
24 | Returns back the computer name and its joined domain if the computer is joined
25 | to a computer, otherwise throws an exception.
26 |
27 | .OUTPUTS
28 | PSCustomObject - An object with a "Name" and "Domain" property for the computer and its
29 | domain.
30 | #>
31 | param (
32 | [parameter(
33 | Position=0,
34 | ValueFromPipeline,
35 | ValueFromPipelineByPropertyName)]
36 | [string] $Computer = "."
37 | )
38 |
39 | process
40 | {
41 | Get-CimInstance -Class Win32_ComputerSystem -ComputerName $Computer -ErrorAction Stop |
42 | Select-Object Name, Domain
43 | }
44 | }
45 |
46 | function Ensure-ComputerDomainJoined
47 | {
48 | <#
49 | .SYNOPSIS
50 | Ensures the computer is domain joined. If not, it will join the specified domain.
51 |
52 | .DESCRIPTION
53 | Ensures the computer is domain joined. If not, it will join the specified domain.
54 |
55 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
56 |
57 | .PARAMETER Domain
58 | The name of the domain that the current to check the domain join status of.
59 |
60 | .PARAMETER UserName
61 | The usename for the user that will be authenticating the request to join the domain.
62 |
63 | .PARAMETER Secret
64 | The Base64 encrypted password for UserName.
65 |
66 | .PARAMETER CertThumbprint
67 | The thumbprint (40-digit hex number) of the certificate to use to decode Secret.
68 |
69 | .PARAMETER CertStore
70 | The certificate store that the certificate with CertThumbrint can be found.
71 |
72 | .PARAMETER Computer
73 | The computer that should be joined to Domain.
74 |
75 | .EXAMPLE
76 | Ensure-ComputerDomainJoined -Domain "foo" -UserName "sampleUser" -Secret $encrypted -CertThumbrint 1234567890ABCDEF1234567890ABCDEF12345678
77 |
78 | Will ensure that the current computer is joined to the foo domain. If it's not,
79 | it will join the domain by authenticating with "sampleUser" and use the password that
80 | is found after decrypting $encrypted using the certificate found with the specified
81 | thumbprint in the local computer certificate store.
82 | #>
83 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
84 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="We're getting the password in cleartext")]
85 | param(
86 | [Parameter(Mandatory)]
87 | [string] $Domain,
88 |
89 | [Parameter(Mandatory)]
90 | [string] $UserName,
91 |
92 | [Parameter(Mandatory)]
93 | [string] $Secret,
94 |
95 | [Parameter(Mandatory)]
96 | [string] $CertThumbprint,
97 |
98 | [string] $CertStore = "cert:\LocalMachine\My",
99 |
100 | [string] $Computer = "."
101 | )
102 |
103 | process
104 | {
105 | Write-Verbose "Ensuring computer is joined the [$Domain] domain"
106 |
107 | try
108 | {
109 | $domainInfo = Get-JoinedDomain
110 | }
111 | catch
112 | {
113 | Write-Error "Unable get computer information."
114 | throw $_
115 | }
116 |
117 | if ($domainInfo.Domain -ne $Domain)
118 | {
119 | Write-Verbose "Computer is not part of the specified domain. It will now be joined to the [$Domain] domain."
120 |
121 | try
122 | {
123 | Set-DnsClient -InterfaceAlias "Ethernet*" -ConnectionSpecificSuffix $Domain
124 |
125 | Write-Verbose "Decrypting password."
126 | $securePassword = Decrypt-Asymmetric -EncryptedBase64String $Secret -CertThumbprint $CertThumbprint -CertStore $CertStore -ErrorAction Stop |
127 | ConvertTo-SecureString -AsPlainText -Force -ErrorAction Stop
128 |
129 | Write-Verbose "Password decrypted."
130 | $cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList("$Domain\$UserName", $securePassword)
131 |
132 | # Specifically not using the -Restart switch because that causes problems with
133 | # the rest of the service deployment. Restarting doesn't actually appear to be
134 | # necessary anyway. In the event that we really did have to restart here,
135 | # we could always just do something like 'shutdown -r -t 90' to allow some
136 | # time for the service to finish deploying before the VM was restarted.
137 | Add-Computer -DomainName $Domain -Credential $cred -Force -ErrorAction Stop
138 | }
139 | catch
140 | {
141 | Write-Error "Unable to add computer to the [$Domain] domain."
142 | throw $_
143 | }
144 | }
145 | else
146 | {
147 | Write-Verbose "Computer is already joined to the [$Domain] domain. Nothing to do."
148 | }
149 | }
150 | }
151 |
152 | function Ensure-DomainJoined
153 | {
154 | <#
155 | .SYNOPSIS
156 | Ensures the computer is domain joined. If not, it will join the specified domain.
157 |
158 | .DESCRIPTION
159 | Ensures the computer is domain joined. If not, it will join the specified domain.
160 |
161 | Additionally ensures that the specified user is an Administrator and a Remote Desktop user.
162 |
163 | The Git repo for this cmdlet can be found here: http://aka.ms/StoreBroker
164 |
165 | .PARAMETER Domain
166 | The name of the domain that the current to check the domain join status of.
167 |
168 | .PARAMETER UserName
169 | The usename for the user that will be authenticating the request to join the domain.
170 |
171 | .PARAMETER Secret
172 | The Base64 encrypted password for UserName.
173 |
174 | .PARAMETER CertThumbprint
175 | The thumbprint (40-digit hex number) of the certificate to use to decode Secret.
176 |
177 | .PARAMETER CertStore
178 | The certificate store that the certificate with CertThumbrint can be found.
179 |
180 | .PARAMETER Computer
181 | The computer that should be joined to Domain.
182 |
183 | .EXAMPLE
184 | Ensure-DomainJoined -Domain "foo" -UserName "sampleUser" -Secret $encrypted -CertThumbrint 1234567890ABCDEF1234567890ABCDEF12345678
185 |
186 | Will ensure that the current computer is joined to the foo domain. If it's not,
187 | it will join the domain by authenticating with "sampleUser" and use the password that
188 | is found after decrypting $encrypted using the certificate found with the specified
189 | thumbprint in the local computer certificate store.
190 | #>
191 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
192 | param(
193 | [Parameter(Mandatory)]
194 | [string] $Domain,
195 |
196 | [Parameter(Mandatory)]
197 | [string] $UserName,
198 |
199 | [Parameter(Mandatory)]
200 | [string] $Secret,
201 |
202 | [Parameter(Mandatory)]
203 | [string] $CertThumbprint,
204 |
205 | [string] $CertStore = "cert:\LocalMachine\My",
206 |
207 | [string] $Computer = "."
208 | )
209 |
210 | Ensure-ComputerDomainJoined -Domain $Domain -UserName $UserName -Secret $Secret -CertThumbprint $CertThumbprint -CertStore $CertStore -Computer $Computer
211 | Ensure-IsInAdministratorsGroup -UserName $UserName -Domain $Domain
212 | Ensure-IsInRemoteDesktopUsersGroup -UserName $UserName -Domain $Domain
213 | }
--------------------------------------------------------------------------------
/PDP/ProductDescription.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 | My Localized AppName
11 |
12 |
13 |
14 |
15 | Video edit
16 | Captions
17 | Trim
18 | Soundtrack
19 | Video sharing
20 |
21 |
22 |
23 |
24 | Turn any video into a memorable moment you'll love to share. With this app, you can trim your video to your favorite parts, highlight key moments with captions and effects, and set the mood with music.
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Various performance optimiazations, this app is wicked-fast now.
55 |
56 |
57 |
58 |
59 |
60 |
66 | Video editing is easy
67 |
73 | Add matching music to your video
74 |
76 | Add text to your video
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | This is Trailer 1's title.
92 |
93 | Description of trailer 1's screenshot that no user will ever see.
94 |
95 |
96 |
97 |
98 |
99 |
100 | Pick your favorite 60 seconds. You can trim from the beginning, middle, or end.
101 | Highlight your favorite moments with fun, colorful captions in a range of styles.
102 | Add music that matches the moment. We've included a few songs for different moods, or you can use songs from your collection.
103 | When your movie is exactly how you want it, share it with family and friends right from the app.
104 |
105 |
106 |
107 |
108 | Computer
109 | Keyboard
110 |
111 |
112 |
113 |
114 | Zune music device
115 | Windows PC
116 |
117 |
118 |
119 |
120 | (c) Contoso LLC.
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | http://www.contoso.com/App
130 |
131 |
132 |
133 |
134 | support@contoso.com
135 |
136 |
137 |
138 |
139 | http://www.contoso.com/App/privacy
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/StoreBroker/IapConfigTemplate.json:
--------------------------------------------------------------------------------
1 | // Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | // This configuration file is for use with the StoreBroker PowerShell module.
4 | // Configure details of the In-App Product ("add on") here, as well as the location of resources
5 | // used by the packaging tool.
6 |
7 | // Inline Comments can be placed in this file using the '//' delimiter.
8 | // The delimiter and any remaining text on that line will be removed.
9 |
10 | {
11 | // New-InAppProductSubmissionPackage parameters
12 | //
13 | // If you notice one of the parameters you specify to New-InAppProductSubmissionPackage does not change, you can specify it here
14 | // in order to avoid providing it at runtime. If New-InAppProductSubmissionPackage does not have a parameter it needs, it will
15 | // check the provided config file for a non-null, non-empty value. The exception to this is -ConfigPath which must
16 | // always be provided. For information on these parameters see the README.md or run `help New-InAppProductSubmissionPackage -ShowWindow`
17 | //
18 | // WARNING: Specifying a parameter at runtime will ignore any value placed in the config file.
19 |
20 | "packageParameters": {
21 |
22 | // There are two supported layouts for your PDP files:
23 | // 1. \\...\PDP.xml
24 | // 2. \\\...\PDP.xml
25 | // The only difference between these two is that there is a directory after the
26 | // and before the sub-directories.
27 | //
28 | // The first layout is generally used when your localization system will be downloading
29 | // the localized PDP files during your build. In that situation, it's always retrieving
30 | // the latest version. Alternatively, if the latest localized versions of your PDP
31 | // files are always stored in the same location, this layout is also for you.
32 | //
33 | // On the other hand, if you will be archiving the localized PDP's based on each release
34 | // to the Store, then the second layout is the one that you should use. In this scenario,
35 | // you will specify the value of "" immediately below, or at the commandline.
36 | //
37 | // Only specify 'PDPRootPath' here if it does not change.
38 | // DO NOT use environment variables in the config. If you need to use an environment
39 | // variable to get to the right path, then you need to specify that path at the commandline
40 | // instead.
41 | //
42 | // File separator '\' must be escaped, i.e. '\\'
43 | // Ex: \\\\filehare\\Public\\AppPDPs\\MyApp
44 |
45 | "PDPRootPath": "",
46 |
47 | // If your PDP files are being placed in a directory structure like this:
48 | // \\\...\PDP.xml
49 | // then specify the correct 'Release' here or at runtime.
50 |
51 | "Release": "",
52 |
53 | // Filenames that SHOULD be processed.
54 | // Wildcards are allowed, eg "InAppProductDescription*.xml".
55 | // It is fine to specify both "PDPInclude" and "PDPExclude".
56 | //
57 | // Ex: "PDPInclude": [
58 | // "IapPDP*.xml", <- Comma to separate items
59 | // "InAppProductDescription.xml" <- No comma for last item, or JSON deserialization will fail
60 | // ]
61 |
62 | "PDPInclude": [
63 | ],
64 |
65 | // Filenames that SHOULD NOT be processed.
66 | // Wildcards are allowed, eg "InAppProductDescription*.xml".
67 | // It is fine to specify both "PDPInclude" and "PDPExclude".
68 | // It is NOT necessary to specify something here, but you can to be more explicit.
69 | //
70 | // Ex: "PDPExclude": [
71 | // "*.xml.lss", <- Comma to separate items
72 | // "*.xml.lct" <- No comma for last item, or JSON deserialization will fail
73 | // ]
74 |
75 | "PDPExclude": [
76 | ],
77 |
78 | // Languages to be excluded from processing by New-InAppProductSubmissionPackage.
79 | // New-InAppProductSubmissionPackage will use the filepath of a PDP XML file
80 | // to identify the language of the metadata. If it sees a match
81 | // with a lang-code in this list, it will skip processing the file.
82 | //
83 | // Ex: "LanguageExclude": [
84 | // "default", <- Comma to separate items
85 | // "qps-ploc" <- No comma for last item, or JSON deserialization will fail
86 | // ],
87 |
88 | "LanguageExclude": [
89 | "default"
90 | ],
91 |
92 | // Your store screenshots must be placed with this structure:
93 | // \\\...\img.png
94 | // Specify 'ImagesRootPath' here.
95 | //
96 | // IMPORTANT: The 'Release' that will be used here is *NOT* the value specified to
97 | // New-InAppProductSubmissionPackage (nor is it the Release value specified earlier in this config file),
98 | // but rather the 'Release' attribute at the top of the corresponding PDP file.
99 | //
100 | // File separator '\' must be escaped, i.e. '\\'
101 | // Ex: \\\\fileshare\\Public\\Screenshots\\MyApp
102 |
103 | "ImagesRootPath": "",
104 |
105 | // Some apps may not localize all of their metadata media (images, trailers, etc..) across all languages.
106 | // By default, StoreBroker will look in the PDP langcode's subfolder within ImagesRootPath for that
107 | // language's media content. If the requested filename is not found, StoreBroker packaging will fail.
108 | // If you specify a fallback language here (e.g. 'en-us'), then if the requested file isn't found in
109 | // the PDP language's media subfolder, StoreBroker will then look into the fallback language's media
110 | // subfolder for the exactly same-named image before failing.
111 |
112 | "MediaFallbackLanguage": "",
113 |
114 | // Full path to a directory where the Packaging Tool can write the .json submission request
115 | // body and .zip package to upload.
116 | //
117 | // File separator '\' must be escaped, i.e. '\\'
118 | // Ex: "OutPath": "C:\\app\\output"
119 |
120 | "OutPath": "",
121 |
122 | // Common name to give to the .json and .zip files outputted by the Packaging Tool.
123 |
124 | "OutName": ""
125 | },
126 |
127 | // Configure details of the IAP Submission
128 |
129 | "iapSubmission": {
130 |
131 | // If known, this is the IapId given to your in-app application ("add on") by the Windows Store.
132 | // This isn't required, but adding this will help protect you from submitting something
133 | // to the wrong product when working at the commandline.
134 | //
135 | // Ex: "iapId": "0ABCDEF12345"
136 | "iapId": "",
137 |
138 | /////////////////////////////////
139 | // //
140 | // PUBLISH MODE AND VISIBILITY //
141 | // //
142 | /////////////////////////////////
143 |
144 | // Publish mode of the submission. One of ["NotSet", "Immediate", "Manual", "SpecificDate"]
145 |
146 | "targetPublishMode": "NotSet",
147 |
148 | // Publish date-time of the submission if the "targetPublishMode" is specified as "SpecificDate".
149 | // Date-time format should follow the ISO 8601 standard.
150 | // Ex: "2000-01-30T00:00:00-08:00"
151 |
152 | "targetPublishDate": null,
153 |
154 | // Visibility of the add-on. One of ["NotSet", "Public", "Private", "Hidden"]
155 | //
156 | // Public: Anyone can find your add-on in the Store.
157 | // Private: Hide this add-on in the Store. Customers with a direct link to the add-on's listing can still download
158 | // it, except on Windows 8 and Windows 8.1.
159 | // Hidden: Hide this add-on and prevent acquisition. Customers with a promotional code can still download it on
160 | // Windows 10 devices.
161 |
162 | "visibility": "NotSet",
163 |
164 | ///////////////////////
165 | // //
166 | // PROPERTIES //
167 | // //
168 | ///////////////////////
169 |
170 | // lifetime
171 | // one of:
172 | // ["Forever", "OneDay", "ThreeDays", "FiveDays", "OneWeek", "TwoWeeks", "OneMonth", "TwoMonths", "ThreeMonths", "SixMonths", "OneYear"]
173 |
174 | "lifetime": "Forever",
175 |
176 | // contentType - Regardless of your add-on's product type, you'll also need to indicate the
177 | // type of content you're offering. For most add-ons, the content type should be "OnlineDownload".
178 | // If another option from the list seems to describe your add-on better (for example, if you
179 | // are offering a music download or an e-book), select that option instead.
180 | //
181 | // one of:
182 | // ["NotSet", "BookDownload", "EMagazine", "ENewspaper", "MusicDownload", "MusicStream",
183 | // "OnlineDataStorage", "VideoDownload", "VideoStream", "Asp", "OnlineDownload"]
184 |
185 | "contentType": "NotSet",
186 |
187 | // Your app can then query for add-ons that match these words.
188 | // This feature lets you build screens in your app that can load add-ons without you having to
189 | // directly specify the product ID in your app's code. You can then change the add-on's keywords
190 | // anytime, without having to make code changes in your app or submit the app again.
191 | // Maximum: 10 keywords of up to 30 characters each
192 | "keywords": [
193 | ],
194 |
195 | // The custom developer data for the add-on (this information was previously called the 'tag').
196 | // You can use this to provide extra context for your in-app product.
197 | // Your app can query this value dynamically, and you can adjust the value at any time by
198 | // updating the info in the add-on's Custom developer data field, without having to make code
199 | // changes in your app or submit the app again.
200 | // Maximum: 3000 characters
201 | // The contents must be provided on one line, but it is possible to specify multiline content using "\r\n".
202 | // Ex: "tag": "These\r\nare\r\nfour\r\nlines"
203 | "tag": "",
204 |
205 | //////////////////////////////
206 | // //
207 | // PRICING AND AVAILABILITY //
208 | // //
209 | //////////////////////////////
210 |
211 | // Pricing
212 | //
213 | // Your add-on will be priced according to the price tier set for "priceId" and will be marketed at
214 | // that tier for ALL available markets. To set a different price for a specific market, use the
215 | // "marketSpecificPricings" object. You can also use the "marketSpecificPricings" object to make
216 | // your add-on "NotAvailable" to a specific market.
217 | //
218 | // It is not currently possible to programmatically access or set sales information via the
219 | // Windows Store Submission API for add-on submissions at this time.
220 |
221 | "pricing": {
222 |
223 | // Price
224 | //
225 | // Set the price of the add-on.
226 | // The value provided should be a "Tier" and NOT an actual price
227 | // For a mapping of "Tiers" to prices, refer to https://docs.microsoft.com/en-us/windows/uwp/monetize/manage-add-on-submissions#price-tiers
228 | // Can also be "Free" or "NotAvailable"
229 |
230 | "priceId": "NotAvailable",
231 |
232 | // Market Specific Pricing
233 | //
234 | // By default, your add-on will be marketed at the price tier set for "priceId" for all possible markets.
235 | // To set a different price tier for a specific region, add the ("region": "Tier") key-value pair.
236 | //
237 | // For a list of supported regions, see https://msdn.microsoft.com/en-us/library/windows/apps/mt148563.aspx
238 |
239 | "marketSpecificPricings": {
240 | }
241 | },
242 |
243 | /////////////////////////////
244 | // //
245 | // NOTES FOR CERTIFICATION //
246 | // //
247 | /////////////////////////////
248 |
249 | // String. Information that helps testers use and understand this submission.
250 | // Customers won't see this info.
251 | // Can include information such as test account credentials, steps to access and verify features, etc.
252 | //
253 | // The notes must be provided on one line, but it is possible to specify multiline notes using "\r\n".
254 | // Ex: "notesForCertification": "These\r\nare\r\nfour\r\nlines"
255 |
256 | "notesForCertification": ""
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/StoreBroker/NugetTools.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | function Get-NugetExe
4 | {
5 | <#
6 | .SYNOPSIS
7 | Downloads nuget.exe from http://nuget.org to a new local temporary directory
8 | and returns the path to the local copy.
9 |
10 | .DESCRIPTION
11 | Downloads nuget.exe from http://nuget.org to a new local temporary directory
12 | and returns the path to the local copy.
13 |
14 | The Git repo for this module can be found here: http://aka.ms/StoreBroker
15 |
16 | .EXAMPLE
17 | Get-NugetExe
18 | Creates a new directory with a GUID under $env:TEMP and then downloads
19 | http://nuget.org/nuget.exe to that location.
20 |
21 | .OUTPUTS
22 | System.String - The path to the newly downloaded nuget.exe
23 | #>
24 | [CmdletBinding(SupportsShouldProcess)]
25 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")]
26 | param()
27 |
28 | if ($null -eq $script:nugetExePath)
29 | {
30 | $sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
31 | $script:nugetExePath = Join-Path $(New-TemporaryDirectory) "nuget.exe"
32 |
33 | Write-Log -Message "Downloading $sourceNugetExe to $script:nugetExePath" -Level Verbose
34 | Invoke-WebRequest $sourceNugetExe -OutFile $script:nugetExePath
35 | }
36 |
37 | return $script:nugetExePath
38 | }
39 |
40 | function Get-NugetPackage
41 | {
42 | <#
43 | .SYNOPSIS
44 | Downloads a nuget package to the specified directory.
45 |
46 | .DESCRIPTION
47 | Downloads a nuget package to the specified directory (or the current
48 | directory if no TargetPath was specified).
49 |
50 | The Git repo for this module can be found here: http://aka.ms/StoreBroker
51 |
52 | .PARAMETER PackageName
53 | The name of the nuget package to download
54 |
55 | .PARAMETER TargetPath
56 | The nuget package will be downloaded to this location.
57 |
58 | .PARAMETER Version
59 | If provided, this indicates the version of the package to download.
60 | If not specified, downloads the latest version.
61 |
62 | .PARAMETER NoStatus
63 | If this switch is specified, long-running commands will run on the main thread
64 | with no commandline status update. When not specified, those commands run in
65 | the background, enabling the command prompt to provide status information.
66 |
67 | .EXAMPLE
68 | Get-NugetPackage "Microsoft.AzureStorage" -Version "6.0.0.0" -TargetPath "c:\foo"
69 | Downloads v6.0.0.0 of the Microsoft.AzureStorage nuget package to the c:\foo directory.
70 |
71 | .EXAMPLE
72 | Get-NugetPackage "Microsoft.AzureStorage" "c:\foo"
73 | Downloads the most recent version of the Microsoft.AzureStorage
74 | nuget package to the c:\foo directory.
75 | #>
76 | [CmdletBinding(SupportsShouldProcess)]
77 | param(
78 | [Parameter(
79 | Mandatory,
80 | ValueFromPipeline)]
81 | [string] $PackageName,
82 |
83 | [Parameter(Mandatory)]
84 | [ValidateScript({if (Test-Path -Path $_ -PathType Container) { $true } else { throw "$_ does not exist." }})]
85 | [string] $TargetPath,
86 |
87 | [string] $Version = "",
88 |
89 | [switch] $NoStatus
90 | )
91 |
92 | Write-Log -Message "Downloading nuget package [$PackageName] to [$TargetPath]" -Level Verbose
93 |
94 | $nugetPath = Get-NugetExe
95 |
96 | if ($NoStatus)
97 | {
98 | if ($PSCmdlet.ShouldProcess($PackageName, $nugetPath))
99 | {
100 | if (-not [System.String]::IsNullOrEmpty($Version))
101 | {
102 | & $nugetPath install $PackageName -o $TargetPath -version $Version -source nuget.org -NonInteractive | Out-Null
103 | }
104 | else
105 | {
106 | & $nugetPath install $PackageName -o $TargetPath -source nuget.org -NonInteractive | Out-Null
107 | }
108 | }
109 | }
110 | else
111 | {
112 | $jobName = "Get-NugetPackage-" + (Get-Date).ToFileTime().ToString()
113 |
114 | if ($PSCmdlet.ShouldProcess($jobName, "Start-Job"))
115 | {
116 | [scriptblock]$scriptBlock = {
117 | param($NugetPath, $PackageName, $TargetPath, $Version)
118 |
119 | if (-not [System.String]::IsNullOrEmpty($Version))
120 | {
121 | & $NugetPath install $PackageName -o $TargetPath -version $Version -source nuget.org
122 | }
123 | else
124 | {
125 | & $NugetPath install $PackageName -o $TargetPath -source nuget.org
126 | }
127 | }
128 |
129 | Start-Job -Name $jobName -ScriptBlock $scriptBlock -Arg @($nugetPath, $PackageName, $TargetPath, $Version) | Out-Null
130 |
131 | if ($PSCmdlet.ShouldProcess($jobName, "Wait-JobWithAnimation"))
132 | {
133 | Wait-JobWithAnimation -JobName $jobName -Description "Retrieving nuget package: $PackageName"
134 | }
135 |
136 | if ($PSCmdlet.ShouldProcess($jobName, "Receive-Job"))
137 | {
138 | Receive-Job $jobName -AutoRemoveJob -Wait -ErrorAction SilentlyContinue -ErrorVariable remoteErrors | Out-Null
139 | }
140 | }
141 |
142 | if ($remoteErrors.Count -gt 0)
143 | {
144 | throw $remoteErrors[0].Exception
145 | }
146 | }
147 | }
148 |
149 | function Test-AssemblyIsDesiredVersion
150 | {
151 | <#
152 | .SYNOPSIS
153 | Checks if the specified file is the expected version.
154 |
155 | .DESCRIPTION
156 | Checks if the specified file is the expected version.
157 |
158 | Does a best effort match. If you only specify a desired version of "6",
159 | any version of the file that has a "major" version of 6 will be considered
160 | a match, where we use the terminology of a version being:
161 | Major.Minor.Build.PrivateInfo.
162 |
163 | The Git repo for this module can be found here: http://aka.ms/StoreBroker
164 |
165 | .PARAMETER AssemblyPath
166 | The full path to the assembly file being tested.
167 |
168 | .PARAMETER DesiredVersion
169 | The desired version of the assembly. Specify the version as specifically as
170 | necessary.
171 |
172 | .EXAMPLE
173 | Test-AssemblyIsDesiredVersion "c:\Microsoft.WindowsAzure.Storage.dll" "6"
174 |
175 | Returns back $true if "c:\Microsoft.WindowsAzure.Storage.dll" has a major version
176 | of 6, regardless of its Minor, Build or PrivateInfo numbers.
177 |
178 | .OUTPUTS
179 | Boolean - $true if the assembly at the specified path exists and meets the specified
180 | version criteria, $false otherwise.
181 | #>
182 | param(
183 | [Parameter(Mandatory)]
184 | [ValidateScript( { if (Test-Path -PathType Leaf -Path $_) { $true } else { throw "'$_' cannot be found." } })]
185 | [string] $AssemblyPath,
186 |
187 | [Parameter(Mandatory)]
188 | [ValidateScript( { if ($_ -match '^\d+(\.\d+){0,3}$') { $true } else { throw "'$_' not a valid version format." } })]
189 | [string] $DesiredVersion
190 | )
191 |
192 | $splitTargetVer = $DesiredVersion.Split('.')
193 |
194 | $file = Get-Item -Path $AssemblyPath -ErrorVariable ev
195 | if (($null -ne $ev) -and ($ev.Count -gt 0))
196 | {
197 | Write-Log "Problem accessing [$Path]: $($ev[0].Exception.Message)" -Level Warning
198 | return $false
199 | }
200 |
201 | $versionInfo = $file.VersionInfo
202 | $splitSourceVer = @(
203 | $versionInfo.ProductMajorPart,
204 | $versionInfo.ProductMinorPart,
205 | $versionInfo.ProductBuildPart,
206 | $versionInfo.ProductPrivatePart
207 | )
208 |
209 | # The cmdlet contract states that we only care about matching
210 | # as much of the version number as the user has supplied.
211 | for ($i = 0; $i -lt $splitTargetVer.Count; $i++)
212 | {
213 | if ($splitSourceVer[$i] -ne $splitTargetVer[$i])
214 | {
215 | return $false
216 | }
217 | }
218 |
219 | return $true
220 | }
221 |
222 | function Get-NugetPackageDllPath
223 | {
224 | <#
225 | .SYNOPSIS
226 | Makes sure that the specified assembly from a nuget package is available
227 | on the machine, and returns the path to it.
228 |
229 | .DESCRIPTION
230 | Makes sure that the specified assembly from a nuget package is available
231 | on the machine, and returns the path to it.
232 |
233 | This will first look for the assembly in the module's script directory.
234 |
235 | Next it will look for the assembly in the location defined by
236 | $SBAlternateAssemblyDir. This value would have to be defined by the user
237 | prior to execution of this cmdlet.
238 |
239 | If not found there, it will look in a temp folder established during this
240 | PowerShell session.
241 |
242 | If still not found, it will download the nuget package
243 | for it to a temp folder accessible during this PowerShell session.
244 |
245 | The Git repo for this module can be found here: http://aka.ms/StoreBroker
246 |
247 | .PARAMETER NugetPackageName
248 | The name of the nuget package to download
249 |
250 | .PARAMETER NugetPackageVersion
251 | Indicates the version of the package to download.
252 |
253 | .PARAMETER AssemblyPackageTailDirectory
254 | The sub-path within the nuget package download location where the assembly should be found.
255 |
256 | .PARAMETER AssemblyName
257 | The name of the actual assembly that the user is looking for.
258 |
259 | .PARAMETER NoStatus
260 | If this switch is specified, long-running commands will run on the main thread
261 | with no commandline status update. When not specified, those commands run in
262 | the background, enabling the command prompt to provide status information.
263 |
264 | .EXAMPLE
265 | Get-NugetPackageDllPath "WindowsAzure.Storage" "6.0.0" "WindowsAzure.Storage.6.0.0\lib\net40\" "Microsoft.WindowsAzure.Storage.dll"
266 |
267 | Returns back the path to "Microsoft.WindowsAzure.Storage.dll", which is part of the
268 | "WindowsAzure.Storage" nuget package. If the package has to be downloaded via nuget,
269 | the command prompt will show a time duration status counter while the package is being
270 | downloaded.
271 |
272 | .EXAMPLE
273 | Get-NugetPackageDllPath "WindowsAzure.Storage" "6.0.0" "WindowsAzure.Storage.6.0.0\lib\net40\" "Microsoft.WindowsAzure.Storage.dll" -NoStatus
274 |
275 | Returns back the path to "Microsoft.WindowsAzure.Storage.dll", which is part of the
276 | "WindowsAzure.Storage" nuget package. If the package has to be downloaded via nuget,
277 | the command prompt will appear to hang during this time.
278 |
279 | .OUTPUTS
280 | System.String - The full path to $AssemblyName.
281 | #>
282 | [CmdletBinding(SupportsShouldProcess)]
283 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")]
284 | param(
285 | [Parameter(Mandatory)]
286 | [string] $NugetPackageName,
287 |
288 | [Parameter(Mandatory)]
289 | [string] $NugetPackageVersion,
290 |
291 | [Parameter(Mandatory)]
292 | [string] $AssemblyPackageTailDirectory,
293 |
294 | [Parameter(Mandatory)]
295 | [string] $AssemblyName,
296 |
297 | [switch] $NoStatus
298 | )
299 |
300 | Write-Log -Message "Looking for $AssemblyName" -Level Verbose
301 |
302 | # First we'll check to see if the user has cached the assembly into the module's script directory
303 | $moduleAssembly = Join-Path $PSScriptRoot $AssemblyName
304 | if (Test-Path -Path $moduleAssembly -PathType Leaf -ErrorAction Ignore)
305 | {
306 | if (Test-AssemblyIsDesiredVersion -AssemblyPath $moduleAssembly -DesiredVersion $NugetPackageVersion)
307 | {
308 | Write-Log -Message "Found $AssemblyName in module directory ($PSScriptRoot)." -Level Verbose
309 | return $moduleAssembly
310 | }
311 | else
312 | {
313 | Write-Log -Message "Found $AssemblyName in module directory ($PSScriptRoot), but its version number didn't match required [$NugetPackageVersion]." -Level Verbose
314 | }
315 | }
316 |
317 | # Next, we'll check to see if the user has defined an alternate path to get the assembly from
318 | if (-not [System.String]::IsNullOrEmpty($SBAlternateAssemblyDir))
319 | {
320 | $alternateAssemblyPath = Join-Path $SBAlternateAssemblyDir $AssemblyName
321 | if (Test-Path -Path $alternateAssemblyPath -PathType Leaf -ErrorAction Ignore)
322 | {
323 | if (Test-AssemblyIsDesiredVersion -AssemblyPath $alternateAssemblyPath -DesiredVersion $NugetPackageVersion)
324 | {
325 | Write-Log -Message "Found $AssemblyName in alternate directory ($SBAlternateAssemblyDir)." -Level Verbose
326 | return $alternateAssemblyPath
327 | }
328 | else
329 | {
330 | Write-Log -Message "Found $AssemblyName in alternate directory ($SBAlternateAssemblyDir), but its version number didn't match required [$NugetPackageVersion]." -Level Verbose
331 | }
332 | }
333 | }
334 |
335 | # Then we'll check to see if we've previously cached the assembly in a temp folder during this PowerShell session
336 | if ([System.String]::IsNullOrEmpty($script:tempAssemblyCacheDir))
337 | {
338 | $script:tempAssemblyCacheDir = New-TemporaryDirectory
339 | }
340 | else
341 | {
342 | $cachedAssemblyPath = Join-Path $(Join-Path $script:tempAssemblyCacheDir $AssemblyPackageTailDirectory) $AssemblyName
343 | if (Test-Path -Path $cachedAssemblyPath -PathType Leaf -ErrorAction Ignore)
344 | {
345 | if (Test-AssemblyIsDesiredVersion -AssemblyPath $cachedAssemblyPath -DesiredVersion $NugetPackageVersion)
346 | {
347 | Write-Log -Message "Found $AssemblyName in temp directory ($script:tempAssemblyCacheDir)." -Level Verbose
348 | return $cachedAssemblyPath
349 | }
350 | else
351 | {
352 | Write-Log -Message "Found $AssemblyName in temp directory ($script:tempAssemblyCacheDir), but its version number didn't match required [$NugetPackageVersion]." -Level Verbose
353 | }
354 | }
355 | }
356 |
357 | # Still not found, so we'll go ahead and download the package via nuget.
358 | Write-Log -Message "$AssemblyName is needed and wasn't found. Acquiring it via nuget..." -Level Verbose
359 | Get-NugetPackage -PackageName $NugetPackageName -Version $NugetPackageVersion -TargetPath $script:tempAssemblyCacheDir -NoStatus:$NoStatus
360 |
361 | $cachedAssemblyPath = Join-Path $(Join-Path $script:tempAssemblyCacheDir $AssemblyPackageTailDirectory) $AssemblyName
362 | if (Test-Path -Path $cachedAssemblyPath -PathType Leaf -ErrorAction Ignore)
363 | {
364 | Write-Log -Message @(
365 | "To avoid this download delay in the future, copy the following file:",
366 | " [$cachedAssemblyPath]",
367 | "either to:",
368 | " [$PSScriptRoot]",
369 | "or to:",
370 | " a directory of your choosing, and save that directory path to `$SBAlternateAssemblyDir")
371 |
372 | return $cachedAssemblyPath
373 | }
374 |
375 | $output = "Unable to acquire a reference to $AssemblyName."
376 | Write-Log -Message $output -Level Error
377 | throw $output
378 | }
379 |
--------------------------------------------------------------------------------
/Documentation/PDP.md:
--------------------------------------------------------------------------------
1 | # StoreBroker PowerShell Module
2 | ## PDP (Product Description Page) Files
3 |
4 |
5 | ----------
6 | #### Table of Contents
7 |
8 | * [Overview](#overview)
9 | * [Sections](#sections)
10 | * [AppStoreName](#appstorename)
11 | * [Screenshots and Captions](#screenshots-and-captions)
12 | * [Folder Structure](#folder-structure)
13 | * [Additional Assets](#additional-assets)
14 | * [Trailers](#trailers)
15 | * [Icons](#icons)
16 | * [Fallback Language Support](#fallback-language-support)
17 | * [Schemas And Samples](#schemas-and-samples)
18 | * [Loc Attributes and Comments](#loc-attributes-and-comments)
19 | * [Marking A String To Not Be Localized](#marking-a-string-to-not-be-localized)
20 |
21 | ----------
22 |
23 |
24 | ## Overview
25 |
26 | One of the biggest benefits of using StoreBroker and the Windows Store Submission API
27 | to update your submission, is the ease with which they can update all of the listing
28 | metadata (descriptions, features, screenshots and captions, etc...) across every language
29 | that you have localized metadata for.
30 |
31 | StoreBroker uses an XML file format that we refer to as "PDP" (Product Description Page)
32 | to store all of this metadata, making it easy for localization systems to parse and
33 | localize the relevant content.
34 |
35 | > There is no requirement that you adopt the PDP XML file format that we are discussing here.
36 | > `New-SubmissionPackage` generates the .json and .zip files ("the payload") that the other
37 | > commands interact with, and it is the only part of StoreBroker that has knowledge of PDP
38 | > files. If you are already using some other file format for the localization of this
39 | > metadata content, but you want to use StoreBroker, then you will have to write the necessary
40 | > code to generate the payload. Refer to the
41 | > [Submission API Documentation](https://msdn.microsoft.com/en-us/windows/uwp/monetize/manage-app-submissions)
42 | > for more information on this.
43 |
44 | ----------
45 |
46 | ## Sections
47 |
48 | The main sections that you'll find in the PDP (depending on which schema version is in use)
49 | are as follows:
50 |
51 | **For Application Submissions**
52 | * AppStoreName
53 | * Keywords
54 | * Description
55 | * ShortDescription
56 | * ShortTitle
57 | * SortTitle
58 | * VoiceTitle
59 | * DevStudio
60 | * ReleaseNotes
61 | * ScreenshotCaptions
62 | * AdditionalAssets
63 | * AppFeatures
64 | * RecommendedHardware
65 | * MinimumHardware
66 | * CopyrightAndTrademark
67 | * AdditionalLicenseTerms
68 | * WebsiteURL
69 | * SupportContactInfo
70 | * PrivacyPolicyURL
71 | * Trailers
72 |
73 | **For In-App Product (IAP) ("add-on") Submissions**
74 | * Title
75 | * Description
76 | * Icon
77 |
78 | These should map fairly clearly to the sections you're already familiar with in the DevPortal.
79 | For additional requirements on number of elements or length of individual elements, refer to
80 | the schema (or sample file).
81 |
82 | ### AppStoreName
83 |
84 | > The `AppStoreName` property in the PDP file is often a source of confusion for users, so it's recommended
85 | > that you pay close attention this expanded explanation below.
86 |
87 | `AppStoreName` maps to the
88 | [`DisplayName`](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-displayname)
89 | property from your application's AppxManifest.xml file. The Store will automatically grab this for every language
90 | that your application has a listing for, meaining that in most scenarios, you never need to provide a value within
91 | the PDP. The only time you should ever specify a value for this in the PDP, is if you will be providing a listing
92 | for a language that your application isn't explicitly being localized to.
93 |
94 | If you provide this value in a PDP for a language that your application already has a `DisplayName` value for,
95 | your submission will eventually fail after it has been committed because of this name conflict.
96 |
97 | **Re-stated again**, you can _only_ successfully provide the `AppStoreName` if _that PDP language_ does not have
98 | a corresponding `DisplayName` entry within your application package.
99 |
100 | If you're in the scenario where you _do_ need to do this for _some_ languages, you'll likely want to follow the
101 | [FAQ](./USAGE.md#faq) ("Does StoreBroker support adding region-specific listings for languages that the app itself
102 | doesn't directly support?") which explains how to use mulitple PDP's within your project, each localized to a
103 | different set of languages. In that scenario, you'd have a PDP with the `AppStoreName` uncommented and localized,
104 | but only for the set of languages that needs it; a different PDP would be used for the other languages, and in that
105 | other PDP, the `AppStoreName` would remain empty/commented-out.
106 |
107 | ### Screenshots and Captions
108 |
109 | > Only relevant for Application submissions
110 |
111 | It's worth a little bit of additional time to explain how screenshots and captions work.
112 |
113 | The DevPortal has different screenshot sections per platform, and you need to specify a caption
114 | for each image that you upload. That makes sense from the DevPortal perspective where it wants
115 | to be as declarative and flexible as possible for you to author how you want your content displayed.
116 |
117 | That approach doesn't make as much sense from a localization perspective, because you likely
118 | use a similar set of screenshots across all platforms, and those screenshots would share the
119 | same localized caption.
120 |
121 | Since the PDP file is all about localizing text, it puts the _captions_ first, and you then associate
122 | screenshots to those captions by filename. There are five different platforms currently supported
123 | for screenshots:
124 |
125 | * DesktopImage
126 | * MobileImage
127 | * XboxImage
128 | * SurfaceHubImage
129 | * HoloLensImage
130 |
131 | You need to specify one or more platform image attributes for each caption (otherwise, there's no
132 | reason to bother localizing the caption). The value of the attribute is the name of the screenshot
133 | that will be found in [screenshot folder structure](#folder-structure).
134 |
135 | #### Folder Structure
136 |
137 | A key attribute to be aware of is the `Release` attribute that is part of the primary
138 | `ProductDescription` node. The value for `Release` is directly used by `New-SubmissionPackage`
139 | to find the screenshots referenced by the PDP.
140 |
141 | The expected folder structure layout for screenshots is as follows:
142 |
143 | \\\...\img.png
144 |
145 | where:
146 | * `ImagesRootPath`: specified in your config file or at the commandline
147 | * `Release`: this is the attribute being discussed
148 | * `lang-code`: the langCode for the language matching that of the PDP
149 | * `...`: any number of sub-folders ... we don't care about these...at this point, we're just
150 | looking recusively for the specific filename
151 | * `img.png`: the filename that you specified in the caption's platform-specific image attribute
152 |
153 | ### Additional Assets
154 |
155 | > Only relevant for Application submissions
156 |
157 | There are three different types of images that can be used for a listing:
158 | 1. Screenshots (these have captions)
159 | 2. Additional Assets (these are images that have no concept of captions)
160 | 3. Trailer screenshots (which also don't have captions, but are tied to trailers)
161 |
162 | In the [previous section](#screenshots-and-captions), we talked about screenshots
163 | (and their captions). Now we will talk about #2 (Additional Assets).
164 |
165 | You can learn more about the specifics of these different images and how they're used by referring to the
166 | [dev portal's online documentation](https://docs.microsoft.com/en-us/windows/uwp/publish/app-screenshots-and-images),
167 | and the related [API documentation](https://docs.microsoft.com/en-us/windows/uwp/monetize/manage-app-submissions#image-object).
168 |
169 | To define these assets, there is a top-level element called `AdditionalAssets` (which
170 | is a sibling to `ScreenshotCaptions`). It can contain any (or all) of the following
171 | elements:
172 |
173 | * `StoreLogo9x16`
174 | * `StoreLogoSquare`
175 | * `Icon`
176 | * `PromotionalArt16x9`
177 | * `PromotionalArtwork2400X1200`
178 | * `XboxBrandedKeyArt`
179 | * `XboxTitledHeroArt`
180 | * `XboxFeaturedPromotionalArt`
181 | * `SquareIcon358X358`
182 | * `BackgroundImage1000X800`
183 | * `PromotionalArtwork414X180`
184 |
185 | These elements do not have any InnerText/content -- they only have a single attribute
186 | called `FileName` which should reference the .png file for that image type.
187 |
188 | Similar to Screenshots, there is full [fallback language support](#fallback-language-support).
189 | You can add the `FallbackLanguage` attribute on an individual element to only affect that one
190 | image type, or you can add it to `AdditionalAssets` to affect them all (or to
191 | `ProductDescription` to affect all asset types).
192 |
193 | #### Trailers
194 |
195 | > Only relevant for Application submissions
196 |
197 | You can learn more about the specifics of trailers and how they're used by referring to the
198 | [dev portal's online documentation](https://docs.microsoft.com/en-us/windows/uwp/publish/app-screenshots-and-images#trailers).
199 |
200 | A single trailer consists of the following information:
201 | * trailer filename
202 | * trailer title (localizable)
203 | * trailer screenshot filename (only one permitted)
204 | * trailer screenshot description (metadata only, never seen be a user)
205 |
206 | From an authoring perspective, it looks like this (with loc comment/attributes removed for brevity):
207 |
208 |
209 |
210 | This is the trailer's title
211 |
212 | The user will never see this text
213 |
214 |
215 |
216 |
217 | While companies may or may not provide localized screenshots per region, most companies will only
218 | ever produce their trailers for a limited number of different languages/regions, and re-use
219 | those same trailers across most other locales. Therefore, it is highly advised that you leverage
220 | [fallback language support](#fallback-language-support) when authoring your trailers
221 | so that trailers are propery re-used, thus reducing the size of the final package that needs
222 | to be uploaded to the Store.
223 |
224 | ### Icons
225 |
226 | > Only relevant for In-App Product (IAP) ("add-on") submissions
227 |
228 | Unlike screenshots, icons have no associated captions with them. However, it is possible that
229 | your IAP may need to use a different icon based on the region (maybe a different symbol or color
230 | is needed based on that region's culture), and so the icon is part of the PDP.
231 |
232 | The only thing that can be specified for the Icon is the filename of that icon. It is expected
233 | that the filename will be found within the defined [folder structure](#folder-structure).
234 |
235 | ### Fallback Language Support
236 |
237 | PDP files are language-specific, and as we saw in the [Folder Structure](#folder-structure) section,
238 | any screenhots or icons that are referenced by a PDP are only searched for within that same language's
239 | media sub-folder within `ImagesRootPath`.
240 |
241 | There are situations however where you might want to share an image/media file across more than one language.
242 | For instance: maybe you want all Spanish language PDP's to use the images from `es-es`.
243 | Both `New-SubmissionPackage` and `New-InAppProductSubmissionPackage` support a `MediaFallbackLanguage` parameter
244 | which lets you specify the language where StoreBroker should look if any of the referenced media files cannot
245 | be found in the language-specific images/media folder.
246 |
247 | For screenshot and icons, you can specify a `FallbackLanguage` attribute whose value would be a lang-code
248 | (ex. `en-US`, `es-es`, etc...). For icons, the attribute is directly on the `` element. For
249 | screenshots, the attribute is available on both the `` _and_ `` elements.
250 | It can be set on either, or both, of those elements. If specified on both, the value in the ``
251 | node value will win, since it is the more-specific value.
252 |
253 | Similarly, there is support for `AdditionalAsset` images (on the individual element nodes as well as on
254 | the `AdditionalAsset` node itself), and for trailers on the `Trailers`, `Trailer`, `Images` and `Image`
255 | elements).
256 |
257 | You can also set `FallbackLanguage` at the root element (`ProductDescription` or `InAppProductDescription`)
258 | to affect every media type.
259 |
260 | As usual, StoreBroker will first search for any media files referenced by that element in that PDP's
261 | langcode-specific images/media sub-folder. If not found, it will then look in the fallback language's
262 | images/media sub-folder. Only then will StoreBroker fail if the file still cannot be found.
263 |
264 | The key to remember is that this behaves in a "fallback" fashion, similar to language localization, and not
265 | as an override. StoreBroker will always attempt to use the language-specific version of the file unless
266 | it can't be found.
267 |
268 | > Specifying the `FallbackLanguage` attribute will override the `MediaFallbackLanguage` parameter/config value
269 | > for `New-SubmissionPackage` and `New-InAppProductSubmissionPackage` for that specific media element.
270 | > There can only be _one_ fallback language for any given media file. So, at most, StoreBroker will search
271 | > for a given media file twice (original language and fallback language) before failing the packaging action.
272 |
273 | ----------
274 |
275 | ## Schemas and Samples
276 |
277 | At this time, StoreBroker has two PDP schemas in use:
278 |
279 | ### Application Submissions
280 | * **Uri**: `http://schemas.microsoft.com/appx/2012/ProductDescription`
281 | * **XSD**: [PDP\ProductDescription.xsd](../PDP/ProductDescription.xsd)
282 | * **Sample XML**: [PDP\ProductDescription.xml](../PDP/ProductDescription.xml)
283 |
284 | ### In-App Product (IAP) ("add-on") Submissions
285 | * **Uri**: `http://schemas.microsoft.com/appx/2012/InAppProductDescription`
286 | * **XSD**: [PDP\InAppProductDescription.xsd](../PDP/InAppProductDescription.xsd)
287 | * **Sample XML**: [PDP\InAppProductDescription.xml](../PDP/InAppProductDescription.xml)
288 |
289 | ----------
290 |
291 | ## Loc Attributes and Comments
292 |
293 | If you reviewed the schema or the sample, you may have noticed some attributes named `_locID`
294 | or comments with the string `_locComment_text` in them. Those are there to assist with
295 | localization systems that don't send the raw file to localizers, but rather send a parsed
296 | representation of the file.
297 |
298 | The `_locID_` attribute is designed to give a unique ID (within the file) that localization
299 | systems can use to refer to the string. The `_locComment_text` comment is designed to be
300 | shown next to the localization string to give context to the localizers on how the string is
301 | being used, and can contain special instructions in brackets that give explicit guidance on
302 | requirements for the way that a string must be localized (ex: `{MaxLength=100}` would indicate
303 | that the final localized string can't exceed 100 characters).
304 |
305 | ### Marking A String To Not Be Localized
306 |
307 | In some situations, you may not want a certain string to be localized (ex: an email address or URL);
308 | this might be obvious to a human localizer, but likely not to an automated localization system.
309 | A common scenario where this might be an issue is if you use automated localization for
310 | [pseudolocalization](https://en.wikipedia.org/wiki/Pseudolocalization) purposes.
311 |
312 | The recommended way to indicate this in your PDP file is to modify that element's `_locComment_text`.
313 | Just change the part in the brackets (`{MaxLength=xxx}`) to instead say `{Locked}`. So, if it
314 | originally looked like this:
315 |
316 |
317 |
318 | change it to look like this:
319 |
320 |
321 |
322 | And then modify your localization system to recognize the comment for that element to indicate that
323 | it should not be localized. You should additionally publish this information to your localizers
324 | so that it can be quite explicit to them as well when they should/shouldn't localize a certain
325 | string.
326 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # StoreBroker PowerShell Module
2 | ## Contributing
3 |
4 | Looking to help out? You've come to the right place. We'd love your help in making this the best
5 | submission solution for every Windows developer.
6 |
7 | Looking for information on how to use this module? Head on over to [README.md](README.md).
8 |
9 | ----------
10 | #### Table of Contents
11 |
12 | * [Overview](#overview)
13 | * [Maintainers](#maintainers)
14 | * [Feedback](#feedback)
15 | * [Bugs](#bugs)
16 | * [Suggestions](#suggestions)
17 | * [Questions](#questions)
18 | * [Static Analysis](#static-analysis)
19 | * [Visual Studio](#visual-studio)
20 | * [Module Manifest](#module-manifest)
21 | * [Logging](#logging)
22 | * [PowerShell Version](#powershell-version)
23 | * [Coding Guidelines](#coding-guidelines)
24 | * [Code Comments](#code-comments)
25 | * [Testing](#testing)
26 | * [Releasing](#releasing)
27 | * [Updating the CHANGELOG](#updating-the-changelog)
28 | * [Adding a New Tag](#adding-a-new-tag)
29 | * [Contributors](#contributors)
30 | * [Legal and Licensing](#legal-and-licensing)
31 |
32 | ----------
33 |
34 | ## Overview
35 |
36 | We're excited that _you're_ excited about this project, and would welcome your contributions to help
37 | it grow. There are many different ways that you can contribute:
38 |
39 | 1. Submit a [bug report](#bugs).
40 | 2. Verify existing fixes for bugs.
41 | 3. Submit your own fixes for a bug. Before submitting, please make sure you have:
42 | * Performed code reviews of your own
43 | * Updated the [test cases](#testing) if needed
44 | * Run the [test cases](#testing) to ensure no feature breaks or test breaks
45 | * Added the [test cases](#testing) for new code
46 | * Ensured that the code is free of [static analysis](#static-analysis) issues
47 | 4. Submit a feature request.
48 | 5. Help answer [questions](https://github.com/Microsoft/StoreBroker/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Aquestion).
49 | 6. Write new [test cases](#testing).
50 | 7. Tell others about the project.
51 | 8. Tell the developers how much you appreciate the product!
52 |
53 | You might also read these two blog posts about contributing code:
54 | * [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza
55 | * [Don't "Push" Your Pull Requests](http://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by Ilya Grigorik.
56 |
57 | Before submitting a feature or substantial code contribution, please discuss it with the
58 | StoreBroker team via [Issues](https://github.com/Microsoft/StoreBroker/issues), and ensure it
59 | follows the product roadmap. Note that all code submissions will be rigorously reviewed by the
60 | StoreBroker Team. Only those that meet a high bar for both quality and roadmap fit will be merged
61 | into the source.
62 |
63 | ## Maintainers
64 |
65 | StoreBroker is maintained by:
66 |
67 | - **[@HowardWolosky](http://github.com/HowardWolosky)**
68 | - **[@DanBelcher-MSFT](http://github.com/DanBelcher-MSFT)**
69 |
70 | As StoreBroker is a production dependency for Microsoft, we have a couple workflow restrictions:
71 |
72 | - Anyone with commit rights can merge Pull Requests provided that there is a :+1: from one of
73 | the members above.
74 | - Releases are performed by a member above so that we can ensure Microsoft internal processes
75 | remain up to date with the latest and that there are no regressions.
76 |
77 | ## Feedback
78 |
79 | All issue types are tracked on the project's [Issues]( https://github.com/Microsoft/StoreBroker/issues)
80 | page.
81 |
82 | In all cases, make sure to search the list of issues before opening a new one.
83 | Duplicate issues will be closed.
84 |
85 | ### Bugs
86 |
87 | For a great primer on how to submit a great bug report, we recommend that you read:
88 | [Painless Bug Tracking](http://www.joelonsoftware.com/articles/fog0000000029.html).
89 |
90 | To report a bug, please include as much information as possible, namely:
91 |
92 | * The version of the module (located in `StoreBroker\StoreBroker.psd1`)
93 | * Your OS version
94 | * Your version of PowerShell (`$PSVersionTable.PSVersion`)
95 | * As much information as possible to reproduce the problem.
96 | * If possible, logs from your execution of the task that exhibit the erroneous behavior
97 | * The behavior you expect to see
98 |
99 | Please also mark your issue with the 'bug' label.
100 |
101 | ### Suggestions
102 |
103 | We welcome your suggestions for enhancements to the extension.
104 | To ensure that we can integrate your suggestions effectively, try to be as detailed as possible
105 | and include:
106 |
107 | * What you want to achieve / what is the problem that you want to address.
108 | * What is your approach for solving the problem.
109 | * If applicable, a user scenario of the feature / enhancement in action.
110 |
111 | Please also mark your issue with the 'suggestion' label.
112 |
113 | ### Questions
114 |
115 | If you've read through all of the documentation, checked the Wiki, and the PowerShell help for
116 | the command you're using still isn't enough, then please open an issue with the `question`
117 | label and include:
118 |
119 | * What you want to achieve / what is the problem that you want to address.
120 | * What have you tried so far.
121 |
122 | ----------
123 |
124 | ## Static Analysis
125 |
126 | This project leverages the [PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer/)
127 | PowerShell module for static analysis.
128 |
129 | It is expected that this module shall remain "clean" from the perspective of that module.
130 |
131 | To run the module, from the root of your enlistment simply call
132 |
133 | Invoke-ScriptAnalyzer -Path .\ -Recurse
134 |
135 | That should return with no output. If you see any output when calling that command,
136 | either fix the issues that it calls out, or add a `[Diagnostics.CodeAnalysis.SuppressMessageAttribute()]`
137 | with a justification explaining why it's ok to suppress that rule within that part of the script.
138 | Refer to the [PSScriptAnalyzer documentation](https://github.com/PowerShell/PSScriptAnalyzer/) for
139 | more information on how to use that attribute, or look at other existing examples within this module.
140 |
141 | > Please ensure that your installation of PSScriptAnalyzer is up-to-date by running:
142 | > `Update-Module -Name PSScriptAnalyzer`
143 | > You should close and re-open your console window if the module was updated as a result of running
144 | > that command.
145 |
146 | ----------
147 |
148 | ### Visual Studio
149 |
150 | A Visual Studio project exists for this module:
151 |
152 | StoreBroker.pssproj
153 |
154 | Even if **you** don't use Visual Studio to edit this module, others do, so if you
155 | add new files to the module, be sure that the VS project is updated as well.
156 |
157 | > To open this project in Visual Studio, you need to have the free
158 | > [PowerShell Tools For Visual Studio](https://visualstudiogallery.msdn.microsoft.com/c9eb3ba8-0c59-4944-9a62-6eee37294597)
159 | > extension installed.
160 |
161 | ----------
162 |
163 | ### Module Manifest
164 |
165 | This is a manifested PowerShell module, and the manifest can be found here:
166 |
167 | StoreBroker\StoreBroker.psd1
168 |
169 | If you add any new modules/files to this module, be sure to update the manifest as well.
170 | New modules should be added to `NestedModules`, and any new functions or aliases that
171 | should be exported need to be added to the corresponding `FunctionsToExport` or
172 | `AliasesToExport` section.
173 |
174 | ----------
175 |
176 | ### Logging
177 |
178 | Instead of using the built-in `Write-*` methods (`Write-Host`, `Write-Warning`, etc...),
179 | please use
180 |
181 | Write-Log
182 |
183 | which is implemented in Helpers.ps1. It will take care of formatting your content in a
184 | consistent manner, as well ensure that the content is logged to a file (if configured to do so
185 | by the user).
186 |
187 | ----------
188 |
189 | ### PowerShell Version
190 |
191 | This module must be able to run on PowerShell version 4. It is permitted to add functionality
192 | that requires a higher version of PowerShell, but only if there is a fallback implementation
193 | that accomplishes the same thing in a PowerShell version 4 compatible way, and the path choice
194 | is controlled by a PowerShell version check.
195 |
196 | For an example of this, see `Write-Log` in `Helpers.ps1` which uses `Write-Information`
197 | for `Informational` messages on v5+ and falls back to `Write-Host` for earlier versions:
198 |
199 | if ($PSVersionTable.PSVersion.Major -ge 5)
200 | {
201 | Write-Information $ConsoleMessage -InformationAction Continue
202 | }
203 | else
204 | {
205 | Write-Host $ConsoleMessage
206 | }
207 |
208 |
209 | ----------
210 |
211 | ### Coding Guidelines
212 |
213 | As a general rule, our coding convention is to follow the style of the surrounding code.
214 | Avoid reformatting any code when submitting a PR as it obscures the functional changes of your change.
215 |
216 | A basic rule of formatting is to use "Visual Studio defaults".
217 | Here are some general guidelines
218 |
219 | * No tabs, indent 4 spaces.
220 | * Braces usually go on their own line,
221 | with the exception of single line statements that are properly indented.
222 | * Use `camelCase` for instance fields, `PascalCase` for function and parameter names
223 | * Avoid the creation of `script` or `global` scoped variables unless absolutely necessary.
224 | If referencing a `script` or `global` scope variable, be sure to explicitly reference it by scope.
225 | * Avoid more than one blank empty line.
226 | * Always use a blank line following a closing bracket `}` unless the next line itself is a closing bracket.
227 | * Add full [Comment Based Help](https://technet.microsoft.com/en-us/library/hh847834.aspx) for all
228 | methods added, whether internal-only or external. The act of writing this documentation may help
229 | you better design your function.
230 | * File encoding should be ASCII (preferred) or UTF8 (with BOM) if absolutely necessary.
231 | * We try to adhere to the [PoshCode Best Practices](https://github.com/PoshCode/PowerShellPracticeAndStyle/tree/master/Best%20Practices)
232 | and [DSCResources Style Guidelines](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md)
233 | and think that you should too.
234 | * We try to limit lines to 100 characters to limit the amount of horizontal scrolling needed when
235 | reviewing/maintaining code. There are of course exceptions, but this is generally an enforced
236 | preference. The [Visual Studio Productivity Power Tools](https://visualstudiogallery.msdn.microsoft.com/34ebc6a2-2777-421d-8914-e29c1dfa7f5d)
237 | extension has a "Column Guides" feature that makes it easy to add a Guideline in column 100
238 | to make it really obvious when coding.
239 |
240 | ----------
241 |
242 | ### Code comments
243 |
244 | It's strongly encouraged to add comments when you are making changes to the code and tests,
245 | especially when the changes are not trivial or may raise confusion.
246 | Make sure the added comments are accurate and easy to understand.
247 | Good code comments should improve readability of the code, and make it much more maintainable.
248 |
249 | That being said, some of the best code you can write is self-commenting. By refactoring your code
250 | into small, well-named functions that concisely describe their purpose, it's possible to write
251 | code that reads clearly while requiring minimal comments to understand what it's doing.
252 |
253 | ----------
254 |
255 | ### Testing
256 |
257 | This module supports testing using the Pester UT framework.
258 |
259 | If you do not have Pester, download it [here](https://github.com/pester/Pester).
260 | Create a `Pester` folder under any path in `$env:PSModulePath`.
261 | Unzip the contents of the download to the `Pester` folder.
262 | Pester should now automatically import whenever you run a function from its module.
263 |
264 | In the StoreBroker module, the source tree and test tree are children of the project root path.
265 | Working code should be placed under `$root\StoreBroker`. Tests should be placed under `$root\Tests`.
266 | Each file in the `Tests` folder should test one file in the `StoreBroker` folder. For example,
267 | the file `$root\StoreBroker\PackageTool.ps1` should have a corresponding file
268 | `$root\Tests\PackageTool.Tests.ps1`. As the example shows, the filename of the test file
269 | indicates which file from the source tree it is testing.
270 |
271 | Tests can be run either from the project root directory or from the `Tests` subfolder.
272 | Navigate to the correct folder and simply run `Invoke-Pester`.
273 |
274 | Pester can also be used to test code-coverage, like so:
275 |
276 | Invoke-Pester -CodeCoverage "$root\StoreBroker\PackageTool.ps1" -TestName "*PackageTool*"
277 |
278 | This command tells Pester to check the `PackageTool.ps1` file for code-coverage.
279 | The `-TestName` parameter tells Pester to run any `Describe` blocks with a `Name` like
280 | `"*PackageTool*"`.
281 |
282 | The code-coverage object can be captured and interacted with, like so:
283 |
284 | $cc = (Invoke-Pester -CodeCoverage "$root\StoreBroker\PackageTool.ps1" -TestName "*PackageTool*" -PassThru -Quiet).CodeCoverage
285 |
286 | There are many more nuances to code-coverage, see
287 | [its documentation](https://github.com/pester/Pester/wiki/Code-Coverage) for more details.
288 |
289 | ----------
290 |
291 | ### Releasing
292 |
293 | If you are a maintainer:
294 |
295 | Ensure that the version number of the module is updated with every pull request that is being
296 | accepted.
297 |
298 | This project follows [semantic versioning](http://semver.org/) in the following way:
299 |
300 | ..
301 |
302 | Where:
303 | * `` - Changes only with _significant_ updates.
304 | * `` - If this is a feature update, increment by one and be sure to reset `` to 0.
305 | * `` - If this is a bug fix, leave `` alone and increment this by one.
306 |
307 | When new code changes are checked in to the repo, a new NuGet package must be published by Microsoft.
308 | This process is documented in Microsoft's internal StoreBroker repo.
309 | Refer to the CONTRIBUTING.md in that repo for more information on creating a signed NuGet package.
310 |
311 | Once the new version has been pulled into master, there are two additional tasks to perform:
312 | * Update [CHANGELOG.md](./CHANGELOG.md)
313 | * Add a tag for that version to the repo
314 |
315 | #### Updating the CHANGELOG
316 | To update [CHANGELOG.md](./CHANGELOG.md), just duplicate the previous section and update it to be
317 | relevant for the new release. Be sure to update all of the sections:
318 | * The version number
319 | * The SB tree (we'll get that path working in a moment)
320 | * The release date
321 | * A brief list of all the changes (use a `-` for the bullet point if it's fixing a bug, or a `+` for a feature)
322 | * The link to the pull request (pr) (so that the discussion on the change can be easily reviewed) and the changelist (cl)
323 | * The author (and a link to their profile)
324 | * If it's a new contributor, also add them to the [Contributors](#contributors) list below.
325 |
326 | Then get a new pull request out for that change to CHANGELOG.
327 |
328 | #### Adding a New Tag
329 | To add a new tag:
330 | 1. Make sure that you're in a clone of the actual repo and not your own private fork.
331 | 2. Make sure that you have checked out `master` and that it's fully up-to-date
332 | 3. Run `git tag -a ''
333 | 4. In the pop-up editor, give a one-line summary of the change (that you possibly already wrote for the CHANGELOG)
334 | 5. Save and close the editor
335 | 6. Run `git push --tags` to upload the new tag you just created
336 |
337 | If you want to make sure you get these tags on any other forks/clients, you can run
338 | `git fetch origin --tags` or `git fetch upstream --tags`, or whatever you've named the source to be.
339 |
340 | ----------
341 |
342 | ### Contributors
343 |
344 | Thank you to all of our contributors, no matter how big or small the contribution:
345 |
346 | - **[Howard Wolosky (@HowardWolosky)](http://github.com/HowardWolosky)**
347 | - **[Daniel Belcher (@DanBelcher-MSFT)](http://github.com/danbelcher-MSFT)**
348 | - **[Lisa Ong (@lisaong)](http://github.com/LisaOng)**
349 | - **[Alex Dobin (@adobin)](http://github.com/ADobin)**
350 | - **[Petr Bartoň (@Pebin)](http://github.com/Pebin)**
351 | - **[Matt Perzel (@mattperzel)](http://github.com/mattperzel)**
352 | - **[Josh Wisniewski (@jowis41)](http://github.com/jowis41)**
353 | - **[Luke Cartwright (@cartwrightluke)](https://github.com/cartwrightluke)**
354 | - **[Rafael Rivera (@riverar)](https://github.com/riverar)**
355 |
356 | ----------
357 |
358 | ### Legal and Licensing
359 |
360 | StoreBroker is licensed under the [MIT license](..\LICENSE).
361 |
362 | You will need to complete a Contributor License Agreement (CLA) for any code submissions.
363 | Briefly, this agreement testifies that you are granting us permission to use the submitted change
364 | according to the terms of the project's license, and that the work being submitted is under
365 | appropriate copyright. You only need to do this once.
366 |
367 | When you submit a pull request, [@msftclas](https://github.com/msftclas) will automatically
368 | determine whether you need to sign a CLA, comment on the PR and label it appropriately.
369 | If you do need to sign a CLA, please visit https://cla.microsoft.com and follow the steps.
370 |
--------------------------------------------------------------------------------
/StoreBroker/AppConfigTemplate.json:
--------------------------------------------------------------------------------
1 | // Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | // This configuration file is for use with the StoreBroker PowerShell module.
4 | // Configure details of the app here, as well as the location of resources
5 | // used by the packaging tool.
6 |
7 | // Inline Comments can be placed in this file using the '//' delimiter.
8 | // The delimiter and any remaining text on that line will be removed.
9 |
10 | {
11 | // New-SubmissionPackage parameters
12 | //
13 | // If you notice one of the parameters you specify to New-SubmissionPackage does not change, you can specify it here
14 | // in order to avoid providing it at runtime. If New-SubmissionPackage does not have a parameter it needs, it will
15 | // check the provided config file for a non-null, non-empty value. The exception to this is -ConfigPath which must
16 | // always be provided. For information on these parameters see the README.md or run `help New-SubmissionPackage -ShowWindow`
17 | //
18 | // WARNING: Specifying a parameter at runtime will ignore any value placed in the config file.
19 | "packageParameters": {
20 |
21 | // There are two supported layouts for your PDP files:
22 | // 1. \\...\PDP.xml
23 | // 2. \\\...\PDP.xml
24 | // The only difference between these two is that there is a directory after the
25 | // and before the sub-directories.
26 | //
27 | // The first layout is generally used when your localization system will be downloading
28 | // the localized PDP files during your build. In that situation, it's always retrieving
29 | // the latest version. Alternatively, if the latest localized versions of your PDP
30 | // files are always stored in the same location, this layout is also for you.
31 | //
32 | // On the other hand, if you will be archiving the localized PDP's based on each release
33 | // to the Store, then the second layout is the one that you should use. In this scenario,
34 | // you will specify the value of "" immediately below, or at the commandline.
35 | //
36 | // Only specify 'PDPRootPath' here if it does not change.
37 | // DO NOT use environment variables in the config. If you need to use an environment
38 | // variable to get to the right path, then you need to specify that path at the commandline
39 | // instead.
40 | //
41 | // File separator '\' must be escaped, i.e. '\\'
42 | // Ex: \\\\filehare\\Public\\AppPDPs\\MyApp
43 | "PDPRootPath": "",
44 |
45 | // If your PDP files are being placed in a directory structure like this:
46 | // \\\...\PDP.xml
47 | // then specify the correct 'Release' here or at runtime.
48 | "Release": "",
49 |
50 | // Filenames that SHOULD be processed.
51 | // Wildcards are allowed, eg "ProductDescription*.xml".
52 | // It is fine to specify both "PDPInclude" and "PDPExclude".
53 | //
54 | // Ex: "PDPInclude": [
55 | // "PDP*.xml", <- Comma to separate items
56 | // "ProductDescription.xml" <- No comma for last item, or JSON deserialization will fail
57 | // ]
58 | "PDPInclude": [
59 | ],
60 |
61 | // Filenames that SHOULD NOT be processed.
62 | // Wildcards are allowed, eg "ProductDescription*.xml".
63 | // It is fine to specify both "PDPInclude" and "PDPExclude".
64 | // It is NOT necessary to specify something here, but you can to be more explicit.
65 | //
66 | // Ex: "PDPExclude": [
67 | // "*.xml.lss", <- Comma to separate items
68 | // "*.xml.lct" <- No comma for last item, or JSON deserialization will fail
69 | // ]
70 | "PDPExclude": [
71 | ],
72 |
73 | // Languages to be excluded from processing by New-SubmissionPackage.
74 | // New-SubmissionPackage will use the filepath of a PDP XML file
75 | // to identify the language of the metadata. If it sees a match
76 | // with a lang-code in this list, it will skip processing the file.
77 | //
78 | // Ex: "LanguageExclude": [
79 | // "default", <- Comma to separate items
80 | // "qps-ploc" <- No comma for last item, or JSON deserialization will fail
81 | // ],
82 | "LanguageExclude": [
83 | "default"
84 | ],
85 |
86 | // Your store screenshots must be placed with this structure:
87 | // \\\...\img.png
88 | // Specify 'ImagesRootPath' here.
89 | //
90 | // IMPORTANT: The 'Release' that will be used here is *NOT* the value specified to
91 | // New-SubmissionPackage (nor is it the Release value specified earlier in this config file),
92 | // but rather the 'Release' attribute at the top of the corresponding PDP file.
93 | //
94 | // File separator '\' must be escaped, i.e. '\\'
95 | // Ex: \\\\fileshare\\Public\\Screenshots\\MyApp
96 | "ImagesRootPath": "",
97 |
98 | // Some apps may not localize all of their metadata media (images, trailers, etc..) across all languages.
99 | // By default, StoreBroker will look in the PDP langcode's subfolder within ImagesRootPath for that
100 | // language's media content. If the requested filename is not found, StoreBroker packaging will fail.
101 | // If you specify a fallback language here (e.g. 'en-us'), then if the requested file isn't found in
102 | // the PDP language's media subfolder, StoreBroker will then look into the fallback language's media
103 | // subfolder for the exactly same-named image before failing.
104 | "MediaFallbackLanguage": "",
105 |
106 | // Specify any number of full paths to .appx, .appxbundle, or .appxupload files here.
107 | // Ex:
108 | // "AppxPath": [
109 | // "C:\app\x86\MyApp_x86.appxupload", <- Comma to separate items
110 | // "C:\app\x64\MyApp_x64.appxupload" <- No comma for last item, or JSON deserialization will fail
111 | // ]
112 | //
113 | // DO NOT use environment variables in the config. If you need to use an environment
114 | // variable to get to the right path, then you need to specify that path at the commandline
115 | // instead. In order to specify a list of paths at the commandline, just separate them
116 | // by commas and but the whole list in parenthesis:
117 | // Ex: ("C:\app\x86\MyApp_x86.appxupload", "C:\app\x64\MyApp_x64.appxupload")
118 | //
119 | // NOTE: File separator '\' must be escaped, i.e. '\\'
120 | // Ex: \\\\fileshare\\Public\\AppPDPs\\MyApp
121 | "AppxPath": [
122 | ],
123 |
124 | // Full path to a directory where the Packaging Tool can write the .json submission request
125 | // body and .zip package to upload.
126 | //
127 | // File separator '\' must be escaped, i.e. '\\'
128 | // Ex: "OutPath": "C:\\app\\output"
129 | "OutPath": "",
130 |
131 | // Common name to give to the .json and .zip files outputted by the Packaging Tool.
132 | "OutName": "",
133 |
134 | // By default, the packages will be renamed using a consistent naming scheme, which
135 | // embeds the application name, version, as well as targeted platform and architecture.
136 | // Ex: "Desktop_MyApp_2.13.22002.0_x86.appxupload"
137 | // To retain the existing package filenames instead, set this to true.
138 | "DisableAutoPackageNameFormatting": false
139 | },
140 |
141 | // Configure details of the App Submission
142 |
143 | "appSubmission": {
144 |
145 | // If known, this is the AppId given to your application by the Windows Store.
146 | // This isn't required, but adding this will help protect you from submitting something
147 | // to the wrong application when working at the commandline.
148 | //
149 | // Ex: "appId": "0ABCDEF12345"
150 | "appId": "",
151 |
152 | /////////////////////////////////
153 | // //
154 | // PUBLISH MODE AND VISIBILITY //
155 | // //
156 | /////////////////////////////////
157 |
158 | // Publish mode of the submission. One of ["NotSet", "Immediate", "Manual", "SpecificDate"]
159 | "targetPublishMode": "NotSet",
160 |
161 | // Publish date-time of the submission if the "targetPublishMode" is specified as "SpecificDate".
162 | // Date-time format should follow the ISO 8601 standard.
163 | // Ex: "2000-01-30T00:00:00-08:00"
164 | "targetPublishDate": null,
165 |
166 | // Visibility of the app. One of ["NotSet", "Public", "Private", "Hidden"]
167 | //
168 | // Public: Anyone can find your app in the Store.
169 | // Private: Hide this app in the Store. Customers with a direct link to the app's listing can still download
170 | // it, except on Windows 8 and Windows 8.1.
171 | // Hidden: Hide this app and prevent acquisition. Customers with a promotional code can still download it on
172 | // Windows 10 devices.
173 | "visibility": "NotSet",
174 |
175 | //////////////////////////////
176 | // //
177 | // PRICING AND AVAILABILITY //
178 | // //
179 | //////////////////////////////
180 |
181 | // Pricing
182 | //
183 | // Your app will be priced according to the price tier set for "priceId" and will be marketed at
184 | // that tier for ALL available markets. To set a different price for a specific market, use the
185 | // "marketSpecificPricings" object. You can also use the "marketSpecificPricings" object to make
186 | // your app "NotAvailable" to a specific market.
187 | //
188 | // If your app is having a sale, set that here using the "sales" object.
189 | "pricing": {
190 |
191 | // Price
192 | //
193 | // Set the price of the app.
194 | // The value provided should be a "Tier" and NOT an actual price
195 | // For a mapping of "Tiers" to prices, refer to https://docs.microsoft.com/en-us/windows/uwp/monetize/manage-add-on-submissions#price-tiers
196 | // Can also be "Free" or "NotAvailable"
197 | "priceId": "NotAvailable",
198 |
199 | // Trial period. One of ["NoFreeTrial", "OneDay", "SevenDays", "FifteenDays", "ThirtyDays", "TrialNeverExpires"]
200 | "trialPeriod": "NoFreeTrial",
201 |
202 | // Market Specific Pricing
203 | //
204 | // By default, your app will be marketed at the price tier set for "priceId" for all possible markets.
205 | // To set a different price tier for a specific region, add the ("region": "Tier") key-value pair.
206 | //
207 | // For a list of supported regions, see https://msdn.microsoft.com/en-us/library/windows/apps/mt148563.aspx
208 | "marketSpecificPricings": {
209 | },
210 |
211 | // Sales
212 | //
213 | // Sales follow the same pattern as the general "pricing" object. Specify a "basePriceId" and the
214 | // app will be on sale at that price tier for ALL possible markets. To specify a different price tier
215 | // for a specific region, or to exclude a region from the sale ("NotAvailable"), use "marketSpecificPricings"
216 | // exactly as you would above. Specify a "startDate" and "endDate" according to the ISO 8601
217 | // e.g. "2000-01-30T00:00:00-08:00". Finally, give a "name" for the sale, to identify it in the portal.
218 | //
219 | // Specify any number of "Sale" objects here. A "Sale" object has the following format:
220 | //
221 | // { <- Open-curly to start the object
222 | // "name": "",
223 | // "basePriceId": "",
224 | // "marketSpecificPricings": {
225 | // },
226 | // "startDate": "",
227 | // "endDate": "" <- No comma for last item, or JSON deserialization will fail
228 | // } <- Closing-curly to end the object
229 | //
230 | //
231 | // Ex:
232 | // "sales": [
233 | // {
234 | // "name": "Sale1",
235 | // "basePriceId": "Free",
236 | // "marketSpecificPricings": {
237 | // "ca-es": "NotAvailable", <- Comma to separate key-value pairs
238 | // "es-mx": "Tier2" <- No comma for last item, or JSON deserialization will fail
239 | // },
240 | // "startDate": "2000-01-30T00:00:00-08:00",
241 | // "endDate": "2000-02-07T00:00:00-08:00"
242 | // }, <- Comma to separate "Sale" objects
243 | // {
244 | // "name": "Sale2",
245 | // "basePriceId": "Tier1",
246 | // "marketSpecificPricings": {
247 | // "en-gb": "NotAvailable",
248 | // "fr-ca": "NotAvailable",
249 | // "es-mx": "Tier2"
250 | // },
251 | // "startDate": "2000-03-01T00:00:00-08:00",
252 | // "endDate": "2001-01-01T00:00:00-08:00"
253 | // } <- No comma for last item, or JSON deserialization will fail
254 | // ]
255 | "sales": [
256 | ]
257 | },
258 |
259 | // Define the preference to make this app available for each Win10Device
260 | "allowTargetFutureDeviceFamilies": {
261 | "Xbox": false,
262 | "Team": false, // Surface Hub
263 | "Holographic": false,
264 | "Desktop": false,
265 | "Mobile": false
266 | },
267 |
268 | // True, Let Microsoft decide whether to make this app available to any future device families.
269 | // False, Require your permission before making this app available to any future device families.
270 | "allowMicrosoftDecideAppAvailabilityToFutureDeviceFamilies": false,
271 |
272 | // EnterpriseLicensing. One of ["None", "Online", "OnlineAndOffline"]
273 | //
274 | // You can allow organizations to acquire your app in volume through the options below.
275 | // Note that changes will only affect new acquisitions; anyone who already has your app
276 | // will be able to continue using it. Initially, only free apps will be offered for volume
277 | // acquisition. Selections you make for paid apps will take effect once support is added
278 | // for acquiring them in volume at a later date.
279 | //
280 | // Online: Make app available to organizations with Store-managed (online) volume licensing.
281 | // This allows organizations to acquire your app in volume. App licenses will be managed
282 | // through the Store's online licensing system.
283 | // OnlineAndOffline: In addition, allow disconnected (offline) licensing for organizations.
284 | // This allows organizations to acquire your app in volume. They can then download your
285 | // package and a license which lets them install it to devices without accessing the
286 | // Store's online licensing system.
287 | "enterpriseLicensing": "None",
288 |
289 | ////////////////////
290 | // //
291 | // APP PROPERTIES //
292 | // //
293 | ////////////////////
294 |
295 | // Category/SubCategory of the submission. Either
296 | // "NotSet"
297 | // or one of:
298 | // ["BooksAndReference", "BooksAndReference_EReader", "BooksAndReference_Fiction", "BooksAndReference_Nonfiction",
299 | // "BooksAndReference_Reference", "Business", "Business_AccountingAndFinance", "Business_Collaboration",
300 | // "Business_CRM", "Business_DataAndAnalytics", "Business_FileManagement", "Business_InventoryAndLogistics",
301 | // "Business_LegalAndHR", "Business_ProjectManagement", "Business_RemoteDesktop", "Business_SalesAndMarketing",
302 | // "Business_TimeAndExpenses", "DeveloperTools", "DeveloperTools_Database", "DeveloperTools_DesignTools",
303 | // "DeveloperTools_DevelopmentKits", "DeveloperTools_Networking", "DeveloperTools_ReferenceAndTraining",
304 | // "DeveloperTools_Servers", "DeveloperTools_Utilities", "DeveloperTools_Webhosting", "Education",
305 | // "Education_BooksAndReference", "Education_EarlyLearning", "Education_InstructionalTools", "Education_Language",
306 | // "Education_StudyAids", "Entertainment", "FoodAndDining", "Games", "Games_ActionAndAdventure",
307 | // "Games_CardAndBoard", "Games_Casino", "Games_Educational", "Games_FamilyAndKids", "Games_Fighting",
308 | // "Games_Music", "Games_Platformer", "Games_PuzzleAndTrivia", "Games_RacingAndFlying", "Games_Roleplaying",
309 | // "Games_Shooter", "Games_Simulation", "Games_Sports", "Games_Strategy", "Games_Word", "GovernmentAndPolitics",
310 | // "HealthAndFitness", "KidsAndFamily", "KidsAndFamily_BooksAndReference", "KidsAndFamily_Entertainment",
311 | // "KidsAndFamily_HobbiesAndToys", "KidsAndFamily_SportsAndActivities", "KidsAndFamily_Travel", "Lifestyle",
312 | // "Lifestyle_Automotive", "Lifestyle_DIY", "Lifestyle_HomeAndGarden", "Lifestyle_Relationships",
313 | // "Lifestyle_SpecialInterest", "Lifestyle_StyleAndFashion", "Medical", "MultimediaDesign",
314 | // "MultimediaDesign_IllustrationAndGraphicDesign", "MultimediaDesign_MusicProduction",
315 | // "MultimediaDesign_PhotoAndVideoProduction", "Music", "NavigationAndMaps", "NewsAndWeather",
316 | // "NewsAndWeather_News", "NewsAndWeather_Weather", "PersonalFinance", "PersonalFinance_BankingAndInvestments",
317 | // "PersonalFinance_BudgetingAndTaxes", "Personalization", "Personalization_RingtonesAndSounds",
318 | // "Personalization_Themes", "Personalization_WallpaperAndLockScreens", "PhotoAndVideo", "Productivity",
319 | // "Security", "Security_PCProtection", "Security_PersonalSecurity", "Shopping", "Social", "Sports", "Travel",
320 | // "Travel_CityGuides", "Travel_Hotels", "UtilitiesAndTools", "UtilitiesAndTools_BackupAndManage"
321 | // "UtilitiesAndTools_FileManager"]
322 | "applicationCategory": "NotSet",
323 |
324 | // Hardware (Preferences)
325 | //
326 | // Any of ["Undefined", "Touch", "Keyboard", "Mouse", "Camera", "Nfc", "NfcHce", "BluetoothLE", "Telephony"]
327 | // Ex: "hardwarePreferences": [
328 | // "Touch",
329 | // "Mouse" <- No comma for last item, or JSON deserialization will fail
330 | // ],
331 |
332 | // Hardware Preferences
333 | //
334 | // Indicate which hardware features are required in order for your app to run properly.
335 | // Customers on hardware that doesn't meet your app's preferences will see a warning before they download your app,
336 | // and they won't be able to rate or review it.
337 | "hardwarePreferences": [
338 | ],
339 |
340 | // Indicates whether the app sells In-App-Products from 3rd parties.
341 | "hasExternalInAppProducts": false,
342 |
343 | // Indicates whether the App meets accessibility guidelines.
344 | "meetAccessibilityGuidelines": false,
345 |
346 | // Indicates whether the app can be installed on removable media.
347 | "canInstallOnRemovableMedia": false,
348 |
349 | // Indicates whether OneDrive backup is enabled on the app instance being submitted.
350 | "automaticBackupEnabled": false,
351 |
352 | // Indicates whether customers can use Windows 10 features to record and broadcast clips of this game.
353 | // Only supported for app in the "Games" category.
354 | "isGameDvrEnabled": false,
355 |
356 | /////////////////////
357 | // //
358 | // GAMING OPTIONS //
359 | // //
360 | /////////////////////
361 |
362 | // Up-to-date documentation for all possible option values can be found here:
363 | // https://docs.microsoft.com/en-us/windows/uwp/monetize/manage-app-submissions#gaming-options-object
364 | "gamingOptions": [{
365 | // An array of one or more of the following strings that describe the genres of the game:
366 | // ["Games_ActionAndAdventure", "Games_CardAndBoard", "Games_Casino", "Games_Educational", "Games_FamilyAndKids",
367 | // "Games_Fighting", "Games_Music", "Games_Platformer", "Games_PuzzleAndTrivia", "Games_RacingAndFlying",
368 | // "Games_RolePlaying", "Games_Shooter", "Games_Simulation", "Games_Sports", "Games_Strategy", "Games_Word"]
369 | "genres": [
370 | ],
371 |
372 | // Indicates whether the game supports local multiplayer.
373 | "isLocalMultiplayer": false,
374 |
375 | // Indicates whether the game supports local co-op.
376 | "isLocalCooperative": false,
377 |
378 | // Indicates whether the game supports online multiplayer.
379 | "isOnlineMultiplayer": false,
380 |
381 | // Indicates whether the game supports online co-op.
382 | "isOnlineCooperative": false,
383 |
384 | // Specifies the minimum number of players the game supports for local multiplayer.
385 | "localMultiplayerMinPlayers": 0,
386 |
387 | // Specifies the maximum number of players the game supports for local multiplayer.
388 | "localMultiplayerMaxPlayers": 0,
389 |
390 | // Specifies the minimum number of players the game supports for local co-op.
391 | "localCooperativeMinPlayers": 0,
392 |
393 | // Specifies the maximum number of players the game supports for local co-op.
394 | "localCooperativeMaxPlayers": 0,
395 |
396 | // Indicates whether the game supports broadcasting.
397 | "isBroadcastingPrivilegeGranted": false,
398 |
399 | // Indicates whether the game supports multiplayer sessions between players on Windows 10 PCs and Xbox.
400 | "isCrossPlayEnabled": false,
401 |
402 | // One of the following string values that indicates whether the game can collect Kinect data and send it to external services:
403 | // ["NotSet", "Unknown", "Enabled", "Disabled"]
404 | "kinectDataForExternal": "Disabled"
405 | }],
406 |
407 | /////////////////////////////
408 | // //
409 | // NOTES FOR CERTIFICATION //
410 | // //
411 | /////////////////////////////
412 |
413 | // String. Information that testers can use to run the app for certification.
414 | // Can include information such as test account credentials, steps to access and verify features, etc.
415 | //
416 | // The notes must be provided on one line, but it is possible to specify multiline notes using "\r\n".
417 | // Ex: "notesForCertification": "These\r\nare\r\nfour\r\nlines"
418 | "notesForCertification": ""
419 | }
420 | }
421 |
--------------------------------------------------------------------------------
/Extensions/ConvertFrom-ExistingIapSubmission.ps1:
--------------------------------------------------------------------------------
1 | # Copyright (C) Microsoft Corporation. All rights reserved.
2 |
3 | <#
4 | .SYNOPSIS
5 | Script for converting an existing In-AppProduct (IAP) submission in the Store
6 | to the January 2017 PDP schema.
7 |
8 | .DESCRIPTION
9 | Script for converting an existing In-AppProduct (IAP) submission in the Store
10 | to the January 2017 PDP schema.
11 |
12 | The Git-repo for the StoreBroker module can be found here: http://aka.ms/StoreBroker
13 |
14 | .PARAMETER IapId
15 | The ID of the IAP that the PDP's will be getting created for.
16 | The most recent submission for this IAP will be used unless a SubmissionId is
17 | explicitly specified.
18 |
19 | .PARAMETER SubmissionId
20 | The ID of the application submission that the PDP's will be getting created for.
21 | The most recent submission for IapId will be used unless a value for this parameter is
22 | provided.
23 |
24 | .PARAMETER SubmissionId
25 | The submission object that you want to convert, which was previously retrieved.
26 |
27 | .PARAMETER Release
28 | The release to use. This value will be placed in each new PDP and used in conjunction with '-OutPath'.
29 | Some examples could be "1601" for a January 2016 release, "March 2016", or even just "1".
30 |
31 | .PARAMETER PdpFileName
32 | The name of the PDP file that will be generated for each region.
33 |
34 | .PARAMETER OutPath
35 | The output directory.
36 | This script will create two subfolders of OutPath:
37 | \PDPs\\
38 | \Images\\
39 | Each of these sub-folders will have region-specific subfolders for their file content.
40 |
41 | .EXAMPLE
42 | .\ConvertFrom-ExistingIapSubmission -IapId 0ABCDEF12345 -Release "March Release" -OutPath "C:\NewPDPs"
43 |
44 | Converts the data from the last published submission for IapId 0ABCDEF12345. The generated files
45 | will use the default name of "PDP.xml" and be located in lang-code specific sub-directories within
46 | c:\NewPDPs.
47 |
48 | .EXAMPLE
49 | .\ConvertFrom-ExistingIapSubmission -IapId 0ABCDEF12345 -SubmissionId 1234567890123456789 -Release "March Release" -PdpFileName "InAppProductDescription.xml" -OutPath "C:\NewPDPs"
50 |
51 | Converts the data from submission 1234567890123456789 for IapId 0ABCDEF12345 (which might be a
52 | published or pending submission). The generated files will be named "InAppProductDescription.xml" and
53 | will be located in lang-code specific sub-directories within c:\NewPDPs.
54 |
55 | .EXAMPLE
56 | .\ConvertFrom-ExistingIapSubmission -Submission $sub -Release "March Release" -OutPath "C:\NewPDPs"
57 |
58 | Converts the data from a submission object that was captured earlier in your PowerShell session.
59 | It might have come from Get-InAppProductSubmission, or it might have been generated some other way.
60 | This method of running the script was created more for debugging purposes, but others may find it
61 | useful. The generated files will use the default name of "PDP.xml" and be located in lang-code
62 | specific sub-directories within c:\NewPDPs.
63 | #>
64 | [CmdletBinding(
65 | SupportsShouldProcess,
66 | DefaultParametersetName = "UseApi")]
67 | param(
68 | [Parameter(
69 | Mandatory,
70 | ParameterSetName = "UseApi",
71 | Position = 0)]
72 | [string] $IapId,
73 |
74 | [Parameter(
75 | ParameterSetName = "UseApi",
76 | Position = 1)]
77 | [string] $SubmissionId = $null,
78 |
79 | [Parameter(
80 | Mandatory,
81 | ParameterSetName = "ProvideSubmission",
82 | Position = 0)]
83 | [PSCustomObject] $Submission = $null,
84 |
85 | [Parameter(Mandatory)]
86 | [string] $Release,
87 |
88 | [string] $PdpFileName = "PDP.xml",
89 |
90 | [Parameter(Mandatory)]
91 | [string] $OutPath
92 | )
93 |
94 | # Import Write-Log
95 | $rootDir = Split-Path -Path $PSScriptRoot -Parent
96 | $helpers = "$rootDir\StoreBroker\Helpers.ps1"
97 | if (-not (Test-Path -Path $helpers -PathType Leaf))
98 | {
99 | throw "Script execution requires Helpers.ps1 which is part of the git repo. Please execute this script from within your cloned repo."
100 | }
101 | . $helpers
102 |
103 | #region Comment Constants
104 | $script:LocIdAttribute = "_locID"
105 | $script:LocIdFormat = "Iap_{0}"
106 | $script:CommentFormat = " _locComment_text=`"{{MaxLength={0}}} {1}`" "
107 | #endregion Comment Constants
108 |
109 | function Add-ToElement
110 | {
111 | <#
112 | .SYNOPSIS
113 | Adds an arbitrary number of comments and attributes to an XmlElement.
114 |
115 | .PARAMETER Element
116 | The XmlElement to be modified.
117 |
118 | .PARAMETER Comment
119 | An array of comments to add to the element.
120 |
121 | .PARAMETER Attribute
122 | A hashtable where the keys are the attribute names and the values are the attribute values.
123 |
124 | .NOTES
125 | If a provided attribute already exists on the Element, the Element will NOT be modified.
126 | It will ONLY be modified if the Element does not have that attribute.
127 |
128 | .EXAMPLE
129 | PS C:\>$xml = [xml] (Get-Content $filePath)
130 | PS C:\>$root = $xml.DocumentElement
131 | PS C:\>Add-ToElement -Element $root -Comment "Comment1", "Comment2" -Attribute @{ "Attrib1"="Val1"; "Attrib2"="Val2" }
132 |
133 | Adds two comments and two attributes to the root element of the XML document.
134 |
135 | #>
136 | param(
137 | [Parameter(Mandatory)]
138 | [System.Xml.XmlElement] $Element,
139 |
140 | [string[]] $Comment,
141 |
142 | [hashtable] $Attribute
143 | )
144 |
145 | if ($Comment.Count -gt 1)
146 | {
147 | # Reverse 'Comment' array in order to preserve order because of nature of 'Prepend'
148 | # Input array is modified in place, no need to capture result
149 | [Array]::Reverse($Comment)
150 | }
151 |
152 | foreach ($text in $Comment)
153 | {
154 | if (-not [String]::IsNullOrWhiteSpace($text))
155 | {
156 | $elem = $Element.OwnerDocument.CreateComment($text)
157 | $Element.PrependChild($elem) | Out-Null
158 | }
159 | }
160 |
161 | foreach ($key in $Attribute.Keys)
162 | {
163 | if ($null -eq $Element.$key)
164 | {
165 | $Element.SetAttribute($key, $Attribute[$key])
166 | }
167 | else
168 | {
169 | $out = "For element $($Element.LocalName), did not create attribute '$key' with value '$($Attribute[$key])' because the attribute already exists."
170 | Write-Log -Message $out -Level Warning
171 | }
172 | }
173 | }
174 |
175 | function Ensure-RootChild
176 | {
177 | <#
178 | .SYNOPSIS
179 | Creates the specified element as a child of the XML root node, only if that element does not exist already.
180 |
181 | .PARAMETER Xml
182 | The XmlDocument to (potentially) modify.
183 |
184 | .PARAMETER Element
185 | The name of the element to existence check.
186 |
187 | .OUTPUTS
188 | XmlElement. Returns a reference to the (possibly newly created) element requested.
189 |
190 | .EXAMPLE
191 | PS C:\>$xml = [xml] (Get-Content $filePath)
192 | PS C:\>Ensure-RootChild -Xml $xml -Element "SomeElement"
193 |
194 | $xml.DocumentElement.SomeElement now exists and is an XmlElement object.
195 | #>
196 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
197 | param(
198 | [Parameter(Mandatory)]
199 | [System.Xml.XmlDocument] $Xml,
200 |
201 | [Parameter(Mandatory)]
202 | [string] $Element
203 | )
204 |
205 | # ProductDescription node
206 | $root = $Xml.DocumentElement
207 |
208 | if ($root.GetElementsByTagName($Element).Count -eq 0)
209 | {
210 | $elem = $Xml.CreateElement($Element, $Xml.DocumentElement.NamespaceURI)
211 | $root.AppendChild($elem) | Out-Null
212 | }
213 |
214 | return $root.GetElementsByTagName($Element)[0]
215 | }
216 |
217 | function Add-Title
218 | {
219 | <#
220 | .SYNOPSIS
221 | Creates the Title node.
222 |
223 | .PARAMETER Xml
224 | The XmlDocument to modify.
225 |
226 | .PARAMETER Listing
227 | The base listing from the submission for a specific Lang.
228 | #>
229 | param(
230 | [Parameter(Mandatory)]
231 | [System.Xml.XmlDocument] $Xml,
232 |
233 | [Parameter(Mandatory)]
234 | [PSCustomObject] $Listing
235 | )
236 |
237 | $elementName = "Title"
238 | $elementNode = Ensure-RootChild -Xml $Xml -Element $elementName
239 | $elementNode.InnerText = $Listing.title
240 |
241 | # Add comment to parent
242 | $maxChars = 100
243 | $paramSet = @{
244 | "Element" = $elementNode;
245 | "Attribute" = @{ $script:LocIdAttribute = ($script:LocIdFormat -f $elementName) };
246 | "Comment" = @(
247 | " [required] ",
248 | ($script:CommentFormat -f $maxChars, "IAP $elementName"))
249 | }
250 |
251 | Add-ToElement @paramSet
252 | }
253 |
254 | function Add-Description
255 | {
256 | <#
257 | .SYNOPSIS
258 | Creates the description node
259 |
260 | .PARAMETER Xml
261 | The XmlDocument to modify.
262 |
263 | .PARAMETER Listing
264 | The base listing from the submission for a specific Lang.
265 | #>
266 | param(
267 | [Parameter(Mandatory)]
268 | [System.Xml.XmlDocument] $Xml,
269 |
270 | [Parameter(Mandatory)]
271 | [PSCustomObject] $Listing
272 | )
273 |
274 | $elementName = "Description"
275 | $elementNode = Ensure-RootChild -Xml $Xml -Element $elementName
276 | $elementNode.InnerText = $Listing.description
277 |
278 | # Add comment to parent
279 | $maxChars = 200
280 | $paramSet = @{
281 | "Element" = $elementNode;
282 | "Attribute" = @{ $script:LocIdAttribute = ($script:LocIdFormat -f $elementName) };
283 | "Comment" = @(
284 | " [optional] ",
285 | ($script:CommentFormat -f $maxChars, "IAP $elementName"))
286 | }
287 |
288 | Add-ToElement @paramSet
289 | }
290 |
291 | function Add-Icon
292 | {
293 | <#
294 | .SYNOPSIS
295 | Creates the icon node.
296 |
297 | .PARAMETER Xml
298 | The XmlDocument to modify.
299 |
300 | .PARAMETER Listing
301 | The base listing from the submission for a specific Lang.
302 |
303 | .OUTPUTS
304 | [String] The path specified for the icon (if available)
305 |
306 | .NOTES
307 | This function is implemented a bit differently than the others.
308 | Icon is an optional element, but if it's specified, the Filename attribute must
309 | be included with a non-empty value. This is fine if the Listing has an icon defined,
310 | but if it doesn't, then we want to include the element, but commented out so that users
311 | know later what they need to add if they wish to include an icon at some future time.
312 | We will create/add the icon node the way we do in all other cases so that we have its XML,
313 | but if we then determine that there is no icon for that listing, we'll convert the element
314 | to its XML string representation that we can add as a comment, and then remove the actual
315 | node.
316 | #>
317 | param(
318 | [Parameter(Mandatory)]
319 | [System.Xml.XmlDocument] $Xml,
320 |
321 | [Parameter(Mandatory)]
322 | [PSCustomObject] $Listing
323 | )
324 |
325 | # For this element, we want the comment above, rather than inside, the element.
326 | $comment = $Xml.CreateComment(' [optional] Specifying an icon is optional. If provided, the icon must be a 300 x 300 png file. ')
327 | $Xml.DocumentElement.AppendChild($comment ) | Out-Null
328 |
329 | $iconFilename = $Listing.icon.fileName
330 |
331 | $elementName = "Icon"
332 | [System.Xml.XmlElement] $elementNode = Ensure-RootChild -Xml $Xml -Element $elementName
333 |
334 | $paramSet = @{
335 | "Element" = $elementNode;
336 | "Attribute" = @{ 'Filename' = $iconFilename };
337 | }
338 |
339 | Add-ToElement @paramSet
340 |
341 | if ($null -eq $iconFilename)
342 | {
343 | # We'll just comment this out for now since it's not being used.
344 | # We do a tiny bit of extra processing to remove the unnecessary xmlns attribute that
345 | # is added to the node when we get the OuterXml.
346 | $iconElementXml = $elementNode.OuterXml -replace 'xmlns="[^"]+"', ""
347 | $comment = $Xml.CreateComment(" $iconElementXml ")
348 | $Xml.DocumentElement.RemoveChild($elementNode) | Out-Null
349 | $Xml.DocumentElement.AppendChild($comment ) | Out-Null
350 | }
351 |
352 | return $iconFilename
353 | }
354 |
355 | function ConvertFrom-Listing
356 | {
357 | <#
358 | .SYNOPSIS
359 | Converts a base listing for an existing submission into a PDP file that conforms with
360 | the January 2017 PDP schema.
361 |
362 | .PARAMETER Listing
363 | The base listing from the submission for the indicated Lang.
364 |
365 | .PARAMETER Lang
366 | The language / region code for the PDP (e.g. "en-us")
367 |
368 | .PARAMETER Release
369 | The release to use. This value will be placed in each new PDP.
370 | Some examples could be "1601" for a January 2016 release, "March 2016", or even just "1".
371 |
372 | .PARAMETER PdpRootPath
373 | The root / base path that all of the language sub-folders will go for the PDP files.
374 |
375 | .PARAMETER FileName
376 | The name of the PDP file that will be generated.
377 |
378 | .OUTPUTS
379 | [String[]] Array of image names that the PDP references
380 |
381 | .EXAMPLE
382 | ConvertFrom-Listing -Listing ($sub.listings."en-us".baseListing) -Lang "en-us" -Release "1701" -PdpRootPath "C:\PDPs\" -FileName "PDP.xml"
383 |
384 | Converts the given "en-us" base listing to the current PDP schema,
385 | and saves it to "c:\PDPs\en-us\PDP.xml"
386 | #>
387 | [CmdletBinding()]
388 | param(
389 | [Parameter(Mandatory)]
390 | [PSCustomObject] $Listing,
391 |
392 | [Parameter(Mandatory)]
393 | [string] $Lang,
394 |
395 | [Parameter(Mandatory)]
396 | [string] $Release,
397 |
398 | [Parameter(Mandatory)]
399 | [string] $PdpRootPath,
400 |
401 | [Parameter(Mandatory)]
402 | [string] $FileName
403 | )
404 |
405 | $xml = [xml]([String]::Format('
406 | ', $Lang, $Release))
411 |
412 | Add-Title -Xml $Xml -Listing $Listing
413 | Add-Description -Xml $Xml -Listing $Listing
414 | $icon = Add-Icon -Xml $Xml -Listing $Listing
415 |
416 | $imageNames = @()
417 | $imageNames += $icon
418 |
419 | # Save XML object to file
420 | $filePath = Ensure-PdpFilePath -PdpRootPath $PdpRootPath -Lang $Lang -FileName $FileName
421 | $xml.Save($filePath)
422 |
423 | # Post-process the file to ensure CRLF (sometimes is only LF).
424 | $content = Get-Content -Encoding UTF8 -Path $filePath
425 | $content -join [Environment]::NewLine | Out-File -Force -Encoding utf8 -FilePath $filePath
426 |
427 | return $imageNames
428 | }
429 |
430 | function Ensure-PdpFilePath
431 | {
432 | <#
433 | .SYNOPSIS
434 | Ensures that the containing folder for a PDP file that will be generated exists so that
435 | it can successfully be written.
436 |
437 | .DESCRIPTION
438 | Ensures that the containing folder for a PDP file that will be generated exists so that
439 | it can successfully be written.
440 |
441 | .PARAMETER PdpRootPath
442 | The root / base path that all of the language sub-folders will go for the PDP files.
443 |
444 | .PARAMETER Lang
445 | The language / region code for the PDP (e.g. "en-us")
446 |
447 | .PARAMETER FileName
448 | The name of the PDP file that will be generated.
449 |
450 | .EXAMPLE
451 | Ensure-PdpFilePath -PdpRootPath "C:\PDPs\" -Lang "en-us" -FileName "PDP.xml"
452 |
453 | Ensures that the path c:\PDPs\en-us\ exists, creating any sub-folder along the way as
454 | necessary, and then returns the path "c:\PDPs\en-us\PDP.xml"
455 |
456 | .OUTPUTS
457 | [String] containing the full path to the PDP file.
458 | #>
459 | [CmdletBinding()]
460 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Best description for purpose")]
461 | param(
462 | [Parameter(Mandatory)]
463 | [string] $PdpRootPath,
464 |
465 | [string] $Lang,
466 |
467 | [string] $FileName
468 | )
469 |
470 | $dropFolder = Join-Path -Path $PdpRootPath -ChildPath $Lang
471 | if (-not (Test-Path -PathType Container -Path $dropFolder))
472 | {
473 | New-Item -Force -ItemType Directory -Path $dropFolder | Out-Null
474 | }
475 |
476 | return (Join-Path -Path $dropFolder -ChildPath $FileName)
477 | }
478 |
479 | function Show-ImageFileNames
480 | {
481 | <#
482 | .SYNOPSIS
483 | Informs the user what the image filenames are that they need to make available to StoreBroker.
484 |
485 | .DESCRIPTION
486 | Informs the user what the image filenames are that they need to make available to StoreBroker.
487 |
488 | .PARAMETER LangImageNames
489 | A hashtable, indexed by langcode, containing an array of image names that the listing
490 | for that langcode references.
491 |
492 | .PARAMETER Release
493 | The release name that was added to the PDP files.
494 |
495 | .EXAMPLE
496 | Show-ImageFileNames -LangImageNames $langImageNames -Release "1701"
497 | #>
498 | [CmdletBinding()]
499 | [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "", Justification="The most common scenario is that there will be multiple images, not a singular image.")]
500 | param(
501 | [Parameter(Mandatory)]
502 | [hashtable] $LangImageNames,
503 |
504 | [Parameter(Mandatory)]
505 | [string] $Release
506 | )
507 |
508 | # If there are no screenshots, nothing to do here
509 | if ($LangImageNames.Count -eq 0)
510 | {
511 | return
512 | }
513 |
514 | # If there are no images being used at all, then we can also early return
515 | $imageCount = 0
516 | foreach ($lang in $LangImageNames.GetEnumerator())
517 | {
518 | $imageCount += $lang.Value.Count
519 | }
520 |
521 | if ($imageCount.Count -eq 0)
522 | {
523 | return
524 | }
525 |
526 | Write-Log -Message @(
527 | "You now need to find all of your images and place them here: \$Release\\...",
528 | " where is the path defined in your config file,",
529 | " and is the same langcode for the directory of the PDP file referencing those images.")
530 |
531 | # Quick analysis to help teams out if they need to do anything special with their PDP's
532 |
533 | $langs = $LangImageNames.Keys | ConvertTo-Array
534 | $seenImages = $LangImageNames[$langs[0]]
535 | $imagesDiffer = $false
536 | for ($i = 1; ($i -lt $langs.Count) -and (-not $imagesDiffer); $i++)
537 | {
538 | if (($LangImageNames[$langs[$i]].Count -ne $seenImages.Count))
539 | {
540 | $imagesDiffer = $true
541 | break
542 | }
543 |
544 | foreach ($image in $LangImageNames[$langs[$i]])
545 | {
546 | if ($seenImages -notcontains $image)
547 | {
548 | $imagesDiffer = $true
549 | break
550 | }
551 | }
552 | }
553 |
554 | # Now show the user the image filenames
555 | if ($imagesDiffer)
556 | {
557 | Write-Log -Level Warning -Message @(
558 | "It appears that you don't have consistent images across all languages.",
559 | "While StoreBroker supports this scenario, some localization systems may",
560 | "not support this without additional work. Please refer to the FAQ in",
561 | "the documentation for more info on how to best handle this scenario.")
562 |
563 | $output = @()
564 | $output += "The currently referenced image filenames, per langcode, are as follows:"
565 | foreach ($langCode in ($LangImageNames.Keys.GetEnumerator() | Sort-Object))
566 | {
567 | $output += " * [$langCode]: " + ($LangImageNames.$langCode -join ", ")
568 | }
569 |
570 | Write-Log -Message $output
571 | }
572 | else
573 | {
574 | Write-Log -Message @(
575 | "Every language that has a PDP references the following images:",
576 | "`t$($seenImages -join `"`n`t`")")
577 | }
578 | }
579 |
580 | # function Main is invoked at the bottom of the file
581 | function Main
582 | {
583 | [CmdletBinding()]
584 | param()
585 |
586 | if ($null -eq (Get-Module StoreBroker))
587 | {
588 | $message = "The StoreBroker module is not available in this PowerShell session. Please import the module, authenticate correctly using Set-StoreBrokerAuthentication, and try again."
589 | throw $message
590 | }
591 |
592 | $sub = $Submission
593 | if ($null -eq $sub)
594 | {
595 | if ([String]::IsNullOrEmpty($SubmissionId))
596 | {
597 | $iap = Get-InAppProduct -IapId $IapId
598 | $SubmissionId = $iap.lastPublishedInAppProductSubmission.id
599 | if ([String]::IsNullOrEmpty($SubmissionId))
600 | {
601 | $SubmissionId = $iap.pendingInAppProductSubmission.id
602 | Write-Log -Message "No published submission exists for this In-App Product. Using the current pending submission." -Level Warning
603 | }
604 | }
605 |
606 | $sub = Get-InAppProductSubmission -IapId $IapId -SubmissionId $SubmissionId
607 | }
608 |
609 | $langImageNames = @{}
610 | $langs = ($sub.listings | Get-Member -type NoteProperty)
611 | $pdpsGenerated = 0
612 | $langs |
613 | ForEach-Object {
614 | $lang = $_.Name
615 | Write-Log -Message "Creating PDP for $lang" -Level Verbose
616 | Write-Progress -Activity "Generating PDP" -Status $lang -PercentComplete $(($pdpsGenerated / $langs.Count) * 100)
617 | try
618 | {
619 | $imageNames = ConvertFrom-Listing -Listing ($sub.listings.$lang) -Lang $lang -Release $Release -PdpRootPath $OutPath -FileName $PdpFileName
620 | $langImageNames[$lang] = $imageNames
621 | $pdpsGenerated++
622 | }
623 | catch
624 | {
625 | Write-Log -Message "Error creating [$lang] PDP:" -Exception $_ -Level Error
626 | throw
627 | }
628 | }
629 |
630 | if ($pdpsGenerated -gt 0)
631 | {
632 | Write-Log -Message "PDP's have been created here: $OutPath"
633 | Show-ImageFileNames -LangImageNames $langImageNames -Release $Release
634 | }
635 | else
636 | {
637 | Write-Log -Level Warning -Message @(
638 | "No PDPs were generated.",
639 | "Please verify that this existing In-App Product has one or more language listings that this extension can convert,",
640 | "otherwise you can start fresh using the sample PDP\InAppProductDescription.xml as a starting point.")
641 | }
642 | }
643 |
644 |
645 |
646 |
647 | # Script body
648 | $OutPath = Resolve-UnverifiedPath -Path $OutPath
649 |
650 | # function Main invocation
651 | Main
652 |
--------------------------------------------------------------------------------