Skip to content

Download Azure Icons #350

Download Azure Icons

Download Azure Icons #350

name: Download Azure Icons
on:
schedule:
- cron: '12 3 * * *'
workflow_dispatch:
jobs:
download:
runs-on: ubuntu-latest
name: Download
outputs:
confighash: ${{ steps.src.outputs.confighash }}
pageversion: ${{ steps.src.outputs.pageversion }}
steps:
- id: src
name: Download sources
shell: pwsh
run: |
# Azure Portal config
$portalconfig=if((irm "https://portal.azure.com/") -match 'MsPortalImpl\.redirect\(([^)]+)\)'){$matches[1]|convertfrom-json}else{throw}
$portalconfighash=$portalconfig.portalServerConfig.portalQuery.configHash
"confighash=$portalconfighash" >> $env:GITHUB_OUTPUT
$portalconfigpageversion=$portalconfig.portalServerConfig.portalQuery.pageVersion
"pageversion=$portalconfigpageversion" >> $env:GITHUB_OUTPUT
"Downloading from Azure Portal version $portalconfigpageversion with config hash $portalconfighash" >> $env:GITHUB_STEP_SUMMARY
# Requires config
$portalrequiresfile=if((irm "https://portal.azure.com/?configHash=$portalconfighash&pageVersion=$portalconfigpageversion") -match '"/Content/PortalRequireConfig/([^"]+)"'){$matches[1]}else{throw}
$portalrequires=if((irm "https://portal.azure.com/Content/PortalRequireConfig/$portalrequiresfile") -match 'MsPortalImpl\.setRequireConfig\(([^)]+)\)'){$matches[1]|convertfrom-json}else{throw}
# Icon Styles CSS
$portalcssfile=($portalrequires.requireConfig.dependencyTree.psobject.properties|?{$_.value.psobject.properties.name -contains '_generated/Less/MsPortalImpl/Base/Base.Images.css'}).name
$iconstyles=if((irm "https://portal.azure.com/$($portalcssfile).js") -match 'define\("_generated/Less/MsPortalImpl/Base/Base\.Images\.css".*return{css:"([^"]+)"'){$matches[1]}else{throw}
$iconstyles|out-file icon-styles.css -nonewline
# Image Library JSON
$imagelibraryfile=($portalrequires.requireConfig.dependencyTree.psobject.properties|?{$_.value.psobject.properties.name -contains 'MsPortalImpl/Base/Base.ImageLibrary'}).name
$imagelibraryjs=irm "https://portal.azure.com/$($imagelibraryfile).js"
$imagelibrary=([regex]'{type:(\d+),data:"(<svg[^"]+</svg>)"}').matches($imagelibraryjs)|select @{n='type';e={[int]($_.groups[1].value)}},@{n='svg';e={$_.groups[2].value}}
$svglibrary=$portalrequires.requireConfig.dependencyTree.psobject.properties|?{$_.value.psobject.properties.name -like '*.svg'}|select -exp name|sort-object -unique|%{([regex]'{type:(\d+),data:"(<svg[^"]+</svg>)"}').matches((irm "https://portal.azure.com/$($_).js"))|select @{n='type';e={[int]($_.groups[1].value)}},@{n='svg';e={$_.groups[2].value}}}
$iconlibrary=$imagelibrary+$svglibrary
$iconlibrary|convertto-json -depth 100|out-file icon-library.json
"Got total of $($iconlibrary.count) library icons" >> $env:GITHUB_STEP_SUMMARY
# Icon Assets JSON
$manifestindex=$portalconfig.portalServerConfig.supportedLanguages.IndexOf('en')
$manifesthash=$portalconfig.portalServerConfig.environment.extensionsManifestHash.assetTypes[$manifestindex][0]
$iconassets=(irm "https://portal.azure.com/Content/ExtensionManifest/$manifesthash.json").manifest
$iconassets|convertto-json -depth 100|out-file icon-assets.json
"Got total of $($iconassets.psobject.properties.value.count) manifests and $($iconassets.psobject.properties.value.assettypes.count) assets" >> $env:GITHUB_STEP_SUMMARY
- id: upload-src
name: Save sources
uses: actions/upload-artifact@v4
with:
name: src
compression-level: 9
path: |
icon-styles.css
icon-library.json
icon-assets.json
- name: Convert sources to icon data
shell: pwsh
run: |
$iconstyles=gc icon-styles.css -raw
$iconlibrary=gc icon-library.json|convertfrom-json
$iconassets=gc icon-assets.json|convertfrom-json
$iconlibraryhashtable=$iconlibrary|%{$ht=@{}}{$ht[[int]$_.type]=$_.svg}{$ht}
$icondata=$iconassets.psobject.properties|?{$_.value.assetTypes}|%{
$m=$_.name
$_.value.assetTypes|?{$_.icon.length -gt 0}|%{
$firsticon=$null
$firsticon=$_.icon[0]
$firsticontype=[int]$firsticon.type
if($firsticon.data -like '<svg*'){
$svg=$firsticon.data
}elseif($firsticontype -gt 1 -and $iconlibraryhashtable.contains($firsticontype)){
$svg=$iconlibraryhashtable[$firsticontype]
}else{
$svg='<svg viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><text x="0" y="25" class="small">'+$firsticontype+'</text></svg>'
}
$svg=$svg -replace "xmlns:svg='http://www.w3.org/2000/svg'","xmlns='http://www.w3.org/2000/svg'"
if($svg -match '^<svg([^>]*)>'){if(-not ($matches[1] -match "xmlns='http://www\.w3\.org/2000/svg'")){$svg=$svg -replace "<svg$($matches[1])>","<svg$($matches[1]) xmlns='http://www.w3.org/2000/svg'>"}}
$svg=$svg -replace '</svg>',"<style>$iconstyles</style></svg>"
$svg=$svg -replace '<title></title>',"<title>$([web.httputility]::htmlencode(($_.singularDisplayName -replace "`n",'')))</title><desc>$([web.httputility]::htmlencode(($_.description -replace "`n",' ')))</desc>"
[regex]::matches($svg,'url\(#(?<guid>[0-9a-fA-F-]{36})\)')|%{$i=0}{$svg=$svg -replace $_.groups['guid'].value,"definition-$($i++;$i)"}
[pscustomobject]@{
name="$($m)/$($_.name)"
svg=$svg
singularDisplayName=$_.singularDisplayName -replace "`n",''
pluralDisplayName=$_.pluralDisplayName -replace "`n",''
resourceType=$_.resourceType.resourceTypeName -replace "`n",''
keywords=$_.keywords -replace "`n",' '
description=$_.description -replace "`n",' '
}
}
}
"Resolved $($icondata.count) icons" >> $env:GITHUB_STEP_SUMMARY
$icondata|convertto-json -depth 100|out-file icon-data.json
- id: upload-data
name: Upload icon data
uses: actions/upload-artifact@v4
with:
name: data
compression-level: 9
path: icon-data.json
- name: Save SVG icons
shell: pwsh
run: |
$icondata=gc icon-data.json|convertfrom-json
new-item -itemtype directory -name svg|out-null
$iconstat=$icondata|%{$_.svg|new-item -path "svg/$($_.name).svg" -force}|measure-object -property length -sum
"Created $($iconstat.count) svg files with total size $([math]::round($iconstat.sum/1024/1024, 1))MB"
- name: Compose HTML
shell: pwsh
run: |
$icondata=gc icon-data.json|convertfrom-json
$html=$icondata|% `
{"<html><head><meta name=`"viewport`" content=`"width=device-width, initial-scale=1`"><title>Azure Icons</title><style>body{margin:0}table,th,td{border:1px solid #CCCCCC;border-collapse:collapse;padding:3px}tbody{word-break:break-all;}thead,tfoot{position:sticky;}thead{inset-block-start:0;background-color:#CCCCCC}tfoot{inset-block-end:0;background-color:#CCCCCC}a:link,a:visited,a:hover{color:black}a:active{color:blue}</style><script>function download(uri, name){const a=document.createElement(`"a`");a.href=uri;a.download=name+`".svg`";a.click();}</script></head><body><table><thead><tr><th scope=`"col`" style=`"width: 10%`">Click to download</th><th scope=`"col`" style=`"width: 20%`">Name</th><th scope=`"col`" style=`"width: 20%`">Type</th><th scope=`"col`" style=`"width: 20%`">Keywords</th><th scope=`"col`" style=`"width: 30%`">Description</th></tr></thead><tbody>"} `
{"<tr title=`"$($_.name)`"><td><img loading=`"lazy`" decoding=`"async`" src=`"svg/$($_.name).svg`" alt=`"$($_.singularDisplayName)`" title=`"Click to download`" onclick=`"download(this.src, this.alt)`" /></td><td>$($_.singularDisplayName)</td><td>$($_.resourceType)</td><td>$($_.keywords)</td><td>$($_.description)</td></tr>"} `
{"</tbody><tfoot><tr><td colspan=`"5`">Extracted from Azure Portal version ${{ steps.src.outputs.pageversion }} on $(get-date -f 'yyyy-MM-dd'). Icons <a target=`"_blank`" href=`"https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/permissions`">used with permission from Microsoft</a>. Source on <a target=`"_blank`" href=`"https://github.com/maskati/azure-icons`">GitHub</a>.</td></tr></tfoot></table></body></html>"}|out-string
$html|out-file index.html
"Created HTML of $([math]::round((gi index.html).length/1024/1024, 1))MB" >> $env:GITHUB_STEP_SUMMARY
- id: upload-azure-icons
name: Upload Azure Icons
uses: actions/upload-artifact@v4
with:
name: azure-icons
compression-level: 9
path: |
svg/
index.html
# https://github.com/jgraph/drawio-libs?tab=readme-ov-file#library-file-format
- name: Compose drawio library
shell: pwsh
run: |
$icondata=gc icon-data.json|convertfrom-json
$mxicons=$icondata|%{
[ordered]@{
h=50;w=50;
aspect='fixed';
title=(@($_.singularDisplayName, $_.name, $_.resourcetype)|?{-not [string]::isnullorwhitespace($_)}) -join ' | ';
data="data:image/svg+xml;base64,$([convert]::tobase64string([text.encoding]::utf8.getbytes($_.svg)))";
}
}
$mxiconsjson=$mxicons|convertto-json -compress -depth 100
$xml=[xml.xmldocument]::new()
$mxlibrary=$xml.createelement('mxlibrary')
$xml.appendchild($mxlibrary)|out-null
$mxlibrary.innertext=$mxiconsjson
$xml.outerxml|out-file drawio.xml
"Created drawio library of $([math]::round((gi drawio.xml).length/1024/1024, 1))MB" >> $env:GITHUB_STEP_SUMMARY
- name: Upload drawio library
uses: actions/upload-artifact@v4
with:
name: drawio
compression-level: 9
path: |
drawio.xml
- name: Compose Markdown
shell: pwsh
run: |
$icondata=gc icon-data.json|convertfrom-json
$markdown=$icondata|% `
{"# Azure Icons`n`nExtracted from Azure Portal version ${{ steps.src.outputs.pageversion }} on $(get-date -f 'yyyy-MM-dd').`n`n[View icons](https://maskati.github.io/azure-icons/)`n`n[Open in draw.io](https://app.diagrams.net/?splash=0&clibs=Uhttps%3A%2F%2Fraw.githubusercontent.com%2Fmaskati%2Fazure-icons%2Frefs%2Fheads%2Fmain%2Fdrawio.xml)`n`nIcons [used with permission from Microsoft](https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/permissions).`n`n| Icon | Type |`n| --- | --- |"} `
{"| [$($_.singularDisplayName -replace '\[|\]|\|','')](svg/$($_.name).svg) | $($_.resourceType -replace '/','/ ') |"} `
{""}|out-string
$markdown|out-file README.md
"Created Markdown of $([math]::round((gi README.md).length/1024/1024, 1))MB" >> $env:GITHUB_STEP_SUMMARY
- name: Upload Markdown
uses: actions/upload-artifact@v4
with:
name: markdown
compression-level: 9
path: README.md
commit:
runs-on: ubuntu-22.04
name: Commit
permissions:
actions: write # required for workflow_dispatch
contents: write # required for git push
needs: download
steps:
- name: Git checkout
uses: actions/checkout@v4
- name: Download Azure Icons
uses: actions/download-artifact@v4
with:
name: azure-icons
path: .
- name: Download markdown
uses: actions/download-artifact@v4
with:
name: markdown
path: .
- name: Download drawio
uses: actions/download-artifact@v4
with:
name: drawio
path: .
- name: Git config as workflow
run: |
git config user.name "GitHub Actions (${{ github.workflow }})"
git config user.email "<>"
- name: Git add and commit if changed
run: |
git add -u svg
git diff-index --quiet HEAD || \
git add index.html README.md drawio.xml && \
(git diff-index --quiet HEAD || git commit -m 'Update to version ${{ needs.download.outputs.pageversion }} with hash ${{ needs.download.outputs.confighash }}')
- name: Git push
id: push
run: |
git push origin ${{ github.ref_name }} --porcelain | grep -qF '[up to date]' && echo 'pushed=false' >> $GITHUB_OUTPUT || echo 'pushed=true' >> $GITHUB_OUTPUT
- name: Git tag
if: github.ref == 'refs/heads/main' && steps.push.outputs.pushed == 'true'
run: |
tag=$(date +'%Y-%m-%d')
git tag --force "$tag"
git push --force origin tag "$tag"
- name: Trigger publish
if: github.ref == 'refs/heads/main' && steps.push.outputs.pushed == 'true'
uses: actions/github-script@v7
with:
script: |
github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'static.yml',
ref: 'main',
})