├── .gitignore ├── LICENSE ├── README.md └── src ├── AzurePipelines └── azure-pipelines.yml ├── Git └── remove-stale-branches.ps1 ├── Identity └── AzureADB2C.cs ├── LinqPad ├── BulkMembers.linq.cs ├── CourierRebuildFilesMinorVersionEndpoint.linq ├── Readme.md └── RestoreContentFromCacheFile.linq.cs ├── LoadTesting ├── LoadTest.cs ├── Readme.md ├── loadtest-lb.jmx └── loadtest.jmx ├── Powershell ├── UmbracoCloud │ ├── Git.ps1 │ ├── GitHub.ps1 │ ├── MSBuild.ps1 │ ├── Nuget.ps1 │ ├── UmbracoCloud.ps1 │ ├── UmbracoCloud.psd1 │ └── UmbracoDeploy.ps1 └── UmbracoPackageMaker.ps1 ├── Sql └── QueryCorruptedPropertyData.sql └── Web ├── ASPNetFileMonitorList.cshtml └── global.asax /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Shannon Deminick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UmbracoScripts 2 | 3 | A place to keep some potentially helpful Umbraco scripts for testing/development 4 | 5 | All folders contain their own readme files for instructions. 6 | -------------------------------------------------------------------------------- /src/AzurePipelines/azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # ************** REQUIREMENTS ************** 2 | 3 | # This file should be committed to the root of your Git repository 4 | 5 | # You must have the DeployToUmbracoCloud.ps1 file committed to the ./Build folder of your 6 | # Git repository. The file can be found here: http://bit.ly/33kTiJG 7 | 8 | # Azure Pipelines variables: 9 | # * gitAddress = The full Git https endpoint for your Dev environment on Umbraco Cloud 10 | # * gitUsername = Your Umbraco Cloud email address 11 | # * gitPassword = Your Umbraco Cloud password - ensure this value is set to Secret 12 | 13 | trigger: 14 | branches: 15 | include: 16 | - master 17 | - refs/tags/* 18 | tags: 19 | include: 20 | - release-* 21 | 22 | pool: 23 | vmImage: 'windows-latest' 24 | 25 | variables: 26 | # This builds all sln files, this could be updated to target what you want to build 27 | solution: '**/*.sln' 28 | # Typically none of these should be changed! 29 | buildPlatform: 'Any CPU' 30 | buildConfiguration: 'Release' 31 | publishDir: '$(build.artifactStagingDirectory)\_Publish' 32 | cloneDir: '$(build.artifactStagingDirectory)\_UmbracoCloud' 33 | zipDir: '$(build.artifactStagingDirectory)\_Zip' 34 | deployScript: 'DeployToUmbracoCloud.ps1' 35 | 36 | stages: 37 | - stage: Build 38 | jobs: 39 | - job: RestoreBuildPublish 40 | steps: 41 | - task: NuGetToolInstaller@1 42 | 43 | - task: NuGetCommand@2 44 | inputs: 45 | restoreSolution: '$(solution)' 46 | feedsToUse: 'config' 47 | nugetConfigPath: 'src/Nuget.config' 48 | 49 | # Build the VS Solution and publish using the old Web application publishing msbuild target 50 | - task: VSBuild@1 51 | inputs: 52 | solution: '$(solution)' 53 | msbuildArgs: /p:UseWPP_CopyWebApplication=True /p:PipelineDependsOnBuild=False /p:PublishProfile=ToFileSys.pubxml /p:DeployOnBuild=true /p:AutoParameterizationWebConfigConnectionStrings=False /p:PublishOutDir=$(publishDir) 54 | platform: '$(buildPlatform)' 55 | configuration: '$(buildConfiguration)' 56 | 57 | # Zip the output 58 | - task: ArchiveFiles@2 59 | inputs: 60 | rootFolderOrFile: '$(publishDir)' 61 | includeRootFolder: false 62 | archiveType: 'zip' 63 | archiveFile: '$(zipDir)/$(Build.BuildId).zip' 64 | replaceExistingArchive: true 65 | 66 | # publish the zipped out website 67 | - task: PublishBuildArtifacts@1 68 | inputs: 69 | PathtoPublish: '$(zipDir)' 70 | ArtifactName: 'zip' 71 | publishLocation: 'Container' 72 | 73 | # publish the Umbraco Deploy build file 74 | - task: PublishBuildArtifacts@1 75 | inputs: 76 | pathtoPublish: '$(build.sourcesDirectory)\build\$(deployScript)' 77 | artifactName: 'buildScript' 78 | publishLocation: 'Container' 79 | 80 | - stage: Deploy 81 | # Only run when there is a release-* tag 82 | condition: contains(variables['Build.SourceBranch'], 'tags/release-') 83 | jobs: 84 | - deployment: DeployToCloud 85 | # Track executed jobs against an "Umbraco Cloud", if it doesnt exist it will be created 86 | environment: Umbraco Cloud 87 | strategy: 88 | runOnce: 89 | deploy: 90 | steps: 91 | - task: ExtractFiles@1 92 | inputs: 93 | # A deployment task automatically downloads artifacts published to this folder 94 | archiveFilePatterns: '$(Agent.BuildDirectory)\zip\*.zip' 95 | destinationFolder: '$(publishDir)' 96 | cleanDestinationFolder: true 97 | # Run the powershell script to create a new Git commit and push to Umbraco Cloud 98 | - powershell: $(Agent.BuildDirectory)\buildScript\$(deployScript) -cloneurl "$(gitAddress)" -uaasuser "$(gitUsername)" -password "$(gitPassword)" -sourcepath "$(publishDir)" -destinationpath "$(cloneDir)" 99 | failOnStderr: true 100 | enabled: true 101 | -------------------------------------------------------------------------------- /src/Git/remove-stale-branches.ps1: -------------------------------------------------------------------------------- 1 | # Really great article on cleaning up git branches: 2 | # http://railsware.com/blog/2014/08/11/git-housekeeping-tutorial-clean-up-outdated-branches-in-local-and-remote-repositories/ 3 | 4 | # But... some of this stuff is so linuxy I cannot read it. 5 | # What I want to be able to do is delete all local branches that are 6 | # merged into my current branch (i.e. dev-v7) 7 | # but also exclude any master-* or dev-* branch names. 8 | # You can do this with those linuxy scripts, or use Powershell :) 9 | 10 | # To list all branches that are merged that are not master- or dev- 11 | # you can run this: 12 | 13 | git branch --merged | 14 | ForEach-Object { $_.Trim() } | 15 | Where-Object {$_ -NotMatch "^\*"} | 16 | Where-Object {-not ( $_ -Like "master-*" )} | 17 | Where-Object {-not ( $_ -Like "dev-*" )} | 18 | ForEach-Object { "branch = $_" } 19 | 20 | # To delete all of these local branches, you can do: 21 | 22 | git branch --merged | 23 | ForEach-Object { $_.Trim() } | 24 | Where-Object {$_ -NotMatch "^\*"} | 25 | Where-Object {-not ( $_ -Like "master-*" )} | 26 | Where-Object {-not ( $_ -Like "dev-*" )} | 27 | ForEach-Object { git branch -d $_ } 28 | 29 | # whoohoo! clean repo :) 30 | -------------------------------------------------------------------------------- /src/Identity/AzureADB2C.cs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Extension methods used to configure Azure AD B2C OAuth provider for the Umbraco Back Office 3 | /// - This is more or less working but there's many 'questions' I have regarding a few things (see notes) 4 | /// - Parts of this example could work for front-end member implementation with the removal of the /umbraco callback 5 | /// paths and the auto-link stuff. 6 | /// 7 | /// 8 | /// 24 | /// 25 | public static class UmbracoADAuthExtensions 26 | { 27 | 28 | //SEE: https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-devquickstarts-web-dotnet/ 29 | 30 | // The ACR claim is used to indicate which policy was executed 31 | public const string AcrClaimType = "http://schemas.microsoft.com/claims/authnclassreference"; 32 | public const string PolicyKey = "b2cpolicy"; 33 | public const string OIDCMetadataSuffix = "/.well-known/openid-configuration"; 34 | public const string AADInstance = "https://login.microsoftonline.com/{0}{1}{2}"; 35 | 36 | public static void ConfigureBackOfficeAzureActiveDirectoryA2BAuth(this IAppBuilder app, 37 | string tenant, 38 | string clientId, 39 | string clientSecret, 40 | string redirectUri, 41 | string signUpPolicyId, 42 | string signInPolicyId, 43 | string userProfilePolicyId, 44 | 45 | string adminClientId, 46 | string adminClientSecret, 47 | 48 | //Guid issuerId, 49 | string caption = "Active Directory", string style = "btn-microsoft", string icon = "fa-windows") 50 | { 51 | 52 | //ORIGINAL OPTIONS SUPPLIED BY SAMPLE B2C APP 53 | 54 | //var options = new OpenIdConnectAuthenticationOptions 55 | //{ 56 | // // These are standard OpenID Connect parameters, with values pulled from web.config 57 | // ClientId = clientId, 58 | // RedirectUri = redirectUri, 59 | // PostLogoutRedirectUri = redirectUri, 60 | // Notifications = new OpenIdConnectAuthenticationNotifications 61 | // { 62 | // AuthenticationFailed = AuthenticationFailed, 63 | // RedirectToIdentityProvider = OnRedirectToIdentityProvider, 64 | // }, 65 | // Scope = "openid", 66 | // ResponseType = "id_token", 67 | 68 | // // The PolicyConfigurationManager takes care of getting the correct Azure AD authentication 69 | // // endpoints from the OpenID Connect metadata endpoint. It is included in the PolicyAuthHelpers folder. 70 | // ConfigurationManager = new PolicyConfigurationManager( 71 | // string.Format(CultureInfo.InvariantCulture, AADInstance, tenant, "/v2.0", OIDCMetadataSuffix), 72 | // new string[] { signUpPolicyId, signInPolicyId, userProfilePolicyId }), 73 | 74 | // // This piece is optional - it is used for displaying the user's name in the navigation bar. 75 | // TokenValidationParameters = new TokenValidationParameters 76 | // { 77 | // NameClaimType = "name" 78 | // }, 79 | //}; 80 | 81 | var adOptions = new OpenIdConnectAuthenticationOptions 82 | { 83 | ClientId = clientId, 84 | RedirectUri = redirectUri, 85 | PostLogoutRedirectUri = redirectUri, 86 | Notifications = new OpenIdConnectAuthenticationNotifications 87 | { 88 | //AuthenticationFailed = AuthenticationFailed, 89 | RedirectToIdentityProvider = OnRedirectToIdentityProvider, 90 | 91 | ////When the user is authorized and we are not asking for an id_token (see way below for details on that), 92 | //// we will get an auth code which we can then use to retrieve information about the user. 93 | //AuthorizationCodeReceived = async notification => 94 | //{ 95 | // // The user's objectId is extracted from the claims provided in the id_token, and used to cache tokens in ADAL 96 | // // The authority is constructed by appending your B2C directory's name to "https://login.microsoftonline.com/" 97 | // // The client credential is where you provide your application secret, and is used to authenticate the application to Azure AD 98 | // var userObjectId = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; 99 | // var authority = string.Format(CultureInfo.InvariantCulture, AADInstance, tenant, string.Empty, string.Empty); 100 | // var credential = new ClientCredential(clientId, clientSecret); 101 | 102 | // // We don't care which policy is used to access the TaskService, so let's use the most recent policy as indicated in the sign-in token 103 | // var mostRecentPolicy = notification.AuthenticationTicket.Identity.FindFirst(AcrClaimType).Value; 104 | 105 | // // The Authentication Context is ADAL's primary class, which represents your connection to your B2C directory 106 | // // ADAL uses an in-memory token cache by default. In this case, we've extended the default cache to use a simple per-user session cache 107 | // var authContext = new AuthenticationContext(authority, new NaiveSessionCache(userObjectId)); 108 | 109 | // // Here you ask for a token using the web app's clientId as the scope, since the web app and service share the same clientId. 110 | // // The token will be stored in the ADAL token cache, for use in our controllers 111 | // var result = await authContext.AcquireTokenByAuthorizationCodeAsync(notification.Code, new Uri(redirectUri), credential, new string[] { clientId }, mostRecentPolicy); 112 | 113 | // //var userDetails = await GetUserByObjectId(result, userObjectId, tenant, clientId, clientSecret); 114 | 115 | // var asdf = result; 116 | //}, 117 | MessageReceived = notification => 118 | { 119 | return Task.FromResult(0); 120 | }, 121 | SecurityTokenReceived = notification => 122 | { 123 | return Task.FromResult(0); 124 | }, 125 | SecurityTokenValidated = notification => 126 | { 127 | //var userObjectId = notification.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; 128 | //var authority = string.Format(CultureInfo.InvariantCulture, AADInstance, tenant, string.Empty, string.Empty); 129 | //var credential = new ClientCredential(clientId, clientSecret); 130 | 131 | // We don't care which policy is used to access the TaskService, so let's use the most recent policy 132 | //var mostRecentPolicy = notification.AuthenticationTicket.Identity.FindFirst(AcrClaimType).Value; 133 | 134 | // Here you ask for a token using the web app's clientId as the scope, since the web app and service share the same clientId. 135 | // AcquireTokenSilentAsync will return a token from the token cache, and throw an exception if it cannot do so. 136 | //var authContext = new AuthenticationContext(authority, new NaiveSessionCache(userObjectId)); 137 | 138 | //var result = await authContext.AcquireTokenSilentAsync(new string[] { clientId }, credential, UserIdentifier.AnyUser, mostRecentPolicy); 139 | 140 | // Here you ask for a token using the web app's clientId as the scope, since the web app and service share the same clientId. 141 | // The token will be stored in the ADAL token cache, for use in our controllers 142 | //var result = await authContext.AcquireTokenByAuthorizationCodeAsync(notification.Code, new Uri(redirectUri), credential, new string[] { clientId }, mostRecentPolicy); 143 | 144 | //var asdf = result; 145 | 146 | 147 | 148 | //The returned identity doesn't actually have 'email' as a claim, but instead has a collection of "emails", so we're going to ensure one is 149 | // in there and then set the Email claim to be the first so that auto-signin works 150 | var emails = notification.AuthenticationTicket.Identity.FindFirst("emails"); 151 | if (emails != null) 152 | { 153 | var email = emails.Value; 154 | notification.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Email, email)); 155 | } 156 | 157 | 158 | return Task.FromResult(0); 159 | } 160 | }, 161 | 162 | //NOTE: in this article they are requesting this scope: https://azure.microsoft.com/en-us/documentation/articles/active-directory-b2c-devquickstarts-web-api-dotnet/ 163 | // I'm unsure if we leave off the offline_access part if we'd get an authcode request back or not, so leaving it here 164 | // for now since it is working. 165 | //Scope = "openid offline_access", 166 | 167 | Scope = "openid", 168 | 169 | //NOTE: If we ask for this, then we'll simply get an ID Token back which we cannot use to request 170 | // additional data of the user. We need to get an authorization code reponse (I'm not sure what the 171 | // string value for that is but if we don't specify then it's the default). 172 | ResponseType = "id_token", 173 | 174 | // The PolicyConfigurationManager takes care of getting the correct Azure AD authentication 175 | // endpoints from the OpenID Connect metadata endpoint. It is included in the PolicyAuthHelpers folder. 176 | // The first parameter is the metadata URL of your B2C directory 177 | // The second parameter is an array of the policies that your app will use. 178 | ConfigurationManager = new PolicyConfigurationManager( 179 | string.Format(CultureInfo.InvariantCulture, AADInstance, tenant, "/v2.0", OIDCMetadataSuffix), 180 | new string[] { signUpPolicyId, signInPolicyId, userProfilePolicyId }), 181 | 182 | // This piece is optional - it is used for displaying the user's name in the navigation bar. 183 | TokenValidationParameters = new TokenValidationParameters 184 | { 185 | NameClaimType = "name", 186 | }, 187 | 188 | SignInAsAuthenticationType = Umbraco.Core.Constants.Security.BackOfficeExternalAuthenticationType 189 | }; 190 | 191 | adOptions.SetChallengeResultCallback(context => new AuthenticationProperties( 192 | new Dictionary 193 | { 194 | {UmbracoADAuthExtensions.PolicyKey, signInPolicyId} 195 | }) 196 | { 197 | RedirectUri = "/Umbraco", 198 | }); 199 | 200 | var orig = adOptions.AuthenticationType; 201 | adOptions.ForUmbracoBackOffice(style, icon); 202 | adOptions.AuthenticationType = orig; 203 | 204 | adOptions.Caption = caption; 205 | 206 | //NOTE: This needs to be set after the ForUmbracoBackOffice 207 | // this needs to be set to what AD returns otherwise you cannot unlink an account 208 | adOptions.AuthenticationType = string.Format( 209 | CultureInfo.InvariantCulture, 210 | "https://login.microsoftonline.com/{0}/v2.0/", 211 | //Not sure where this comes from! ... perhaps 'issuerId', but i don't know where to find this, 212 | // i just know this based on the response we get from B2C 213 | "ae25bf5e-871e-454a-a1b6-a3560a09ec5e"); 214 | 215 | //This will auto-create users based on the authenticated user if they are new 216 | //NOTE: This needs to be set after the explicit auth type is set 217 | adOptions.SetExternalSignInAutoLinkOptions(new ExternalSignInAutoLinkOptions(autoLinkExternalAccount: true)); 218 | 219 | app.UseOpenIdConnectAuthentication(adOptions); 220 | } 221 | 222 | // This notification can be used to manipulate the OIDC request before it is sent. Here we use it to send the correct policy. 223 | private static async Task OnRedirectToIdentityProvider(RedirectToIdentityProviderNotification notification) 224 | { 225 | PolicyConfigurationManager mgr = notification.Options.ConfigurationManager as PolicyConfigurationManager; 226 | if (notification.ProtocolMessage.RequestType == OpenIdConnectRequestType.LogoutRequest) 227 | { 228 | OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseRevoke.Properties.Dictionary[UmbracoADAuthExtensions.PolicyKey]); 229 | notification.ProtocolMessage.IssuerAddress = config.EndSessionEndpoint; 230 | } 231 | else 232 | { 233 | OpenIdConnectConfiguration config = await mgr.GetConfigurationByPolicyAsync(CancellationToken.None, notification.OwinContext.Authentication.AuthenticationResponseChallenge.Properties.Dictionary[UmbracoADAuthExtensions.PolicyKey]); 234 | notification.ProtocolMessage.IssuerAddress = config.AuthorizationEndpoint; 235 | } 236 | } 237 | 238 | //// Used for avoiding yellow-screen-of-death 239 | //private static Task AuthenticationFailed(AuthenticationFailedNotification notification) 240 | //{ 241 | // notification.HandleResponse(); 242 | // notification.Response.Redirect("/Home/Error?message=" + notification.Exception.Message); 243 | // return Task.FromResult(0); 244 | //} 245 | 246 | public static async Task GetUserByObjectId(AuthenticationResult authResult, string objectId, string tenant, string adminClientId, string adminClientSecret) 247 | { 248 | return await SendGraphGetRequest(authResult, "/users/" + objectId, null, tenant, adminClientId, adminClientSecret); 249 | } 250 | 251 | public static async Task SendGraphGetRequest(AuthenticationResult authResult, string api, string query, string tenant, string adminClientId, string adminClientSecret) 252 | { 253 | var authContext = new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext("https://login.microsoftonline.com/" + tenant); 254 | var credential = new Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential(adminClientId, adminClientSecret); 255 | 256 | // Here you ask for a token using the web app's clientId as the scope, since the web app and service share the same clientId. 257 | // AcquireTokenSilentAsync will return a token from the token cache, and throw an exception if it cannot do so. 258 | //var authContext = new AuthenticationContext(authority, new NaiveSessionCache(userObjectId)); 259 | 260 | //// We don't care which policy is used to access the TaskService, so let's use the most recent policy 261 | //var mostRecentPolicy = authTicket.Identity.FindFirst(AcrClaimType).Value; 262 | //var result = await authContext.AcquireTokenSilentAsync(new string[] { clientId }, credential, UserIdentifier.AnyUser, mostRecentPolicy); 263 | 264 | //// First, use ADAL to acquire a token using the app's identity (the credential) 265 | //// The first parameter is the resource we want an access_token for; in this case, the Graph API. 266 | var result = authContext.AcquireToken("https://graph.windows.net", credential); 267 | 268 | // For B2C user managment, be sure to use the beta Graph API version. 269 | var http = new HttpClient(); 270 | var url = "https://graph.windows.net/" + tenant + api + "?" + "api-version=beta"; 271 | if (!string.IsNullOrEmpty(query)) 272 | { 273 | url += "&" + query; 274 | } 275 | 276 | //Console.ForegroundColor = ConsoleColor.Cyan; 277 | //Console.WriteLine("GET " + url); 278 | //Console.WriteLine("Authorization: Bearer " + result.AccessToken.Substring(0, 80) + "..."); 279 | //Console.WriteLine(""); 280 | 281 | // Append the access token for the Graph API to the Authorization header of the request, using the Bearer scheme. 282 | var request = new HttpRequestMessage(HttpMethod.Get, url); 283 | //request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.Token); 284 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken); 285 | var response = await http.SendAsync(request); 286 | 287 | if (!response.IsSuccessStatusCode) 288 | { 289 | string error = await response.Content.ReadAsStringAsync(); 290 | object formatted = JsonConvert.DeserializeObject(error); 291 | throw new WebException("Error Calling the Graph API: \n" + JsonConvert.SerializeObject(formatted, Formatting.Indented)); 292 | } 293 | 294 | //Console.ForegroundColor = ConsoleColor.Green; 295 | //Console.WriteLine((int)response.StatusCode + ": " + response.ReasonPhrase); 296 | //Console.WriteLine(""); 297 | 298 | return await response.Content.ReadAsStringAsync(); 299 | } 300 | 301 | } 302 | 303 | public class NaiveSessionCache : TokenCache 304 | { 305 | private static readonly object FileLock = new object(); 306 | string UserObjectId = string.Empty; 307 | string CacheId = string.Empty; 308 | public NaiveSessionCache(string userId) 309 | { 310 | UserObjectId = userId; 311 | CacheId = UserObjectId + "_TokenCache"; 312 | 313 | this.AfterAccess = AfterAccessNotification; 314 | this.BeforeAccess = BeforeAccessNotification; 315 | Load(); 316 | } 317 | 318 | public void Load() 319 | { 320 | lock (FileLock) 321 | { 322 | this.Deserialize((byte[])HttpContext.Current.Session[CacheId]); 323 | } 324 | } 325 | 326 | public void Persist() 327 | { 328 | lock (FileLock) 329 | { 330 | // reflect changes in the persistent store 331 | HttpContext.Current.Session[CacheId] = this.Serialize(); 332 | // once the write operation took place, restore the HasStateChanged bit to false 333 | this.HasStateChanged = false; 334 | } 335 | } 336 | 337 | // Empties the persistent store. 338 | public override void Clear() 339 | { 340 | base.Clear(); 341 | System.Web.HttpContext.Current.Session.Remove(CacheId); 342 | } 343 | 344 | public override void DeleteItem(TokenCacheItem item) 345 | { 346 | base.DeleteItem(item); 347 | Persist(); 348 | } 349 | 350 | // Triggered right before ADAL needs to access the cache. 351 | // Reload the cache from the persistent store in case it changed since the last access. 352 | void BeforeAccessNotification(TokenCacheNotificationArgs args) 353 | { 354 | Load(); 355 | } 356 | 357 | // Triggered right after ADAL accessed the cache. 358 | void AfterAccessNotification(TokenCacheNotificationArgs args) 359 | { 360 | // if the access operation resulted in a cache update 361 | if (this.HasStateChanged) 362 | { 363 | Persist(); 364 | } 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/LinqPad/BulkMembers.linq.cs: -------------------------------------------------------------------------------- 1 | //How many to create? 2 | var count = 10000; 3 | 4 | var memberType = ApplicationContext.Services.MemberTypeService.GetAll() 5 | .OrderBy(x => x.Name) 6 | .First(x => x.Name.StartsWith("_") == false); 7 | 8 | ("Creating with member type: " + memberType.Name).Dump(); 9 | 10 | for(var i = 0;i< count;i++) 11 | { 12 | var id = "BM_" + i + Guid.NewGuid().ToString("N"); 13 | var member = ApplicationContext.Services.MemberService.CreateMemberWithIdentity(id, id + "@bm.com", id, memberType); 14 | ("Created member: " + id).Dump(); 15 | } 16 | 17 | "Done".Dump(); -------------------------------------------------------------------------------- /src/LinqPad/CourierRebuildFilesMinorVersionEndpoint.linq: -------------------------------------------------------------------------------- 1 | 2 | <RuntimeDirectory>\System.Net.Http.dll 3 | System.Net.Http 4 | 5 | 6 | var url = "http://local-shantest-carlsberg-group.rainbowsrock.net/umbraco/Upgrades/MinorVersionUpgradeService/PostPerformRebuild "; 7 | //var url = "http://local-shantest-carlsberg-group.rainbowsrock.net/umbraco/Upgrades/MinorVersionUpgradeService/PostPerformSoftRebuild "; 8 | var _httpClient = new System.Net.Http.HttpClient(); 9 | 10 | var byteArray = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "shannon@umbraco.com", "L!GsCCecl24IEE!L")); 11 | var header = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); 12 | 13 | _httpClient.DefaultRequestHeaders.Authorization = header; 14 | _httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json; charset=utf-8"); 15 | _httpClient.DefaultRequestHeaders.Add("X-UmbracoCoreMinorVersionUpgrade", "Hello"); 16 | 17 | var response = await _httpClient.PostAsync(url, null); 18 | response.Dump(); -------------------------------------------------------------------------------- /src/LinqPad/Readme.md: -------------------------------------------------------------------------------- 1 | #LinqPad scripts 2 | 3 | These scripts are useful for working with the LinqPad Umbraco driver (https://github.com/Shazwazza/UmbracoLinqPadDriver) -------------------------------------------------------------------------------- /src/LinqPad/RestoreContentFromCacheFile.linq.cs: -------------------------------------------------------------------------------- 1 | 2 | var contentXmlFile = new FileInfo("X:\\TEMP\\umbraco.config"); 3 | if (!contentXmlFile.Exists) 4 | throw new InvalidOperationException("No file found"); 5 | 6 | var contentService = ApplicationContext.Services.ContentService; 7 | 8 | var xmlDocument = XDocument.Load(contentXmlFile.FullName); 9 | foreach (var xmlContentItem in xmlDocument.Root.XPathSelectElements("//*[@isDoc]")) 10 | { 11 | var id = int.Parse(xmlContentItem.Attribute("id").Value); 12 | ("Processing xml content item ID " + id).Dump(); 13 | 14 | var found = contentService.GetById(id); 15 | if (found == null) 16 | { 17 | ("No content found by id " + id).Dump(); 18 | continue; 19 | } 20 | 21 | foreach (var xmlProperty in xmlContentItem.XPathSelectElements("./*[not(@isDoc)]")) 22 | { 23 | var propertyAlias = xmlProperty.Name.LocalName; 24 | ("Processing property with alias " + propertyAlias).Dump(); 25 | 26 | found.SetValue(propertyAlias, xmlProperty.Value); 27 | } 28 | 29 | "Saving content item".Dump(); 30 | 31 | var result = contentService.SaveAndPublishWithStatus(found); 32 | 33 | result.Result.Dump(); 34 | } 35 | -------------------------------------------------------------------------------- /src/LoadTesting/LoadTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Linq; 4 | using System.Web.Mvc; 5 | using Umbraco.Core.Services; 6 | using Umbraco.Core.Models; 7 | using System.Web; 8 | using System.Web.Hosting; 9 | using System.Web.Routing; 10 | using System.Diagnostics; 11 | using Umbraco.Core.Composing; 12 | 13 | // need: this file, Zbu.WebManagement, Interop.mscoree.dll 14 | // hit /LoadTest on a fresh install 15 | 16 | // restarts: either set a % on the create calls, or call restart 17 | 18 | namespace Zbu.LoadTest 19 | { 20 | public class LoadTestController : Controller 21 | { 22 | public LoadTestController(ServiceContext serviceContext) 23 | { 24 | _serviceContext = serviceContext; 25 | } 26 | 27 | private static readonly Random _random = new Random(); 28 | private static readonly object _locko = new object(); 29 | 30 | private static volatile int _containerId = -1; 31 | 32 | private const string _containerAlias = "LoadTestContainer"; 33 | private const string _contentAlias = "LoadTestContent"; 34 | private const int _textboxDefinitionId = -88; 35 | private const int _maxCreate = 1000; 36 | 37 | private static readonly string HeadHtml = @" 38 | 39 | LoadTest 40 | 50 | 51 | 52 |
53 |

LoadTest

54 |
" + System.Configuration.ConfigurationManager.AppSettings["umbracoConfigurationStatus"] + @"
55 |
56 | "; 57 | 58 | private const string FootHtml = @" 59 | "; 60 | 61 | private static readonly string _containerTemplateText = @" 62 | @inherits Umbraco.Web.Mvc.UmbracoViewPage 63 | @{ 64 | Layout = null; 65 | var container = Umbraco.ContentAtRoot().OfTypes(""" + _containerAlias + @""").FirstOrDefault(); 66 | var contents = container.Children().ToArray(); 67 | var groups = contents.GroupBy(x => x.Value(""origin"")); 68 | var id = contents.Length > 0 ? contents[0].Id : -1; 69 | var wurl = Request.QueryString[""u""] == ""1""; 70 | var missing = contents.Length > 0 && contents[contents.Length - 1].Id - contents[0].Id >= contents.Length; 71 | } 72 | " + HeadHtml + @" 73 |
74 | @contents.Length items 75 |
    76 | @foreach (var group in groups) 77 | { 78 |
  • @group.Key: @group.Count()
  • 79 | } 80 |
81 |
82 | @foreach (var content in contents) 83 | { 84 | while (content.Id > id) 85 | { 86 |
@id :: MISSING
87 | id++; 88 | } 89 | if (wurl) 90 | { 91 |
@content.Id :: @content.Name :: @content.Url
92 | } 93 | else 94 | { 95 |
@content.Id :: @content.Name
96 | } id++; 97 | } 98 |
99 | " + FootHtml; 100 | private readonly ServiceContext _serviceContext; 101 | 102 | private ActionResult ContentHtml(string s) 103 | { 104 | return Content(HeadHtml + s + FootHtml); 105 | } 106 | 107 | public ActionResult Index() 108 | { 109 | var res = EnsureInitialize(); 110 | if (res != null) return res; 111 | 112 | var html = @"Welcome. You can: 113 | 122 | "; 123 | 124 | return ContentHtml(html); 125 | } 126 | 127 | private ActionResult EnsureInitialize() 128 | { 129 | if (_containerId > 0) return null; 130 | 131 | lock (_locko) 132 | { 133 | if (_containerId > 0) return null; 134 | 135 | var contentTypeService = _serviceContext.ContentTypeService; 136 | var contentType = contentTypeService.Get(_contentAlias); 137 | if (contentType == null) 138 | return ContentHtml("Not installed, first you must install."); 139 | 140 | var containerType = contentTypeService.Get(_containerAlias); 141 | if (containerType == null) 142 | return ContentHtml("Panic! Container type is missing."); 143 | 144 | var contentService = _serviceContext.ContentService; 145 | var container = contentService.GetPagedOfType(containerType.Id, 0, 100, out _, null).FirstOrDefault(); 146 | if (container == null) 147 | return ContentHtml("Panic! Container is missing."); 148 | 149 | _containerId = container.Id; 150 | return null; 151 | } 152 | } 153 | 154 | public ActionResult Install() 155 | { 156 | var dataTypeService = _serviceContext.DataTypeService; 157 | 158 | //var dataType = dataTypeService.GetAll(Constants.DataTypes.DefaultContentListView); 159 | 160 | 161 | //if (!dict.ContainsKey("pageSize")) dict["pageSize"] = new PreValue("10"); 162 | //dict["pageSize"].Value = "200"; 163 | //dataTypeService.SavePreValues(dataType, dict); 164 | 165 | var contentTypeService = _serviceContext.ContentTypeService; 166 | 167 | var contentType = new ContentType(-1) 168 | { 169 | Alias = _contentAlias, 170 | Name = "LoadTest Content", 171 | Description = "Content for LoadTest", 172 | Icon = "icon-document" 173 | }; 174 | var def = _serviceContext.DataTypeService.GetDataType(_textboxDefinitionId); 175 | contentType.AddPropertyType(new PropertyType(def) 176 | { 177 | Name = "Origin", 178 | Alias = "origin", 179 | Description = "The origin of the content.", 180 | }); 181 | contentTypeService.Save(contentType); 182 | 183 | var containerTemplate = ImportTemplate(_serviceContext, 184 | "LoadTestContainer", "LoadTestContainer", _containerTemplateText); 185 | 186 | var containerType = new ContentType(-1) 187 | { 188 | Alias = _containerAlias, 189 | Name = "LoadTest Container", 190 | Description = "Container for LoadTest content", 191 | Icon = "icon-document", 192 | AllowedAsRoot = true, 193 | IsContainer = true 194 | }; 195 | containerType.AllowedContentTypes = containerType.AllowedContentTypes.Union(new[] 196 | { 197 | new ContentTypeSort(new Lazy(() => contentType.Id), 0, contentType.Alias), 198 | }); 199 | containerType.AllowedTemplates = containerType.AllowedTemplates.Union(new[] { containerTemplate }); 200 | containerType.SetDefaultTemplate(containerTemplate); 201 | contentTypeService.Save(containerType); 202 | 203 | var contentService = _serviceContext.ContentService; 204 | var content = contentService.Create("LoadTestContainer", -1, _containerAlias); 205 | contentService.SaveAndPublish(content); 206 | 207 | return ContentHtml("Installed."); 208 | } 209 | 210 | public ActionResult Create(int n = 1, int r = 0, string o = null) 211 | { 212 | var res = EnsureInitialize(); 213 | if (res != null) return res; 214 | 215 | if (r < 0) r = 0; 216 | if (r > 100) r = 100; 217 | var restart = GetRandom(0, 100) > (100 - r); 218 | 219 | var contentService = _serviceContext.ContentService; 220 | 221 | if (n < 1) n = 1; 222 | if (n > _maxCreate) n = _maxCreate; 223 | for (int i = 0; i < n; i++) 224 | { 225 | var name = Guid.NewGuid().ToString("N").ToUpper() + "-" + (restart ? "R" : "X") + "-" + o; 226 | var content = contentService.Create(name, _containerId, _contentAlias); 227 | content.SetValue("origin", o); 228 | contentService.SaveAndPublish(content); 229 | } 230 | 231 | if (restart) 232 | DoRestart(); 233 | 234 | return ContentHtml("Created " + n + " content" 235 | + (restart ? ", and restarted" : "") 236 | + "."); 237 | } 238 | 239 | private int GetRandom(int minValue, int maxValue) 240 | { 241 | lock (_locko) 242 | { 243 | return _random.Next(minValue, maxValue); 244 | } 245 | } 246 | 247 | public ActionResult Clear() 248 | { 249 | var res = EnsureInitialize(); 250 | if (res != null) return res; 251 | 252 | var contentType = _serviceContext.ContentTypeService.Get(_contentAlias); 253 | _serviceContext.ContentService.DeleteOfType(contentType.Id); 254 | 255 | return ContentHtml("Cleared."); 256 | } 257 | 258 | private void DoRestart() 259 | { 260 | HttpContext.User = null; 261 | System.Web.HttpContext.Current.User = null; 262 | Thread.CurrentPrincipal = null; 263 | HttpRuntime.UnloadAppDomain(); 264 | } 265 | 266 | public ActionResult Restart() 267 | { 268 | DoRestart(); 269 | 270 | return ContentHtml("Restarted."); 271 | } 272 | 273 | public ActionResult Die() 274 | { 275 | var timer = new System.Threading.Timer(_ => 276 | { 277 | throw new Exception("die!"); 278 | }); 279 | timer.Change(100, 0); 280 | 281 | return ContentHtml("Dying."); 282 | } 283 | 284 | public ActionResult Domains() 285 | { 286 | var currentDomain = AppDomain.CurrentDomain; 287 | var currentName = currentDomain.FriendlyName; 288 | var pos = currentName.IndexOf('-'); 289 | if (pos > 0) currentName = currentName.Substring(0, pos); 290 | 291 | var text = new System.Text.StringBuilder(); 292 | text.Append("
Process ID: " + Process.GetCurrentProcess().Id + "
"); 293 | text.Append("
"); 294 | text.Append("
IIS Site: " + HostingEnvironment.ApplicationHost.GetSiteName() + "
"); 295 | text.Append("
App ID: " + currentName + "
"); 296 | //text.Append("
AppPool: " + Zbu.WebManagement.AppPoolHelper.GetCurrentApplicationPoolName() + "
"); 297 | text.Append("
"); 298 | 299 | text.Append("
Domains:
    "); 300 | text.Append("
  • Not implemented.
  • "); 301 | /* 302 | foreach (var domain in Zbu.WebManagement.AppDomainHelper.GetAppDomains().OrderBy(x => x.Id)) 303 | { 304 | var name = domain.FriendlyName; 305 | pos = name.IndexOf('-'); 306 | if (pos > 0) name = name.Substring(0, pos); 307 | text.Append("
  • " 311 | +"[" + domain.Id + "] " + name 312 | + (domain.IsDefaultAppDomain() ? " (default)" : "") 313 | + (domain.Id == currentDomain.Id ? " (current)" : "") 314 | + "
  • "); 315 | } 316 | */ 317 | text.Append("
"); 318 | 319 | return ContentHtml(text.ToString()); 320 | } 321 | 322 | public ActionResult Recycle() 323 | { 324 | return ContentHtml("Not implemented—please use IIS console."); 325 | } 326 | 327 | private static Template ImportTemplate(ServiceContext svces, string name, string alias, string text, ITemplate master = null) 328 | { 329 | var t = new Template(name, alias) { Content = text }; 330 | if (master != null) 331 | t.SetMasterTemplate(master); 332 | svces.FileService.SaveTemplate(t); 333 | return t; 334 | } 335 | } 336 | 337 | public class TestComponent : IComponent 338 | { 339 | public void Initialize() 340 | { 341 | RouteTable.Routes.MapRoute( 342 | name: "LoadTest", 343 | //url: "umbraco/backoffice/zbqrtbnk/LoadTest/{action}"; 344 | url: "LoadTest/{action}", 345 | defaults: new 346 | { 347 | controller = "LoadTest", 348 | action = "Index" 349 | }, 350 | namespaces: new[] { "Zbu.LoadTest" } 351 | ); 352 | } 353 | 354 | public void Terminate() 355 | { 356 | } 357 | } 358 | 359 | public class TestComposer : ComponentComposer, IUserComposer 360 | { 361 | public override void Compose(Composition composition) 362 | { 363 | base.Compose(composition); 364 | 365 | composition.Register(typeof(LoadTestController), Lifetime.Request); 366 | } 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/LoadTesting/Readme.md: -------------------------------------------------------------------------------- 1 | # Load testing utils 2 | 3 | ## Listing, creating & restarting 4 | 5 | loadtest.jmx is an Apache JMeter file which is used to trigger the local load testing. 6 | It requires that the LoadTest.cs file exist in ~/App_Code or is compiled as part of the application. 7 | 8 | These tests will test how Umbraco behaves when there are a lot of threads that are both listing data, creating new data and shutting down the app domain. 9 | Very usefuly for testing how cache, lucene, etc... behaves with restarts and publishing. 10 | 11 | The controller is: LoadTestController and is routed /LoadTest and will display a menu. 12 | -------------------------------------------------------------------------------- /src/LoadTesting/loadtest-lb.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | continue 16 | 17 | false 18 | 64 19 | 20 | 16 21 | 1 22 | 1429204789000 23 | 1429204789000 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | localhost 34 | 37620 35 | 36 | 37 | 38 | 39 | 40 | 4 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | /LoadTestContainer?u=0&nodata=1 54 | GET 55 | true 56 | false 57 | true 58 | false 59 | false 60 | 61 | 62 | 63 | 64 | 1 65 | true 66 | 1 67 | 68 | ThroughputController.percentThroughput 69 | 20.0 70 | 0.0 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | /LoadTest/Create?o=jmeter&r=0 85 | GET 86 | true 87 | false 88 | true 89 | false 90 | false 91 | 92 | 93 | 94 | 95 | 96 | 1 97 | true 98 | 1 99 | 100 | ThroughputController.percentThroughput 101 | 2.0 102 | 0.0 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | /LoadTest/Restart 117 | GET 118 | true 119 | false 120 | true 121 | false 122 | false 123 | 124 | 125 | 126 | 127 | 128 | 1 129 | true 130 | 1 131 | 132 | ThroughputController.percentThroughput 133 | 0.5 134 | 0.0 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | /LoadTest/Die 149 | GET 150 | true 151 | false 152 | true 153 | false 154 | false 155 | 156 | 157 | 158 | 159 | 160 | true 161 | 162 | saveConfig 163 | 164 | 165 | true 166 | true 167 | true 168 | 169 | true 170 | true 171 | true 172 | true 173 | false 174 | true 175 | true 176 | false 177 | false 178 | true 179 | false 180 | false 181 | false 182 | false 183 | false 184 | 0 185 | true 186 | true 187 | 188 | 189 | 190 | 191 | 192 | 193 | false 194 | 195 | saveConfig 196 | 197 | 198 | true 199 | true 200 | true 201 | 202 | true 203 | true 204 | true 205 | true 206 | false 207 | true 208 | true 209 | false 210 | false 211 | true 212 | false 213 | false 214 | false 215 | false 216 | false 217 | 0 218 | true 219 | true 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | continue 228 | 229 | false 230 | 64 231 | 232 | 16 233 | 1 234 | 1429204789000 235 | 1429204789000 236 | false 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | localhost 246 | 32603 247 | 248 | 249 | 250 | 251 | 252 | 4 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | /LoadTestContainer?u=0&nodata=1 266 | GET 267 | true 268 | false 269 | true 270 | false 271 | false 272 | 273 | 274 | 275 | 276 | 1 277 | true 278 | 1 279 | 280 | ThroughputController.percentThroughput 281 | 8.0 282 | 0.0 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | /LoadTest/Create?o=jmeter&r=30 297 | GET 298 | true 299 | false 300 | true 301 | false 302 | false 303 | 304 | 305 | 306 | 307 | 308 | 1 309 | true 310 | 1 311 | 312 | ThroughputController.percentThroughput 313 | 2.0 314 | 2.0 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | /LoadTest/Restart 329 | GET 330 | true 331 | false 332 | true 333 | false 334 | false 335 | 336 | 337 | 338 | 339 | 340 | 1 341 | true 342 | 1 343 | 344 | ThroughputController.percentThroughput 345 | 0.5 346 | 0.5 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | /LoadTest/Die 361 | GET 362 | true 363 | false 364 | true 365 | false 366 | false 367 | 368 | 369 | 370 | 371 | 372 | true 373 | 374 | saveConfig 375 | 376 | 377 | true 378 | true 379 | true 380 | 381 | true 382 | true 383 | true 384 | true 385 | false 386 | true 387 | true 388 | false 389 | false 390 | true 391 | false 392 | false 393 | false 394 | false 395 | false 396 | 0 397 | true 398 | true 399 | 400 | 401 | 402 | 403 | 404 | 405 | false 406 | 407 | saveConfig 408 | 409 | 410 | true 411 | true 412 | true 413 | 414 | true 415 | true 416 | true 417 | true 418 | false 419 | true 420 | true 421 | false 422 | false 423 | true 424 | false 425 | false 426 | false 427 | false 428 | false 429 | 0 430 | true 431 | true 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | -------------------------------------------------------------------------------- /src/LoadTesting/loadtest.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | continue 16 | 17 | false 18 | 128 19 | 20 | 32 21 | 1 22 | 1429204789000 23 | 1429204789000 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | umbracotest.dev 34 | 35 | 36 | 37 | 38 | 39 | 40 | 4 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | /LoadTestContainer?u=0&nodata=1 54 | GET 55 | true 56 | false 57 | true 58 | false 59 | false 60 | 61 | 62 | 63 | 64 | 1 65 | true 66 | 1 67 | 68 | ThroughputController.percentThroughput 69 | 8.0 70 | 0.0 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | /LoadTest/Create?o=jmeter&r=30 85 | GET 86 | true 87 | false 88 | true 89 | false 90 | false 91 | 92 | 93 | 94 | 95 | 96 | 1 97 | true 98 | 1 99 | 100 | ThroughputController.percentThroughput 101 | 2.0 102 | 0.0 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | /LoadTest/Restart 117 | GET 118 | true 119 | false 120 | true 121 | false 122 | false 123 | 124 | 125 | 126 | 127 | 128 | 1 129 | true 130 | 1 131 | 132 | ThroughputController.percentThroughput 133 | 0.5 134 | 0.0 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | /LoadTest/Die 149 | GET 150 | true 151 | false 152 | true 153 | false 154 | false 155 | 156 | 157 | 158 | 159 | 160 | true 161 | 162 | saveConfig 163 | 164 | 165 | true 166 | true 167 | true 168 | 169 | true 170 | true 171 | true 172 | true 173 | false 174 | true 175 | true 176 | false 177 | false 178 | true 179 | false 180 | false 181 | false 182 | false 183 | false 184 | 0 185 | true 186 | true 187 | 188 | 189 | 190 | 191 | 192 | 193 | false 194 | 195 | saveConfig 196 | 197 | 198 | true 199 | true 200 | true 201 | 202 | true 203 | true 204 | true 205 | true 206 | false 207 | true 208 | true 209 | false 210 | false 211 | true 212 | false 213 | false 214 | false 215 | false 216 | false 217 | 0 218 | true 219 | true 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /src/Powershell/UmbracoCloud/Git.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-Git 2 | { 3 | <# 4 | .Synopsis 5 | Invoke git, handling its quirky stderr that isn't error 6 | 7 | .PARAMETER IsBoolean 8 | If this switch is specified, then it will treat the $LASTEXITCODE as a boolean result 9 | and the method will return a boolean true if it is 0 or false if it is 1 10 | 11 | .Outputs 12 | Git messages 13 | 14 | .Example 15 | Invoke-Git push 16 | 17 | .Example 18 | Invoke-Git "add ." 19 | #> 20 | 21 | param( 22 | [Parameter(Mandatory)] 23 | [string] $Command, 24 | 25 | [switch] $IsBoolean 26 | ) 27 | 28 | try { 29 | # I could do this in the main script just once, but then the caller would have to know to do that 30 | # in every script where they use this function. 31 | $old_env = $env:GIT_REDIRECT_STDERR 32 | $env:GIT_REDIRECT_STDERR = '2>&1' 33 | 34 | Write-Verbose "Executing: git $Command ..." 35 | $output = Invoke-Expression "git $Command " 36 | 37 | if ($LASTEXITCODE -gt 0) 38 | { 39 | if ($IsBoolean) 40 | { 41 | return $false 42 | } 43 | 44 | # note: No catch below (only the try/finally). Let the caller handle the exception. 45 | Throw "Error Encountered executing: 'git $Command '" 46 | } 47 | else 48 | { 49 | if ($IsBoolean) 50 | { 51 | return $true 52 | } 53 | 54 | # because $output probably has miultiple lines (array of strings), by piping it to write-verbose we get multiple lines. 55 | # Cannot pipe Write-Information currently, see https://github.com/PowerShell/PowerShell/issues/2559 56 | $output | Write-Verbose 57 | } 58 | } 59 | # note: No catch here. Let the caller handle it. 60 | finally 61 | { 62 | $env:GIT_REDIRECT_STDERR = $old_env 63 | } 64 | } 65 | 66 | function Push-GitChanges 67 | { 68 | [CmdletBinding(DefaultParameterSetName = 'None')] 69 | param( 70 | [Parameter(Mandatory)] 71 | [string] $BranchName 72 | ) 73 | 74 | Invoke-Git "push -u origin `"$BranchName`"" 75 | 76 | return $? 77 | } 78 | 79 | function Add-GitChanges 80 | { 81 | <# 82 | .SYNOPSIS 83 | Commit Git changes 84 | .DESCRIPTION 85 | Commit Git changes 86 | .PARAMETER Message 87 | The commit message 88 | #> 89 | [CmdletBinding(DefaultParameterSetName = 'None')] 90 | param( 91 | [Parameter(Mandatory)] 92 | [string] $Message 93 | ) 94 | 95 | Invoke-Git "add -A" 96 | Invoke-Git "commit -am `"$Message`" --author `"Friendly Upgrade Bot `"" 97 | return $? 98 | } 99 | 100 | function Switch-GitBranch 101 | { 102 | <# 103 | .SYNOPSIS 104 | Checkout a Git branch 105 | .DESCRIPTION 106 | Checkout a Git branch 107 | .PARAMETER BranchName 108 | The branch name to checkout 109 | #> 110 | [CmdletBinding(DefaultParameterSetName = 'None')] 111 | param( 112 | [Parameter(Mandatory)] 113 | [string] $BranchName 114 | ) 115 | 116 | Invoke-Git "checkout `"$BranchName`"" 117 | return $? 118 | } 119 | 120 | function Get-GitBranchExists 121 | { 122 | [CmdletBinding(DefaultParameterSetName = 'None')] 123 | param( 124 | [Parameter(Mandatory)] 125 | [string] $BranchName 126 | ) 127 | 128 | $result = Invoke-Git -Command "show-ref --verify --quiet `"refs/heads/$($BranchName)`"" -IsBoolean 129 | return $result 130 | } 131 | 132 | function New-GitBranch 133 | { 134 | [CmdletBinding(DefaultParameterSetName = 'None')] 135 | param( 136 | [Parameter(Mandatory)] 137 | [string] $BranchName 138 | ) 139 | 140 | Invoke-Git "branch $BranchName" 141 | if($LASTEXITCODE -eq 0) { 142 | Invoke-Git "checkout $BranchName" 143 | if($LASTEXITCODE -eq 0) { 144 | Invoke-Git "checkout $BranchName" 145 | } 146 | else { 147 | throw "Git command failed" 148 | } 149 | } 150 | else { 151 | throw "Git command failed" 152 | } 153 | } 154 | 155 | function Copy-CloudRepo 156 | { 157 | <# 158 | .SYNOPSIS 159 | Clone an Umbraco Cloud repository 160 | .DESCRIPTION 161 | Clone an Umbraco Cloud repository 162 | .PARAMETER GitClonePath 163 | The physical path to clone the repository 164 | .PARAMETER GitUsername 165 | Username to use for authenticating 166 | .PARAMETER GitPassword 167 | Password to use for authenticating 168 | .PARAMETER GitAddress 169 | The Git endpoint 170 | #> 171 | [CmdletBinding(DefaultParameterSetName = 'None')] 172 | param( 173 | [Parameter(Mandatory)] 174 | [string] $GitClonePath, 175 | 176 | [Parameter(Mandatory)] 177 | [string] $GitUsername, 178 | 179 | [Parameter(Mandatory)] 180 | [string] $GitPassword, 181 | 182 | [Parameter(Mandatory)] 183 | [string] $GitAddress 184 | ) 185 | 186 | #Ensure the path that the Umbraco Cloud repository will be cloned to 187 | New-Item -ItemType Directory -Force -Path $GitClonePath 188 | 189 | Add-Type -AssemblyName System.Web 190 | 191 | $UsernameEncoded = [System.Web.HttpUtility]::UrlEncode($GitUsername) 192 | $UmbCloudPasswordEncoded = [System.Web.HttpUtility]::UrlEncode($GitPassword) 193 | $currentRemoteUri = New-Object System.Uri $GitAddress 194 | $newRemoteUrlBuilder = New-Object System.UriBuilder($currentRemoteUri) 195 | $newRemoteUrlBuilder.UserName = $UsernameEncoded 196 | $newRemoteUrlBuilder.Password = $UmbCloudPasswordEncoded 197 | $gitAuthenticatedUrl = $newRemoteUrlBuilder.ToString() 198 | Invoke-Git "clone `"${gitAuthenticatedUrl}`" `"${GitClonePath}`"" 199 | } 200 | -------------------------------------------------------------------------------- /src/Powershell/UmbracoCloud/GitHub.ps1: -------------------------------------------------------------------------------- 1 | function Get-CurrentPackageVersion 2 | { 3 | [CmdletBinding(DefaultParameterSetName = 'None')] 4 | param( 5 | [Parameter(Mandatory)] 6 | [string] $OwnerName, 7 | 8 | [Parameter(Mandatory)] 9 | [string] $RepositoryName, 10 | 11 | [Parameter(Mandatory)] 12 | [string] $AccessToken, 13 | 14 | [Parameter(Mandatory)] 15 | [string] $PackageFile, 16 | 17 | [Parameter(Mandatory)] 18 | [string] $PackageName 19 | ) 20 | 21 | Add-Type -AssemblyName System.Xml.Linq 22 | 23 | # TODO: We could auth everything like this, else we can pass the access token to each method 24 | 25 | # $secureString = "ACCESSTOKENHERE" | ConvertTo-SecureString -AsPlainText -Force 26 | # $cred = New-Object System.Management.Automation.PSCredential "", $secureString 27 | # Set-GitHubAuthentication -Credential $cred -SessionOnly 28 | 29 | Set-GitHubConfiguration -DisableTelemetry -DisableLogging 30 | 31 | $contentResult = Get-GitHubContent -OwnerName $OwnerName -RepositoryName $RepositoryName -Path $PackageFile -MediaType Raw -AccessToken $AccessToken 32 | $xmlStream = New-Object System.IO.MemoryStream 33 | $xmlStream.Write($contentResult, 0, $contentResult.Length) 34 | $xmlStream.Position = 0 35 | $reader = New-Object System.Xml.XmlTextReader($xmlStream) 36 | $xml = [System.Xml.Linq.XDocument]::Load($reader) 37 | $reader.Dispose() 38 | $xmlStream.Dispose() 39 | 40 | # Write-Verbose "package.config output: $xml" 41 | 42 | $xpath = "string(//package[@id='$PackageName']/@version)" 43 | $packageVersion = [string][System.Xml.XPath.Extensions]::XPathEvaluate($xml, $xpath); 44 | 45 | Write-Verbose "$PackageName version = $packageVersion" 46 | 47 | return $packageVersion.ToString() 48 | } 49 | 50 | function Get-PullRequest 51 | { 52 | [CmdletBinding(DefaultParameterSetName = 'None')] 53 | param( 54 | [Parameter(Mandatory)] 55 | [string] $OwnerName, 56 | 57 | [Parameter(Mandatory)] 58 | [string] $RepositoryName, 59 | 60 | [Parameter(Mandatory)] 61 | [string] $AccessToken, 62 | 63 | [Parameter(Mandatory)] 64 | [string] $BranchName 65 | ) 66 | 67 | Set-GitHubConfiguration -DisableTelemetry -DisableLogging 68 | 69 | $pullRequests = Get-GitHubPullRequest -OwnerName $OwnerName -RepositoryName $RepositoryName -AccessToken $AccessToken -Head "$($OwnerName):$($BranchName)" 70 | return $pullRequests 71 | } 72 | 73 | function New-PullRequest 74 | { 75 | [CmdletBinding(DefaultParameterSetName = 'None')] 76 | param( 77 | [Parameter(Mandatory)] 78 | [string] $OwnerName, 79 | 80 | [Parameter(Mandatory)] 81 | [string] $RepositoryName, 82 | 83 | [Parameter(Mandatory)] 84 | [string] $AccessToken, 85 | 86 | [Parameter(Mandatory)] 87 | [string] $PackageVersion, 88 | 89 | [Parameter(Mandatory)] 90 | [string] $PackageName, 91 | 92 | [Parameter(Mandatory)] 93 | [string] $BranchName 94 | ) 95 | 96 | Set-GitHubConfiguration -DisableTelemetry -DisableLogging 97 | 98 | $prParams = @{ 99 | OwnerName = $OwnerName 100 | Repository = $RepositoryName 101 | Title = "$PackageName $PackageVersion Update" 102 | Head = "$($OwnerName):$($BranchName)" 103 | Base = 'master' 104 | Body = "The Friendly Upgrade Bot has an update ready for you." 105 | MaintainerCanModify = $true 106 | AccessToken = $AccessToken 107 | } 108 | $pr = New-GitHubPullRequest @prParams 109 | 110 | return $pr 111 | } -------------------------------------------------------------------------------- /src/Powershell/UmbracoCloud/MSBuild.ps1: -------------------------------------------------------------------------------- 1 | function Build-Project 2 | { 3 | [CmdletBinding(DefaultParameterSetName = 'None')] 4 | param( 5 | 6 | [Parameter(Mandatory)] 7 | [string] $ProjectFile, 8 | 9 | [Parameter(Mandatory)] 10 | [string] $MSBuildExe 11 | ) 12 | 13 | & $MSBuildExe $ProjectFile 14 | } 15 | 16 | function Get-MSBuildExe 17 | { 18 | [CmdletBinding(DefaultParameterSetName = 'None')] 19 | param( 20 | [Parameter(Mandatory)] 21 | [string] $DestinationFolder, 22 | 23 | [Parameter(Mandatory)] 24 | [string] $NugetExe 25 | ) 26 | 27 | (New-Item -ItemType Directory -Force -Path $DestinationFolder) | Out-Null 28 | (New-Item "$DestinationFolder\vswhere" -type directory -force) | Out-Null 29 | 30 | $path = "$DestinationFolder\vswhere" 31 | $vswhere = "$DestinationFolder\vswhere.exe" 32 | if (-not (test-path $vswhere)) 33 | { 34 | Write-Verbose "Download VsWhere..." 35 | &$NugetExe install vswhere -OutputDirectory $path -Verbosity quiet 36 | $dir = ls "$path\vswhere.*" | sort -property Name -descending | select -first 1 37 | $file = ls -path "$dir" -name vswhere.exe -recurse 38 | mv "$dir\$file" $vswhere 39 | } 40 | 41 | $MSBuild = &$vswhere -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1 42 | if (-not (test-path $MSBuild)) { 43 | throw "MSBuild not found!" 44 | } 45 | 46 | return $MSBuild 47 | } 48 | -------------------------------------------------------------------------------- /src/Powershell/UmbracoCloud/Nuget.ps1: -------------------------------------------------------------------------------- 1 | function Get-NugetExe { 2 | [CmdletBinding(DefaultParameterSetName = 'None')] 3 | param( 4 | [Parameter(Mandatory)] 5 | [string] $DestinationFolder 6 | ) 7 | 8 | (New-Item -ItemType Directory -Force -Path $DestinationFolder) | Out-Null 9 | 10 | $sourceNugetExe = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 11 | $nugetExePath = Join-Path $DestinationFolder "nuget.exe" 12 | 13 | if (-not (Test-Path $nugetExePath -PathType Leaf)) { 14 | Invoke-WebRequest $sourceNugetExe -OutFile $nugetExePath 15 | } 16 | 17 | return $nugetExePath 18 | } 19 | 20 | function Update-NugetPackage { 21 | [CmdletBinding(DefaultParameterSetName = 'None')] 22 | param( 23 | [Parameter(Mandatory)] 24 | [string] $PackageName, 25 | 26 | [Parameter(Mandatory)] 27 | [string] $PackageVersion, 28 | 29 | [Parameter(Mandatory)] 30 | [string] $ProjectFile, 31 | 32 | [Parameter(Mandatory)] 33 | [string] $RootGitDirectory, 34 | 35 | [Parameter(Mandatory)] 36 | [string] $NugetExe, 37 | 38 | [Parameter(Mandatory)] 39 | [string] $MSBuildPath, 40 | 41 | [switch] $Safe 42 | ) 43 | 44 | $projFile = Get-Item $ProjectFile 45 | if ($projFile.Exists -eq $false) { 46 | throw "The project file does not exist $ProjectFile" 47 | } 48 | 49 | Write-Verbose "Running Nuget restore/update for package $PackageName ..." 50 | 51 | $MSBuildPath = "$($MSBuildPath.TrimEnd('\\'))" 52 | 53 | $nugetConfigFile = Find-NugetConfig -CurrentDirectory $($projFile.Directory.FullName) -RootGitDirectory $RootGitDirectory 54 | $nugetConfigFilePath = $nugetConfigFile.FullName 55 | 56 | # The folder where packages will be downloaded to which by convention is always 57 | # in the /packages folder relative to the nuget.config file. 58 | $packagesPath = Join-Path $($nugetConfigFile.Directory.FullName) "packages" 59 | 60 | # First we need to do a nuget restore 61 | $nugetResult = Invoke-NugetRestore -NugetExe "$NugetExe" -ProjectFile "$ProjectFile" -NugetConfigFile "$nugetConfigFilePath" -PackagesPath "$packagesPath" -MSBuildPath "$MSBuildPath" 62 | if ($nugetResult -eq $true) { 63 | # Then we can do a nuget update 64 | Invoke-NugetUpdate -NugetExe "$NugetExe" -PackageName "$PackageName" -PackageVersion "$PackageVersion" -ProjectFile "$ProjectFile" -NugetConfigFile "$nugetConfigFilePath" -PackagesPath "$packagesPath" -MSBuildPath "$MSBuildPath" -Safe:$Safe 65 | } 66 | } 67 | 68 | function Invoke-NugetRestore { 69 | [CmdletBinding(DefaultParameterSetName = 'None')] 70 | param( 71 | [Parameter(Mandatory)] 72 | [string] $NugetExe, 73 | 74 | [Parameter(Mandatory)] 75 | [string] $ProjectFile, 76 | 77 | [Parameter(Mandatory)] 78 | [string] $NugetConfigFile, 79 | 80 | [Parameter(Mandatory)] 81 | [string] $PackagesPath, 82 | 83 | [Parameter(Mandatory)] 84 | [string] $MSBuildPath 85 | ) 86 | 87 | if ((Get-Item $NugetExe).Exists -eq $false) { 88 | throw "The Nuget exe file does not exist $NugetExe" 89 | } 90 | 91 | Write-Verbose "Running Nuget restore..." 92 | 93 | & $NugetExe restore "$ProjectFile" -ConfigFile "$NugetConfigFile" -PackagesDirectory "$PackagesPath" -Project2ProjectTimeOut 20 -NonInteractive -MSBuildPath "$MSBuildPath" 94 | 95 | if ($LASTEXITCODE -eq 0) { 96 | return $true 97 | } 98 | else { 99 | throw "An error occurred, quitting" 100 | } 101 | } 102 | 103 | function Invoke-NugetUpdate { 104 | [CmdletBinding(DefaultParameterSetName = 'None')] 105 | param( 106 | [Parameter(Mandatory)] 107 | [string] $NugetExe, 108 | 109 | [Parameter(Mandatory)] 110 | [string] $PackageName, 111 | 112 | [Parameter(Mandatory)] 113 | [string] $PackageVersion, 114 | 115 | [Parameter(Mandatory)] 116 | [string] $ProjectFile, 117 | 118 | [Parameter(Mandatory)] 119 | [string] $NugetConfigFile, 120 | 121 | [Parameter(Mandatory)] 122 | [string] $PackagesPath, 123 | 124 | [Parameter(Mandatory)] 125 | [string] $MSBuildPath, 126 | 127 | [switch] $Safe 128 | ) 129 | 130 | if ((Get-Item $NugetExe).Exists -eq $false) { 131 | throw "The Nuget exe file does not exist $NugetExe" 132 | } 133 | 134 | # Get all csproj files that we are upgrading 135 | $csProjs = Get-CSProjFilesForUpdate -PackageName $PackageName -PackageVersion $PackageVersion -ProjectFile $ProjectFile 136 | 137 | Write-Verbose "Running Nuget update..." 138 | 139 | if ($Safe) { 140 | & $NugetExe update "$ProjectFile" -ConfigFile "$NugetConfigFile" -RepositoryPath "$PackagesPath" -Id $PackageName -Version $PackageVersion -FileConflictAction IgnoreAll -NonInteractive -MSBuildPath "$MSBuildPath" -safe -Verbosity detailed 141 | } 142 | else { 143 | & $NugetExe update "$ProjectFile" -ConfigFile "$NugetConfigFile" -RepositoryPath "$PackagesPath" -Id $PackageName -Version $PackageVersion -FileConflictAction IgnoreAll -NonInteractive -MSBuildPath "$MSBuildPath" -Verbosity detailed 144 | } 145 | 146 | if (!($LASTEXITCODE -eq 0)) { 147 | throw "An error occurred, quitting" 148 | } 149 | 150 | # Run the install.ps1 scripts in the nuget tools for each project updated 151 | foreach($csProj in $csProjs) { 152 | Write-Verbose "Finding tools/install.ps1 for $csProj" 153 | Invoke-NugetToolsScripts -PackagesPath $PackagesPath -PackageName $PackageName -PackageVersion $PackageVersion -CsProjFile $csProj 154 | } 155 | } 156 | 157 | function Invoke-NugetToolsScripts { 158 | [CmdletBinding(DefaultParameterSetName = 'None')] 159 | param( 160 | [Parameter(Mandatory)] 161 | [string] $PackagesPath, 162 | 163 | [Parameter(Mandatory)] 164 | [string] $PackageName, 165 | 166 | [Parameter(Mandatory)] 167 | [string] $PackageVersion, 168 | 169 | [Parameter(Mandatory)] 170 | [string] $CsProjFile 171 | ) 172 | 173 | # first we need to construct the "installPath" 174 | $installPath = Join-Path $PackagesPath "$PackageName.$PackageVersion" 175 | if (!(Test-Path -Path $installPath)) { 176 | throw "Could not find the installed package folder at $installPath" 177 | } 178 | $toolsPath = Join-Path $installPath "tools" 179 | if (Test-Path -Path $toolsPath) { 180 | $installScript = Join-Path $toolsPath "install.ps1" 181 | if (Test-Path -Path $installScript) { 182 | # Ok, we can execute 183 | 184 | # The last step is to 'fake' the $project object 185 | $projectProperties = New-Object -TypeName PSObject 186 | $projectProperties | Add-Member -MemberType ScriptMethod -Name Item -Value { 187 | param([string]$val) 188 | return @{ 189 | # return the path of the project being updated 190 | Value = (Get-Item $CsProjFile).Directory.FullName 191 | } 192 | } 193 | $project = New-Object -TypeName PSObject 194 | $project | Add-Member -MemberType NoteProperty -Name Properties -Value $projectProperties 195 | 196 | # Then we need to fake a $DTE object 197 | $itemOps = New-Object -TypeName PSObject 198 | $itemOps | Add-Member -MemberType ScriptMethod -Name OpenFile -Value { 199 | param([string]$val) 200 | # nop 201 | } 202 | $DTE = New-Object -TypeName PSObject 203 | $DTE | Add-Member -MemberType NoteProperty -Name ItemOperations -Value $itemOps 204 | 205 | Write-Verbose "Found tools/install.ps1 script in Nuget package. Executing script..." 206 | & $installScript $installPath $toolsPath $null $project | Out-Null 207 | Write-Verbose "tools/install.ps1 completed" 208 | } 209 | } 210 | } 211 | 212 | function Get-CSProjFilesForUpdate { 213 | 214 | [CmdletBinding(DefaultParameterSetName = 'None')] 215 | param( 216 | [Parameter(Mandatory)] 217 | [string] $PackageName, 218 | 219 | [Parameter(Mandatory)] 220 | [string] $PackageVersion, 221 | 222 | [Parameter(Mandatory)] 223 | [string] $ProjectFile 224 | ) 225 | 226 | Add-Type -AssemblyName System.Xml.Linq 227 | 228 | # the project file can be an sln or a csproj. If it's an sln, get all csproj's for it 229 | # then check if they will be upgraded 230 | 231 | # Get all csproj's that were upgraded 232 | $csprojs = New-Object Collections.Generic.List[string] 233 | $fileExt = [System.IO.Path]::GetExtension($ProjectFile) 234 | if ($fileExt -eq ".sln") { 235 | $slnPath = (Get-Item $ProjectFile).Directory.FullName 236 | # read in sln file contents 237 | $slnContent = Get-Content -Path $ProjectFile -Raw 238 | # parse out all csproj's 239 | $regex = "^Project\(\`".+?, \`"(.+\.csproj)\`"" 240 | $m = [regex]::Matches($slnContent, $regex, [System.Text.RegularExpressions.RegexOptions]::Multiline) 241 | # add all csproj's to the list 242 | foreach ($i in $m) { 243 | $csprojRelativePath = $i.Groups[1].Value; 244 | $csprojAbsPath = Join-Path -Path $slnPath -ChildPath $csprojRelativePath 245 | # get the packages.config file for this csproj to see if it references the package 246 | $packagesConfig = (Get-Item $csprojAbsPath).Directory.GetFiles("packages.config") 247 | if ($packagesConfig.Length -gt 0){ 248 | 249 | # if the version for this csproj doesn't match our target version then add it to the list 250 | $xml = [System.Xml.Linq.XDocument]::Load($packagesConfig[0].FullName) 251 | $xpath = "string(//package[@id='$PackageName']/@version)" 252 | $version = [string][System.Xml.XPath.Extensions]::XPathEvaluate($xml, $xpath); 253 | 254 | if ($PackageVersion -ne $version){ 255 | $csprojs.Add($csprojAbsPath) 256 | } 257 | } 258 | } 259 | } 260 | else { 261 | $csprojs.Add($ProjectFile) 262 | } 263 | 264 | Write-Verbose "The .csproj files being updated are $csprojs" 265 | 266 | return $csprojs 267 | } 268 | 269 | function Find-NugetConfig { 270 | [CmdletBinding(DefaultParameterSetName = 'None')] 271 | param( 272 | [Parameter(Mandatory)] 273 | [string] $CurrentDirectory, 274 | 275 | [Parameter(Mandatory)] 276 | [string] $RootGitDirectory 277 | ) 278 | 279 | $folder = Get-Item $CurrentDirectory 280 | 281 | Write-Verbose "Finding Nuget.config, current folder: $CurrentDirectory" 282 | 283 | $nugetConfigFiles = Get-ChildItem -Path $CurrentDirectory -Filter "NuGet.config" 284 | 285 | if ($nugetConfigFiles.Count -eq 0) { 286 | if ($CurrentDirectory.ToLower() -eq $RootGitDirectory.ToLower()) { 287 | throw "No Nuget.config file found in repository" 288 | } 289 | 290 | # move up 291 | $parent = $folder.Parent; 292 | if ($null -eq $parent -or $parent.Exists -eq $false) { 293 | throw "No Nuget.config file found on file system" 294 | } 295 | 296 | # recurse 297 | return Find-NugetConfig -CurrentDirectory $parent.FullName -RootGitDirectory $RootGitDirectory 298 | } 299 | 300 | Write-Verbose "Found nuget config $($nugetConfigFiles[0].FullName)" 301 | return $nugetConfigFiles[0]; 302 | } 303 | 304 | function Get-LatestPackageVersion { 305 | <# 306 | .SYNOPSIS 307 | Gets the latest version of a package from a Nuget package repository 308 | .DESCRIPTION 309 | Gets the latest version of a package from a Nuget package repository 310 | .PARAMETER PackageName 311 | The package name to get the latest version for 312 | #> 313 | [CmdletBinding(DefaultParameterSetName = 'None')] 314 | param( 315 | [Parameter(Mandatory)] 316 | [string] $PackageName 317 | ) 318 | 319 | $nugetOutput = & $nuget list "PackageId:$PackageName" -NonInteractive | Out-String 320 | 321 | $nugetVersions = $nugetOutput.Split([System.Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) 322 | 323 | $latestVersion = $nugetVersions | 324 | Sort-Object { ([semver] $_.Split(' ')[1]) } | 325 | Select-Object -Last 1 326 | $latestSemver = $latestVersion.Split(' ')[1] 327 | 328 | return $latestSemver 329 | } 330 | 331 | function Get-UpgradeAvailable { 332 | <# 333 | .SYNOPSIS 334 | Compares to semver versions and returns $true if the DestVersion is greater than SourceVersion 335 | .DESCRIPTION 336 | Compares to semver versions and returns $true if the DestVersion is greater than SourceVersion 337 | .PARAMETER SourceVersion 338 | The source version 339 | .PARAMETER DestVersion 340 | The destination version 341 | #> 342 | [CmdletBinding(DefaultParameterSetName = 'None')] 343 | param( 344 | [Parameter(Mandatory)] 345 | [string] $SourceVersion, 346 | 347 | [Parameter(Mandatory)] 348 | [string] $DestVersion 349 | ) 350 | 351 | $sourceSemver = [semver] $SourceVersion 352 | $destSemver = [semver] $DestVersion 353 | 354 | return $sourceSemver.CompareTo($destSemver).Equals(-1) 355 | } -------------------------------------------------------------------------------- /src/Powershell/UmbracoCloud/UmbracoCloud.ps1: -------------------------------------------------------------------------------- 1 | function Move-BuildOutputToUmbracoCloud { 2 | 3 | <# 4 | .SYNOPSIS 5 | Deploys the build output of a Web Application project to Umbraco Cloud deployment repository. 6 | .DESCRIPTION 7 | Deploys the build output of a Web Application project to Umbraco Cloud deployment repository. 8 | .PARAMETER UmbCloudUrl 9 | The Umbraco Cloud Git deployment URL. 10 | .PARAMETER UmbCloudUsername 11 | The Umbraco Cloud Git username. 12 | .PARAMETER UmbCloudPassword 13 | The Umbraco Cloud Git password. 14 | .PARAMETER SourcePath 15 | The build output of the web application project to copy over to the Umbraco Cloud repository. 16 | .PARAMETER DestinationPath 17 | The folder to clone the Umbraco Cloud repository to which will be updated with the build output. 18 | #> 19 | [CmdletBinding(DefaultParameterSetName = 'None')] 20 | [Alias('Publish-ToUmbracoCloud')] 21 | param 22 | ( 23 | [String] [Parameter(Mandatory = $true)] 24 | $UmbCloudUrl, 25 | 26 | [String] [Parameter(Mandatory = $true)] 27 | $UmbCloudUsername, 28 | 29 | [String] [Parameter(Mandatory = $true)] 30 | $UmbCloudPassword, 31 | 32 | [String] [Parameter(Mandatory = $true)] 33 | $SourcePath, 34 | 35 | [String] [Parameter(Mandatory = $true)] 36 | $DestinationPath 37 | ) 38 | 39 | #Clone the Umbraco Cloud repository 40 | Write-Verbose "Cloning Umbraco Cloud repository $UmbCloudUrl to $DestinationPath..." 41 | Copy-CloudRepo -GitClonePath $DestinationPath -GitUsername $UmbCloudUsername -GitPassword $UmbCloudPassword -GitAddress $UmbCloudUrl 42 | 43 | #Copy the buildout to the Umbraco Cloud repository excluding the umbraco and umbraco_client folders 44 | Write-Verbose "Copying Build output to the Umbraco Cloud repository..." 45 | Get-ChildItem -Path $SourcePath | % { Copy-Item $_.fullname "$DestinationPath" -Recurse -Force -Exclude @("umbraco", "umbraco_client") } 46 | 47 | #Change location to the Path where the Umbraco Cloud repository is cloned 48 | Set-Location -Path $DestinationPath 49 | 50 | #Silence warnings about LF/CRLF 51 | Write-Verbose "Silence warnings about LF/CRLF" 52 | Invoke-Git "config core.safecrlf false" 53 | 54 | #Commit the build output to the Umbraco Cloud repository 55 | Invoke-Git "status" 56 | Invoke-Git "add -A" 57 | Write-Verbose "Committing changes to repository..." 58 | Invoke-Git "-c user.name=`"Umbraco Cloud`" -c user.email=`"support@umbraco.io`" commit -m `"Committing build output from VSTS`" --author=`"Umbraco Cloud `"" 59 | 60 | #Push the added files to Umbraco Cloud 61 | Write-Verbose "Deploying to Umbraco Cloud..." 62 | Invoke-Git "push origin master" 63 | 64 | #Remove credentials from the configured remote 65 | Invoke-Git "remote set-url `"origin`" $UmbCloudUrl" 66 | 67 | Write-Verbose "Deployment finished" 68 | 69 | } 70 | 71 | function Invoke-PackageAutoUpgrade { 72 | 73 | <# 74 | .SYNOPSIS 75 | Checks for package updates and if there are any will upgrade the source code repository and submit a Pull Requet. 76 | .DESCRIPTION 77 | Checks for package updates and if there are any will upgrade the source code repository and submit a Pull Requet. 78 | .PARAMETER PackageName 79 | The Nuget package name to check for upgrades. 80 | .PARAMETER ProjectFile 81 | The full path to the sln or csproj file to upgrade. 82 | .PARAMETER PackageFile 83 | The virtual path to the packages.config file to check for ugprades on the GitHub repository. 84 | .PARAMETER GitHubOwner 85 | The GitHub owner name. 86 | .PARAMETER GitHubRepository 87 | The GitHub repository name. 88 | .PARAMETER GitHubAccessToken 89 | The GitHub access token. 90 | .PARAMETER DoNotCommit 91 | Flag if set will not commit changes and therefor not push or create a PR 92 | .PARAMETER DoNotPush 93 | Flag if set will not push committed changes and therefor not create a PR 94 | .PARAMETER DoNotPR 95 | Flag if set will not create a PR from the pushed and committed changes 96 | #> 97 | [CmdletBinding(DefaultParameterSetName = 'None')] 98 | param 99 | ( 100 | [String] [Parameter(Mandatory = $true)] 101 | $PackageName, 102 | 103 | [String] [Parameter(Mandatory = $true)] 104 | $ProjectFile, 105 | 106 | # TODO: don't explicitly pass this in, if its an sln file we need to loop over the csproj files and automatically detect, 107 | # same with a csproj we should auto-detect this path, but that might be difficult to do since we don't know how the repo is structured. 108 | # For now, this is fine. 109 | [String] [Parameter(Mandatory = $true)] 110 | $PackageFile, 111 | 112 | [String] [Parameter(Mandatory = $true)] 113 | $GitHubOwner, 114 | 115 | [String] [Parameter(Mandatory = $true)] 116 | $GitHubRepository, 117 | 118 | [String] [Parameter(Mandatory = $true)] 119 | $GitHubAccessToken, 120 | 121 | [switch] 122 | $DoNotCommit, 123 | [switch] 124 | $DoNotPush, 125 | [switch] 126 | $DoNotPR 127 | ) 128 | 129 | # Variables 130 | 131 | $buildFolder = $PSScriptRoot 132 | $tempFolder = "$env:temp\UmbracoCloudPs" 133 | $repoRoot = (Get-Item $buildFolder).Parent 134 | 135 | try { 136 | # Get version in GitHub 137 | Write-Verbose "Getting latest version of $PackageName from GitHub" 138 | $packageVersion = Get-CurrentPackageVersion -OwnerName $GitHubOwner -RepositoryName $GitHubRepository -AccessToken $GitHubAccessToken -PackageFile $PackageFile -PackageName $PackageName 139 | if (!$packageVersion) { 140 | Throw "Could not determine package version, cannot continue" 141 | } 142 | Write-Verbose "Latest local version of $PackageName is $packageVersion" 143 | 144 | # Get the latest version from Nuget 145 | 146 | Write-Verbose "Getting latest version of $PackageName from Nuget" 147 | $nuget = Get-NugetExe -DestinationFolder $tempFolder 148 | $latest = Get-LatestPackageVersion -PackageName $PackageName 149 | if (!$packageVersion) { 150 | Throw "Could not determine package version, cannot continue" 151 | } 152 | Write-Verbose "Latest nuget version of $PackageName is $latest" 153 | 154 | # Compare versions, next we need to run nuget + PR 155 | $hasUpgrade = Get-UpgradeAvailable -SourceVersion $packageVersion -DestVersion $latest 156 | 157 | if ($hasUpgrade -eq $true) { 158 | Write-Verbose "An upgrade is available!" 159 | 160 | $branchName = "$PackageName-upgrade-$latest"; 161 | 162 | Write-Verbose "Checking if a PR is already created..." 163 | $pr = Get-PullRequest -OwnerName $GitHubOwner -RepositoryName $GitHubRepository -AccessToken $GitHubAccessToken -BranchName $branchName 164 | if ($pr) { 165 | throw "A Pull Request already exists for this upgrade" 166 | } 167 | 168 | $msbuild = Get-MSBuildExe -DestinationFolder $tempFolder -NugetExe $nuget 169 | Write-Verbose "MSBuild found at $msbuild" 170 | $msbuildPath = (Get-Item $msbuild).Directory.FullName 171 | 172 | Write-Verbose "Creating Git Branch '$branchName' ..." 173 | $branchExists = Get-GitBranchExists -BranchName $branchName 174 | if ($branchExists -eq $true) { 175 | Write-Verbose "Branch $branchName already exists, updating to branch" 176 | Switch-GitBranch -BranchName $branchName 177 | # throw "Branch $branchName already exists" 178 | } 179 | else { 180 | New-GitBranch -BranchName $branchName 181 | } 182 | 183 | Write-Verbose "Upgrading project..." 184 | Update-NugetPackage -PackageName $PackageName -PackageVersion $latest -ProjectFile $ProjectFile -RootGitDirectory $($repoRoot.FullName) -NugetExe $nuget -MSBuildPath $msbuildPath 185 | 186 | # Write-Verbose "Building project..." 187 | # Build-Project -MSBuildExe $msbuild -ProjectFile $ProjectFile 188 | 189 | # TODO: Potentially we need to revert all /Config/* files because this will overright them and we dont want to commit those changes 190 | # However in some cases we might want to see what those changes are so for now we'll leave it up to the developer to review the changes 191 | # and revert what they want. 192 | # This all depends on the Nuget behavior IgnoreAll, etc... 193 | 194 | if ($false -eq $DoNotCommit) { 195 | Write-Verbose "Committing changes..." 196 | Add-GitChanges -Message "Updated files for the $PackageName $latest Nuget upgrade" 197 | 198 | if ($false -eq $DoNotPush) { 199 | Write-Verbose "Pushing changes..." 200 | Push-GitChanges -BranchName $branchName 201 | 202 | if ($false -eq $DoNotPR) { 203 | Write-Verbose "Creating pull request..." 204 | $pr = New-PullRequest -OwnerName $GitHubOwner -RepositoryName $GitHubRepository -AccessToken $GitHubAccessToken -PackageVersion $latest -PackageName $PackageName -BranchName $branchName 205 | } 206 | } 207 | } 208 | } 209 | else { 210 | Write-Verbose "Nothing to upgrade" 211 | } 212 | } 213 | finally { 214 | # Clear temp files 215 | Remove-Item $tempFolder -Recurse 216 | } 217 | } -------------------------------------------------------------------------------- /src/Powershell/UmbracoCloud/UmbracoCloud.psd1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/UmbracoScripts/4e822bf403110be9e02b03c7cbbcaddaeadde72f/src/Powershell/UmbracoCloud/UmbracoCloud.psd1 -------------------------------------------------------------------------------- /src/Powershell/UmbracoCloud/UmbracoDeploy.ps1: -------------------------------------------------------------------------------- 1 | 2 | function Invoke-UmbracoDeployExtraction { 3 | <# 4 | .DESCRIPTION 5 | This will send a request to trigger a Deploy extraction or return a valid bearer header token 6 | value to put in for the BearerToken authenticationheader to make an http request to a Deploy endpoint. 7 | .PARAMETER Action 8 | Trigger - Trigger deploy to start extraction. Requires BaseUrl and Reason. 9 | GetToken - Only return the bearer Authentication token. 10 | GetStatus - Retrieves the status of an extraction. 11 | TriggerWithStatus - Trigger the deploy to start extraction and wait until it succeeds or fails and return the status response. 12 | 13 | .PARAMETER ApiKey 14 | The Deploy ApiKey 15 | 16 | .PARAMETER BaseUrl 17 | The base URL including the scheme, host, port excluding the trailing slash 18 | 19 | .PARAMETER Reason 20 | The reason for extraction, this is used for logging/information 21 | 22 | .PARAMETER TaskId 23 | The task Id to get the status for when using GetStatus. If not specified will get the status from the last/current task. 24 | 25 | .PARAMETER PollingDelaySeconds 26 | The number of seconds to delay in between polling. Default is 3. 27 | 28 | .EXAMPLE 29 | Invoke-UmbracoDeployExtraction -ApiKey "7C327019-20BB-4B49-B514-386415648981" -Action Trigger -BaseUrl "http://localhost:45332" -Reason "test" -Verbose 30 | 31 | Triggers a deployment and ensures any verbose info is printed to the screen and returns the json result as a string 32 | 33 | .EXAMPLE 34 | Invoke-UmbracoDeployExtraction -ApiKey "7C327019-20BB-4B49-B514-386415648981" -Action Trigger -BaseUrl "http://localhost:45332" -Reason "test" 35 | 36 | Triggers a deployment and returns the json result as a string 37 | 38 | .EXAMPLE 39 | Invoke-UmbracoDeployExtraction -ApiKey "7C327019-20BB-4B49-B514-386415648981" -Action GetToken 40 | 41 | Returns the authentication token for triggering an extraction 42 | 43 | .EXAMPLE 44 | Invoke-UmbracoDeployExtraction -ApiKey "7C327019-20BB-4B49-B514-386415648981" -Action GetStatus -BaseUrl "http://localhost:45332" 45 | 46 | Returns the status for the current/last triggered extraction task 47 | 48 | .EXAMPLE 49 | Invoke-UmbracoDeployExtraction -ApiKey "7C327019-20BB-4B49-B514-386415648981" -Action GetStatus -BaseUrl "http://localhost:45332" -TaskId "8C327019-20BB-4B49-B514-386415648981" 50 | 51 | Returns the status for the triggered extraction task with the specified id 52 | #> 53 | [CmdletBinding(DefaultParameterSetName = 'None')] 54 | [Alias('Extract-UmbracoDeploy')] 55 | param( 56 | [Parameter(Mandatory)] 57 | [ValidateSet('Trigger', 'GetToken', 'GetStatus', 'TriggerWithStatus')] 58 | [string] $Action, 59 | 60 | [Parameter(Mandatory)] 61 | [string] $ApiKey, 62 | 63 | [string] $BaseUrl, 64 | [string] $Reason, 65 | [string] $TaskId, 66 | [int] $PollingDelaySeconds = 3 67 | ) 68 | if ($Action -eq "GetToken" -or $Action -eq "Trigger" -or $Action -eq "TriggerWithStatus") { 69 | if ([string]::IsNullOrEmpty($Reason)) { 70 | throw "$Reason cannot be null or empty" 71 | } 72 | 73 | $requestParams = Get-RequestParameters -Endpoint "/umbraco/umbracodeploy/extract/start/$Reason" 74 | } 75 | 76 | if ($Action -eq "GetToken") { 77 | return $requestParams.Token 78 | } 79 | else { 80 | 81 | if ([string]::IsNullOrEmpty($BaseUrl)) { 82 | throw "BaseUrl cannot be null or empty" 83 | } 84 | 85 | if ($Action -eq "Trigger" -or $Action -eq "TriggerWithStatus") { 86 | 87 | $response = Send-Request -Token $requestParams.Token -BaseUrl $BaseUrl -Endpoint $requestParams.Endpoint -Action Post 88 | 89 | $json = ConvertFrom-Json -InputObject $response.Content 90 | $TaskId = $json.TaskId # The task Id result 91 | 92 | if ($Action -eq "Trigger") { 93 | return $response.ToString() 94 | } 95 | } 96 | 97 | if ($Action -eq "GetStatus" -or $Action -eq "TriggerWithStatus") { 98 | if ([string]::IsNullOrEmpty($TaskId)) { 99 | $requestParams = Get-RequestParameters -Endpoint "/umbraco/umbracodeploy/statusreport/getcurrent" 100 | } 101 | else { 102 | $requestParams = Get-RequestParameters -Endpoint "/umbraco/umbracodeploy/statusreport/get/$TaskId" 103 | } 104 | 105 | $response = Send-Request -Token $requestParams.Token -BaseUrl $BaseUrl -Endpoint $requestParams.Endpoint -Action Get 106 | 107 | $json = ConvertFrom-Json -InputObject $response.Content 108 | 109 | if ($Action -eq "GetStatus") { 110 | return $json 111 | } 112 | else { 113 | 114 | $json = Get-ExtractionResult -Token $requestParams.Token -Endpoint $requestParams.Endpoint -BaseUrl $BaseUrl -Json $json -PollingDelaySeconds $PollingDelaySeconds 115 | 116 | # if the response is unknown it most likely means that the app restarted after we first initialized 117 | # the extraction in which case the status gets removed from memory, so we'll retry the whole thing 118 | if ($json.Status -eq "Unknown") { 119 | Write-Verbose "Status result is Unknown, retrying Extraction again..." 120 | 121 | Start-Sleep -Seconds $PollingDelaySeconds 122 | 123 | # send the extraction again 124 | $requestParams = Get-RequestParameters -Endpoint "/umbraco/umbracodeploy/extract/start/$Reason" 125 | $response = Send-Request -Token $requestParams.Token -BaseUrl $BaseUrl -Endpoint $requestParams.Endpoint -Action Post 126 | $json = ConvertFrom-Json -InputObject $response.Content 127 | $TaskId = $json.TaskId # The task Id result 128 | 129 | # update the values to poll again 130 | $requestParams = Get-RequestParameters -Endpoint "/umbraco/umbracodeploy/statusreport/get/$TaskId" 131 | $response = Send-Request -Token $requestParams.Token -BaseUrl $BaseUrl -Endpoint $requestParams.Endpoint -Action Get 132 | $json = ConvertFrom-Json -InputObject $response.Content 133 | 134 | $json = Get-ExtractionResult -Token $requestParams.Token -Endpoint $requestParams.Endpoint -BaseUrl $BaseUrl -Json $json -PollingDelaySeconds $PollingDelaySeconds 135 | } 136 | 137 | if ($json.Status -ne "Completed") { 138 | $err = $json.Status 139 | if (-not ([string]::IsNullOrEmpty($json.Log))) { 140 | $err = $json.Log 141 | } 142 | elseif ($json.Exception) { 143 | $err = $json.Exception 144 | } 145 | throw "UmbracoDeploy extraction failed. Response: $($err)" 146 | } 147 | 148 | return $json 149 | } 150 | } 151 | } 152 | } 153 | 154 | function Get-UnixTimestamp { 155 | [CmdletBinding()] 156 | param( 157 | [Parameter(Mandatory)] 158 | [DateTime] $Timestamp 159 | ) 160 | 161 | $utcnow = [TimeZoneInfo]::ConvertTimeToUtc($Timestamp) 162 | $utcbase = New-Object DateTime 1970, 1, 1, 0, 0, 0, ([DateTimeKind]::Utc) 163 | $result = $utcnow - $utcbase 164 | $seconds = $result.TotalSeconds 165 | return $seconds 166 | } 167 | 168 | function Get-Signature { 169 | [CmdletBinding()] 170 | param( 171 | [Parameter(Mandatory)] 172 | [string] $RequestUri, 173 | [Parameter(Mandatory)] 174 | [DateTime] $Timestamp, 175 | [Parameter(Mandatory)] 176 | [string] $Nonce, 177 | [Parameter(Mandatory)] 178 | [string] $Secret 179 | ) 180 | 181 | $unixTimestamp = Get-UnixTimestamp -Timestamp $Timestamp 182 | $secretBytes = [Text.Encoding]::UTF8.GetBytes($Secret) 183 | $signatureString = "$RequestUri$unixTimestamp$Nonce" 184 | $signatureBytes = [Text.Encoding]::UTF8.GetBytes($signatureString) 185 | 186 | $hmacsha = New-Object System.Security.Cryptography.HMACSHA256 187 | $hmacsha.key = $secretBytes 188 | $computedHashBytes = $hmacsha.ComputeHash($signatureBytes) 189 | $computedString = [Convert]::ToBase64String($computedHashBytes) 190 | 191 | return $computedString 192 | } 193 | 194 | function Get-AuthorizationHeader { 195 | [CmdletBinding()] 196 | param( 197 | [Parameter(Mandatory)] 198 | [string] $Signature, 199 | [Parameter(Mandatory)] 200 | [string] $Nonce, 201 | [Parameter(Mandatory)] 202 | [DateTime] $Timestamp 203 | ) 204 | 205 | $unixTimestamp = Get-UnixTimestamp -Timestamp $Timestamp 206 | $token = "${Signature}:${Nonce}:${unixTimestamp}" 207 | $tokenBytes = [Text.Encoding]::UTF8.GetBytes($token) 208 | $encoded = [Convert]::ToBase64String($tokenBytes) 209 | return $encoded 210 | } 211 | 212 | function Send-Request { 213 | [CmdletBinding()] 214 | param( 215 | [Parameter(Mandatory)] 216 | [string] $Token, 217 | [Parameter(Mandatory)] 218 | [string] $Endpoint, 219 | [Parameter(Mandatory)] 220 | [string] $BaseUrl, 221 | [Parameter(Mandatory)] 222 | [string] $Action 223 | ) 224 | 225 | $uri = "${BaseUrl}${Endpoint}" 226 | 227 | Write-Verbose "Sending request to $uri" 228 | 229 | # Powershell is supposed to support the Authentication parameter for Invoke-WebRequest but it doesn't until later versions 230 | $headers = @{ 231 | Authorization = "Bearer $Token" 232 | } 233 | 234 | $response = Invoke-WebRequest -Uri $uri -Headers $headers -ContentType "application/json" -Method $Action 235 | Write-Verbose $response 236 | 237 | if ($response.StatusCode -ne 200) { 238 | throw "Cannot continue the request failed. Use -Verbose flag for more info" 239 | } 240 | 241 | return $response 242 | } 243 | 244 | function Get-ExtractionResult { 245 | [CmdletBinding()] 246 | param( 247 | [Parameter(Mandatory)] 248 | [int] $PollingDelaySeconds, 249 | [Parameter(Mandatory)] 250 | [string] $Token, 251 | [Parameter(Mandatory)] 252 | [string] $Endpoint, 253 | [Parameter(Mandatory)] 254 | [string] $BaseUrl, 255 | [Parameter(Mandatory)] 256 | $Json 257 | ) 258 | 259 | While ($Json.Status -eq "Executing" -or $Json.Status -eq "New") { 260 | Write-Verbose "Still in progress..." 261 | Start-Sleep -Seconds $PollingDelaySeconds 262 | $response = Send-Request -Token $Token -BaseUrl $BaseUrl -Endpoint $Endpoint -Action Get 263 | 264 | $Json = ConvertFrom-Json -InputObject $response.Content 265 | } 266 | 267 | return $Json 268 | } 269 | 270 | function Get-RequestParameters { 271 | [CmdletBinding()] 272 | param( 273 | [Parameter(Mandatory)] 274 | [string] $Endpoint 275 | ) 276 | 277 | $now = Get-Date 278 | $nonce = New-Guid 279 | $signature = Get-Signature -RequestUri $Endpoint -Timestamp $now -Nonce $nonce -Secret $ApiKey 280 | $token = Get-AuthorizationHeader -Signature $signature -Nonce $nonce -Timestamp $now 281 | 282 | return @{ 283 | Signature = $signature 284 | Token = $token 285 | Endpoint = $Endpoint 286 | } 287 | } -------------------------------------------------------------------------------- /src/Powershell/UmbracoPackageMaker.ps1: -------------------------------------------------------------------------------- 1 | ############################################################### 2 | # Input parameters define the version number for the build, if using a pre-release version 3 | # number, then enter it including the dash, example "-beta", if not using a pre-release version 4 | # then don't enter anything for that value. 5 | ############################################################### 6 | 7 | param ( 8 | [Parameter(Mandatory=$true)] 9 | [ValidatePattern("^\d\.\d\.(?:\d\.\d$|\d$)")] 10 | [string] 11 | $ReleaseVersionNumber, 12 | [Parameter(Mandatory=$true)] 13 | [string] 14 | [AllowEmptyString()] 15 | $PreReleaseName 16 | ) 17 | 18 | ############################################################### 19 | # Define some file/folder paths, this assumes that you have output a createdPackages.config 20 | # from Umbraco's package creator in the back office. You will need to do that first to create 21 | # the initial package file and copy that template into the $BuildFolder 22 | ############################################################### 23 | 24 | # Define the build folder path 25 | $BuildFolder = Join-Path -Path $RepoRoot -ChildPath "build"; 26 | # Define the createdPackages.config path 27 | $CreatedPackagesConfig = Join-Path -Path $BuildFolder -ChildPath "createdPackages.config" 28 | # Define the build output (release) folder path 29 | $ReleaseFolder = Join-Path -Path $BuildFolder -ChildPath "Releases\v$ReleaseVersionNumber$PreReleaseName"; 30 | 31 | # Delete the release folder contents if it exists 32 | if ((Get-Item $ReleaseFolder -ErrorAction SilentlyContinue) -ne $null) 33 | { 34 | Write-Warning "$ReleaseFolder already exists on your local machine. It will now be deleted." 35 | Remove-Item $ReleaseFolder -Recurse 36 | } 37 | 38 | ############################################################### 39 | # DO THE UMBRACO PACKAGE BUILD 40 | ############################################################### 41 | 42 | # Load in the XML from the file 43 | $CreatedPackagesConfigXML = [xml](Get-Content $CreatedPackagesConfig) 44 | # Set the version number in createdPackages.config 45 | $CreatedPackagesConfigXML.packages.package.version = "$ReleaseVersionNumber" 46 | $CreatedPackagesConfigXML.Save($CreatedPackagesConfig) 47 | 48 | #copy the orig manifest to temp location to be updated to be used for the package 49 | $PackageManifest = Join-Path -Path $BuildFolder -ChildPath "packageManifest.xml" 50 | New-Item -ItemType Directory -Path $TempFolder 51 | Copy-Item $PackageManifest "$TempFolder\package.xml" 52 | $PackageManifest = (Join-Path -Path $TempFolder -ChildPath "package.xml") 53 | 54 | # Set the data in packageManifest.config 55 | $PackageManifestXML = [xml](Get-Content $PackageManifest) 56 | $PackageManifestXML.umbPackage.info.package.version = "$ReleaseVersionNumber" 57 | $PackageManifestXML.umbPackage.info.package.name = $CreatedPackagesConfigXML.packages.package.name 58 | $PackageManifestXML.umbPackage.info.package.license.set_InnerXML($CreatedPackagesConfigXML.packages.package.license.get_InnerXML()) 59 | $PackageManifestXML.umbPackage.info.package.license.url = $CreatedPackagesConfigXML.packages.package.license.url 60 | $PackageManifestXML.umbPackage.info.package.url = $CreatedPackagesConfigXML.packages.package.url 61 | $PackageManifestXML.umbPackage.info.author.name = $CreatedPackagesConfigXML.packages.package.author.get_InnerXML() 62 | $PackageManifestXML.umbPackage.info.author.website = $CreatedPackagesConfigXML.packages.package.author.url 63 | 64 | #clear the files from the manifest 65 | $NewFilesXML = $PackageManifestXML.CreateElement("files") 66 | 67 | #package the files ... This will lookup all files in the file system that need to be there and update 68 | # the package manifest XML with the correct data along with copying these files to the temp folder 69 | # so they can be zipped with the package 70 | 71 | Function WritePackageFile ($f) 72 | { 73 | Write-Host $f.FullName -foregroundcolor cyan 74 | $NewFileXML = $PackageManifestXML.CreateElement("file") 75 | $NewFileXML.set_InnerXML("") 76 | $GuidName = ([guid]::NewGuid()).ToString() + "_" + $f.Name 77 | $NewFileXML.guid = $GuidName 78 | $NewFileXML.orgPath = ReverseMapPath $f 79 | $NewFileXML.orgName = $f.Name 80 | $NewFilesXML.AppendChild($NewFileXML) 81 | Copy-Item $f.FullName "$TempFolder\$GuidName" 82 | } 83 | Function ReverseMapPath ($f) 84 | { 85 | $resultPath = "~"+ $f.Directory.FullName.Replace($WebProjFolder, "").Replace("\","/") 86 | Return $resultPath 87 | } 88 | Function MapPath ($f) 89 | { 90 | $resultPath = Join-Path -Path $WebProjFolder -ChildPath ($f.Replace("~", "").Replace("/", "\")) 91 | Return $resultPath 92 | } 93 | foreach($FileXML in $CreatedPackagesConfigXML.packages.package.files.file) 94 | { 95 | $File = Get-Item (MapPath $FileXML) 96 | if ($File -is [System.IO.DirectoryInfo]) 97 | { 98 | Get-ChildItem -path $File -Recurse ` 99 | | Where-Object { $_ -isnot [System.IO.DirectoryInfo]} ` 100 | | ForEach-Object { WritePackageFile($_) } ` 101 | | Out-Null 102 | } 103 | else { 104 | WritePackageFile($File)| Out-Null 105 | } 106 | } 107 | $PackageManifestXML.umbPackage.ReplaceChild($NewFilesXML, $PackageManifestXML.SelectSingleNode("/umbPackage/files")) | Out-Null 108 | $PackageManifestXML.Save($PackageManifest) 109 | 110 | #finally zip the package 111 | $DestZIP = "$ReleaseFolder\Articulate.zip" 112 | Add-Type -assembly "system.io.compression.filesystem" 113 | [io.compression.zipfile]::CreateFromDirectory($TempFolder, $DestZIP) 114 | 115 | ############################################################### 116 | # Finished creating the Umbraco package format as a zip file 117 | ############################################################### 118 | -------------------------------------------------------------------------------- /src/Sql/QueryCorruptedPropertyData.sql: -------------------------------------------------------------------------------- 1 | --This will show all property's that are storing data in more than one field type which is not supported 2 | SELECT cmsPropertyType.id, cmsPropertyType.Alias, cmsDataType.dbType, cmsPropertyData.* FROM cmsPropertyType 3 | INNER JOIN cmsDataType ON cmsPropertyType.dataTypeId = cmsDataType.nodeId 4 | INNER JOIN cmsPropertyData ON cmsPropertyType.id = cmsPropertyData.propertytypeid 5 | WHERE 6 | (cmsPropertyData.dataNtext IS NOT NULL AND cmsPropertyData.dataDate IS NOT NULL) OR 7 | (cmsPropertyData.dataNtext IS NOT NULL AND cmsPropertyData.dataNvarchar IS NOT NULL) OR 8 | (cmsPropertyData.dataNtext IS NOT NULL AND cmsPropertyData.dataInt IS NOT NULL) OR 9 | (cmsPropertyData.dataInt IS NOT NULL AND cmsPropertyData.dataDate IS NOT NULL) OR 10 | (cmsPropertyData.dataInt IS NOT NULL AND cmsPropertyData.dataNvarchar IS NOT NULL) OR 11 | (cmsPropertyData.dataNvarchar IS NOT NULL AND cmsPropertyData.dataDate IS NOT NULL) -------------------------------------------------------------------------------- /src/Web/ASPNetFileMonitorList.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Reflection; 2 | @using System.Web; 3 | @using System.Collections; 4 | @using System.Diagnostics 5 | 6 | @model dynamic 7 | @{ 8 | var fileChangesMonitor = typeof(HttpRuntime) 9 | .GetProperty("FileChangesMonitor", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) 10 | .GetValue(null, null); 11 | 12 | var fcnVal = fileChangesMonitor.GetType() 13 | .GetField("_FCNMode", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 14 | .GetValue(fileChangesMonitor); 15 | 16 | var dirs = (Hashtable)fileChangesMonitor.GetType() 17 | .GetField("_dirs", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 18 | .GetValue(fileChangesMonitor); 19 | 20 | var dirMonAppPathInternal = fileChangesMonitor.GetType() 21 | .GetField("_dirMonAppPathInternal", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 22 | .GetValue(fileChangesMonitor); 23 | var dirMonAppPathInternalName = dirMonAppPathInternal == null ? null : dirMonAppPathInternal == null ? null : dirMonAppPathInternal.GetType() 24 | .GetField("Directory", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 25 | .GetValue(dirMonAppPathInternal); 26 | var dirMonAppPathInternalIsDirMonAppPathInternal = dirMonAppPathInternal == null ? null : dirMonAppPathInternal.GetType() 27 | .GetField("_isDirMonAppPathInternal", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 28 | .GetValue(dirMonAppPathInternal); 29 | var dirMonAppPathInternalDirMonCompletion = dirMonAppPathInternal == null ? null : dirMonAppPathInternal.GetType() 30 | .GetField("_dirMonCompletion", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 31 | .GetValue(dirMonAppPathInternal); 32 | 33 | var dirMonSubdirs = fileChangesMonitor.GetType() 34 | .GetField("_dirMonSubdirs", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 35 | .GetValue(fileChangesMonitor); 36 | var dirMonSubdirsName = dirMonSubdirs == null ? null : dirMonSubdirs.GetType() 37 | .GetField("Directory", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 38 | .GetValue(dirMonSubdirs); 39 | var dirMonSubdirsIsDirMonAppPathInternal = dirMonSubdirs == null ? null : dirMonSubdirs.GetType() 40 | .GetField("_isDirMonAppPathInternal", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 41 | .GetValue(dirMonSubdirs); 42 | var dirMonSubdirsDirMonCompletion = dirMonSubdirs == null ? null : dirMonSubdirs.GetType() 43 | .GetField("_dirMonCompletion", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 44 | .GetValue(dirMonSubdirs); 45 | } 46 | 47 | 48 | 49 | 50 | 51 | 52 | FCN Viewer 53 | 54 | 55 | 56 |
57 |
58 |

FCN Mode

59 |
    60 |
  • This is the detected FCN Mode: @fcnVal
  • 61 |
62 |

FileChangesMonitor directory monitor hash table

63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | @{ 71 | var totalFileWatchers = 0; 72 | } 73 | @foreach (System.Collections.DictionaryEntry d in dirs) 74 | { 75 | var fileMons = (Hashtable) d.Value.GetType() 76 | .GetField("_fileMons", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 77 | .GetValue(d.Value); 78 | 79 | //I'm not sure what the difference with the result of this is, the decompiled source 80 | //excludes file monitors that have something to do with "_cShortNames" 81 | var fileMonsCount = d.Value.GetType() 82 | .GetMethod("GetFileMonitorsCount", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.IgnoreCase) 83 | .Invoke(d.Value, null); 84 | 85 | totalFileWatchers += fileMons.Count; 86 | 87 | 88 | 89 | 90 | } 91 | 92 | 93 | 94 | 95 | 96 | 97 |
Directory watchedFile monitor count
@d.Key@fileMons.Count
@dirs.Count@totalFileWatchers
98 |

FCN Mode 'Single' dir monitor properties

99 |
    100 |
  • FileChangesMonitor._dirMonAppPathInternal = @dirMonAppPathInternal
  • 101 |
  • @dirMonAppPathInternalName
  • 102 |
  • _dirMonAppPathInternal._isDirMonAppPathInternal = @dirMonAppPathInternalIsDirMonAppPathInternal
  • 103 |
  • _dirMonAppPathInternal._dirMonCompletion = @dirMonAppPathInternalDirMonCompletion
  • 104 |
105 |

Normal sub dir monitor properties

106 |
    107 |
  • FileChangesMonitor._dirMonSubdirs = @dirMonSubdirs
  • 108 |
  • @dirMonSubdirsName
  • 109 |
  • _dirMonSubdirs._isDirMonAppPathInternal = @dirMonSubdirsIsDirMonAppPathInternal
  • 110 |
  • _dirMonSubdirs._dirMonCompletion = @dirMonSubdirsDirMonCompletion
  • 111 |
112 |
113 |
114 | @{ 115 | PerformanceCounter pc1 = new PerformanceCounter("ASP.NET Applications", 116 | "Requests Total", 117 | "__Total__"); 118 | 119 | PerformanceCounter pc2 = new PerformanceCounter("ASP.NET", 120 | "Application Restarts"); 121 | 122 | Type t = typeof(HttpRuntime).Assembly.GetType( 123 | "System.Web.DirMonCompletion"); 124 | 125 | int dirMonCount = (int)t.InvokeMember("_activeDirMonCompletions", 126 | BindingFlags.NonPublic 127 | | BindingFlags.Static 128 | | BindingFlags.GetField, 129 | null, 130 | null, 131 | null); 132 | 133 | // The perf client polls the server every 400 milliseconds 134 | System.Threading.Thread.Sleep(800); 135 | 136 | //Response.Output.WriteLine( 137 | // "Requests={0},Restarts={1},DirMonCompletions={2}", 138 | // pc1.NextValue(), 139 | // pc2.NextValue(), 140 | // dirMonCount); 141 | } 142 |

Performance monitor properties

143 |
    144 |
  • ASP.NET Applications - Requests Total = @pc1.NextValue()
  • 145 |
  • ASP.NET - Application Restarts = @pc2.NextValue()
  • 146 |
  • DirMonCompletions = @dirMonCount
  • 147 |
148 |
149 |
150 | 151 | 152 | -------------------------------------------------------------------------------- /src/Web/global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %> 2 | <%@ Import namespace="System.Diagnostics" %> 3 | <%@ Import namespace="System.Reflection" %> 4 | <%@ Import namespace="System.Web.Hosting" %> 5 | <%@ Import namespace="Umbraco.Core" %> 6 | <%@ Import namespace="Umbraco.Core.Logging" %> 7 | 8 | 49 | --------------------------------------------------------------------------------