With the new Store Integration now speedily being added to tenants and my previous post on adding Company Portal proving so popular, I thought I’d have a look at what else is sitting in Graph
This post from Sander Rozemuller gave me an excellent starting point and the script dropped nicely into a function:
The full script can be found here on GitHub
Function Add-MSStoreApp(){
<#
.SYNOPSIS
This function adds Microsoft Store Apps using Winget
.DESCRIPTION
The function connects to the Graph API Interface and creates a Microsoft Store App using the new experience
.EXAMPLE
Add-MSStoreApp -name "WhatsApp"
.NOTES
NAME: Add-MSStoreApp
#>
[cmdletbinding()]
param
(
$name
)
$appName = $name
$storeSearchUrl = "https://storeedgefd.dsx.mp.microsoft.com/v9.0/manifestSearch"
$body = @{
Query = @{
KeyWord = $appName
MatchType = "Substring"
}
} | ConvertTo-Json
$appSearch = Invoke-RestMethod -Uri $storeSearchUrl -Method POST -ContentType 'application/json' -body $body
$exactApp = $appSearch.Data | Where-Object { $_.PackageName -eq $appName }
$appUrl = "https://storeedgefd.dsx.mp.microsoft.com/v9.0/packageManifests/{0}" -f $exactApp.PackageIdentifier
$app = Invoke-RestMethod -Uri $appUrl -Method GET
$appId = $app.Data.PackageIdentifier
$appInfo = $app.Data.Versions[-1].DefaultLocale
$appInstaller = $app.Data.Versions[-1].Installers
$appdescription = ($appInfo.Shortdescription).ToString()
$appdescription2 = $appdescription.replace("`n"," ").replace("`r"," ").replace("\n"," ").replace("\\n"," ")
$appdeveloper = $appInfo.Publisher
$appdisplayName = $appInfo.packageName
$appinformationUrl = $appInfo.PublisherSupportUrl
$apprunAsAccount = ($appInstaller.scope | select-object -First 1)
$appisFeatured = $false
$apppackageIdentifier = $appId
$appprivacyInformationUrl = $appInfo.PrivacyUrl
$apppublisher = $appInfo.publisher
$deployUrl = "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps"
$json = @"
{
"@odata.type": "#microsoft.graph.winGetApp",
"categories": [],
"description": "$appdescription2",
"developer": "$appdeveloper",
"displayName": "$appdisplayName",
"informationUrl": "$appinformationUrl",
"installExperience": {
"runAsAccount": "$apprunAsAccount"
},
"isFeatured": false,
"notes": "",
"owner": "",
"packageIdentifier": "$apppackageIdentifier",
"privacyInformationUrl": "$appprivacyInformationUrl",
"publisher": "$apppublisher",
"repositoryType": "microsoftStore",
"roleScopeTagIds": []
}
"@
$appDeploy = Invoke-mggraphrequest -uri $deployUrl -Method POST -Body $json -ContentType "application/JSON"
return $appDeploy
}
I also separated out the assignment and switched it to Group ID rather than everyone (NOTE: the group needs to be a user group):
Function Add-StoreAppAssignment(){
<#
.SYNOPSIS
This function is used to add a store app assignment using the Graph API REST interface
.DESCRIPTION
The function connects to the Graph API Interface and adds a store app assignment
.EXAMPLE
Add-StoreAppAssignment -StoreAppID $StoreAppIdId -TargetGroupId $TargetGroupId
Adds a Store app assignment in Intune
.NOTES
NAME: Add-SStoreAppAssignment
#>
[cmdletbinding()]
param
(
$StoreAppID,
$TargetGroupId
)
$graphApiVersion = "Beta"
$Resource = "deviceManagement/deviceAppManagement/mobileApps/$storeAppID/assign"
try {
if(!$StoreAppID){
write-host "No Compliance Policy Id specified, specify a valid Compliance Policy Id" -f Red
break
}
if(!$TargetGroupId){
write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red
break
}
$JSON = @"
{
"mobileAppAssignments": [
{
"settings": {
"installTimeSettings": null,
"notifications": "showAll",
"restartSettings": null,
"@odata.type": "#microsoft.graph.winGetAppAssignmentSettings"
},
"intent": "Required",
"target": {
"groupId": "$TargetGroupId"
},
"@odata.type": "#microsoft.graph.mobileAppAssignment"
}
]
}
"@
$uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)"
Invoke-MgGraphRequest -Uri $uri -Method Post -Body $JSON -ContentType "application/json"
}
catch {
$ex = $_.Exception
$errorResponse = $ex.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
Write-Host "Response content:`n$responseBody" -f Red
Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
write-host
}
}
Then a function to display all Store Apps:
Function Get-MSStoreApps(){
<#
.SYNOPSIS
This function is used to get MS Store Apps from the Graph API REST interface
.DESCRIPTION
The function connects to the Graph API Interface and gets any MS Store Apps
.EXAMPLE
Get-MSStoreApps
Returns any MS Store Apps configured in Intune
Get-MSStoreApps -id $id
Returns specific app
.NOTES
NAME: Get-MSStoreApps
#>
[cmdletbinding()]
param
(
$id
)
$graphApiVersion = "beta"
$DCP_resource = "deviceAppManagement/MobileApps"
try {
if($Name){
$uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)/$id"
((Invoke-MgGraphRequest -Uri $uri -Method Get -OutputType PSObject).value)
}
else {
$uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)?`$filter=(isof('microsoft.graph.winGetApp'))"
(Invoke-MgGraphRequest -Uri $uri -Method Get -OutputType PSObject).value
}
}
catch {
$ex = $_.Exception
$errorResponse = $ex.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($errorResponse)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
Write-Host "Response content:`n$responseBody" -f Red
Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
write-host
}
}
Putting these together you could:
Assign all Store apps:
##Assign Store Apps
$sapps = Get-MSStoreApps
foreach ($sapp in $sapps) {
Add-StoreAppAssignment -Id $sapp.id -TargetGroupId "$intunegrp.id"GROUPIDHERE"
}
Or add multiple apps:
##List of apps to install
$storeapps = @("Company Portal", "Microsoft To Do: Lists, Tasks & Reminders", "Windows Terminal")
##Add each
foreach ($app in $storeapps) {
Add-MSStoreApp -Name $app
}
The possibilities are endless!!
trying this with MS teams and the runas account is blank. if i put SYSTEM, it will create the app and i can assign it, but it fails to install (gets stuck on “Installing”) and i am unsure how to troubleshoot.
if i use your function to generate the JSON, i get this, where RunAsAccount is blank. i know win32 apps are in preview in Intune/Winget world. i just want to validate that i cannot use this method for teams and that its not supported as a SYSTEM install and i will need to use a winget script.
{
“@odata.type”: “#microsoft.graph.winGetApp”,
“categories”: [],
“description”: “Make amazing things happen together at home, work, and school by connecting and collaborating with anyone from anywhere.”,
“developer”: “Microsoft Corporation”,
“displayName”: “Microsoft Teams”,
“informationUrl”: “https://go.microsoft.com/fwlink/p/?linkid=2126612”,
“installExperience”: {
“runAsAccount”: “”
},
“isFeatured”: false,
“largeIcon”: {
“@odata.type”: “#microsoft.graph.mimeContent”,
“type”: “string”,
“value”: “”
},
“notes”: “”,
“owner”: “”,
“packageIdentifier”: “XP8BT8DW290MPQ”,
“privacyInformationUrl”: “https://privacy.microsoft.com/en-US/privacystatement#mainnoticetoendusersmodule”,
“publisher”: “Microsoft Corporation”,
“repositoryType”: “microsoftStore”,
“roleScopeTagIds”: []
}
It doesn’t list some of the options the JSON needs on the Win32 apps, I would stick with a Winget script for these at the moment
I actually found that I could install Adobe Creative Cloud if I changed the scope to user from machine. I didn’t realize that the following was also in the error.
{“error”:{“code”:”ModelValidationFailure”,”message”:”Requested value ‘machine’ was not found.”
The icon generation fails for every app I tested with. The API also doesn’t seem to accept machine scope so unsure how to requested accepted values.
Thank you, will do some testing and get it updated
It looks like the API for images doesn’t exist any more so I will remove that from the next update.
I can’t programatically detect if it’s a user or machine group, but will add a note in the post
It’s also failing for me. Trying to get Adobe Create Cloud deployed. I also had to remove the icon capture piece as that was failing completely.
Invoke-mggraphrequest : POST https://graph.microsoft.com/beta/deviceAppManagement/mobileApps
HTTP/1.1 400 Bad Request
{“error”:{“code”:”BadRequest”,”message”:”Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON
format.”
Doesn’t work.
What doesn’t work? Are you getting any error messages?