private async Task CreateBlobAsync(IntuneAppPackage package, MobileAppContentFile contentFile) { var blockCount = 0; var blockIds = new List <string>(); const int chunkSize = 5 * 1024 * 1024; package.Data.Seek(0, SeekOrigin.Begin); var lastBlockId = (Math.Ceiling((double)package.Data.Length / chunkSize) - 1).ToString("0000"); foreach (var chunk in Chunk(package.Data, chunkSize, false)) { var blockId = blockCount++.ToString("0000"); logger.LogInformation($"Uploading block {blockId} of {lastBlockId} to {contentFile.AzureStorageUri}."); await using (var ms = new MemoryStream(chunk)) { await TryPutBlockAsync(contentFile, blockId, ms); } blockIds.Add(blockId); } await new CloudBlockBlob(new Uri(contentFile.AzureStorageUri)).PutBlockListAsync(blockIds); }
/// <inheritdoc /> public async Task PublishAsync(IntuneAppPackage package) { logger.LogInformation($"Publishing Intune app package for {package.App.DisplayName}."); var app = await GetAppAsync(package.App); var sw = Stopwatch.StartNew(); var requestBuilder = new MobileLobAppRequestBuilder(msGraphClient.DeviceAppManagement.MobileApps[app.Id] .AppendSegmentToRequestUrl(app.ODataType.TrimStart('#')), msGraphClient); MobileAppContent content = null; // if content has never been committed, need to use last created content if one exists, otherwise an error is thrown if (app.CommittedContentVersion == null) { content = (await requestBuilder.ContentVersions.Request().OrderBy("id desc").GetAsync()).FirstOrDefault(); } if (content == null) { content = await requestBuilder.ContentVersions.Request().AddAsync(new MobileAppContent()); } else if ((await requestBuilder.ContentVersions[content.Id].Files.Request().Filter("isCommitted ne true").GetAsync()).Any()) { // partially committed content - delete that content version await requestBuilder.ContentVersions[content.Id].Request().DeleteAsync(); } // manifests are only supported if the app is a WindowsMobileMSI (not a Win32 app installing an msi) if (!(app is WindowsMobileMSI)) { package.File.Manifest = null; } await CreateAppContentFileAsync(requestBuilder.ContentVersions[content.Id], package); MobileLobApp update = (MobileLobApp)Activator.CreateInstance(package.App.GetType()); update.CommittedContentVersion = content.Id; await msGraphClient.DeviceAppManagement.MobileApps[app.Id].Request().UpdateAsync(update); logger.LogInformation($"Published Intune app package for {app.DisplayName} in {sw.ElapsedMilliseconds}ms."); }
private async Task CreateBlobAsync(IntuneAppPackage package, MobileAppContentFile contentFile, IMobileAppContentFileRequestBuilder contentFileRequestBuilder) { var blockCount = 0; var blockIds = new List <string>(); const int chunkSize = 25 * 1024 * 1024; package.Data.Seek(0, SeekOrigin.Begin); var lastBlockId = (Math.Ceiling((double)package.Data.Length / chunkSize) - 1).ToString("0000"); var sw = Stopwatch.StartNew(); foreach (var chunk in Chunk(package.Data, chunkSize, false)) { if (sw.ElapsedMilliseconds >= 450000) { contentFile = await RenewStorageUri(contentFileRequestBuilder); sw.Restart(); } var blockId = blockCount++.ToString("0000"); logger.LogInformation($"Uploading block {blockId} of {lastBlockId} to {contentFile.AzureStorageUri}."); await using (var ms = new MemoryStream(chunk)) { try { await TryPutBlockAsync(contentFile, blockId, ms); } catch (StorageException ex) when(ex.RequestInformation.HttpStatusCode == 403) { // normally the timer should account for renewing upload URIs, but the Intune APIs are fundamentally unstable and sometimes 403s will be encountered randomly contentFile = await RenewStorageUri(contentFileRequestBuilder); sw.Restart(); await TryPutBlockAsync(contentFile, blockId, ms); } } blockIds.Add(blockId); } await new CloudBlockBlob(new Uri(contentFile.AzureStorageUri)).PutBlockListAsync(blockIds); }
private async Task <MobileAppContentFile> AddContentFileAsync(IMobileAppContentRequestBuilder requestBuilder, IntuneAppPackage package) { return(await requestBuilder.Files.Request() .WithMaxRetry(10) .WithRetryDelay(30) .WithShouldRetry((delay, count, r) => r.StatusCode == HttpStatusCode.NotFound) .AddAsync(package.File)); }
private async Task CreateAppContentFileAsync(IMobileAppContentRequestBuilder requestBuilder, IntuneAppPackage package) { // add content file var contentFile = await AddContentFileAsync(requestBuilder, package); // waits for the desired status, refreshing the file along the way async Task WaitForStateAsync(MobileAppContentFileUploadState state) { logger.LogInformation($"Waiting for app content file to have a state of {state}."); // ReSharper disable AccessToModifiedClosure - intended var waitStopwatch = Stopwatch.StartNew(); while (true) { contentFile = await requestBuilder.Files[contentFile.Id].Request().GetAsync(); if (contentFile.UploadState == state) { logger.LogInformation($"Waited {waitStopwatch.ElapsedMilliseconds}ms for app content file to have a state of {state}."); return; } var failedStates = new[] { MobileAppContentFileUploadState.AzureStorageUriRequestFailed, MobileAppContentFileUploadState.AzureStorageUriRenewalFailed, MobileAppContentFileUploadState.CommitFileFailed }; if (failedStates.Contains(contentFile.UploadState.GetValueOrDefault())) { throw new InvalidOperationException($"{nameof(contentFile.UploadState)} is in a failed state of {contentFile.UploadState} - was waiting for {state}."); } const int waitTimeout = 240000; const int testInterval = 2000; if (waitStopwatch.ElapsedMilliseconds > waitTimeout) { throw new InvalidOperationException($"Timed out waiting for {nameof(contentFile.UploadState)} of {state} - current state is {contentFile.UploadState}."); } await Task.Delay(testInterval); } // ReSharper restore AccessToModifiedClosure } // refetch until we can get the uri to upload to await WaitForStateAsync(MobileAppContentFileUploadState.AzureStorageUriRequestSuccess); var sw = Stopwatch.StartNew(); await CreateBlobAsync(package, contentFile); logger.LogInformation($"Uploaded app content file in {sw.ElapsedMilliseconds}ms."); // commit await requestBuilder.Files[contentFile.Id].Commit(package.EncryptionInfo).Request().PostAsync(); // refetch until has committed await WaitForStateAsync(MobileAppContentFileUploadState.CommitFileSuccess); }
private async Task CreateAppContentFileAsync(IMobileAppContentRequestBuilder requestBuilder, IntuneAppPackage package) { // add content file var contentFile = await AddContentFileAsync(requestBuilder, package); // refetch until we can get the uri to upload to contentFile = await WaitForStateAsync(requestBuilder.Files[contentFile.Id], MobileAppContentFileUploadState.AzureStorageUriRequestSuccess); var sw = Stopwatch.StartNew(); await CreateBlobAsync(package, contentFile, requestBuilder.Files[contentFile.Id]); logger.LogInformation($"Uploaded app content file in {sw.ElapsedMilliseconds}ms."); // commit await requestBuilder.Files[contentFile.Id].Commit(package.EncryptionInfo).Request().PostAsync(); // refetch until has committed await WaitForStateAsync(requestBuilder.Files[contentFile.Id], MobileAppContentFileUploadState.CommitFileSuccess); }