├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── EventGridTrigger
├── function.json
└── run.ps1
├── LICENSE
├── Negotiate
├── function.json
└── run.ps1
├── README.md
├── extensions.csproj
├── host.json
├── powershell-func-resource-monitor.gif
├── profile.ps1
├── requirements.psd1
└── www
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | obj
3 | csx
4 | .vs
5 | edge
6 | Publish
7 |
8 | *.user
9 | *.suo
10 | *.cscfg
11 | *.Cache
12 | project.lock.json
13 |
14 | /packages
15 | /TestResults
16 |
17 | /tools/NuGet.exe
18 | /App_Data
19 | /secrets
20 | /data
21 | .secrets
22 | appsettings.json
23 | local.settings.json
24 |
25 | node_modules
26 | dist
27 |
28 | # Local python packages
29 | .python_packages/
30 |
31 | # Python Environments
32 | .env
33 | .venv
34 | env/
35 | venv/
36 | ENV/
37 | env.bak/
38 | venv.bak/
39 |
40 | # Byte-compiled / optimized / DLL files
41 | __pycache__/
42 | *.py[cod]
43 | *$py.class
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "ms-azuretools.vscode-azurefunctions",
4 | "ms-vscode.PowerShell"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Attach to PowerShell Functions",
6 | "type": "PowerShell",
7 | "request": "attach",
8 | "customPipeName": "AzureFunctionsPSWorker",
9 | "runspaceId": 1,
10 | "preLaunchTask": "func: host start"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "azureFunctions.projectRuntime": "~2",
3 | "azureFunctions.projectLanguage": "PowerShell",
4 | "azureFunctions.deploySubpath": ".",
5 | "azureFunctions.preDeployTask": "func: extensions install",
6 | "files.exclude": {
7 | "obj": true,
8 | "bin": true
9 | },
10 | "debug.internalConsoleOptions": "neverOpen"
11 | }
12 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "func",
6 | "command": "host start",
7 | "problemMatcher": "$func-watch",
8 | "dependsOn": "func: extensions install",
9 | "isBackground": true
10 | }
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/EventGridTrigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "eventGridTrigger",
5 | "name": "eventGridEvent",
6 | "direction": "in"
7 | },
8 | {
9 | "type": "signalR",
10 | "direction": "out",
11 | "name": "SignalRMessages",
12 | "hubName": "resources"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/EventGridTrigger/run.ps1:
--------------------------------------------------------------------------------
1 | param($eventGridEvent, $TriggerMetadata)
2 |
3 | $resourceId = $eventGridEvent.subject
4 | $eventType = $eventGridEvent.eventType
5 |
6 | if ($eventType -ne "Microsoft.Resources.ResourceDeleteSuccess") {
7 | $resource = Get-AzResource -ResourceId $resourceId
8 | }
9 |
10 | Push-OutputBinding -Name SignalRMessages -Value (@{
11 | Target = 'newResourceEvent'
12 | Arguments = , @{
13 | resourceGroupName = $resource.ResourceGroupName
14 | resourceName = $resource.ResourceName
15 | resourceId = $resourceId
16 | resourceProvider = $eventGridEvent.data.resourceProvider
17 | location = $resource.Location
18 | timestamp = $eventGridEvent.eventTime
19 | operationName = $eventGridEvent.data.operationName
20 | username = $eventGridEvent.data.claims["name"]
21 | eventType = $eventGridEvent.eventType
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Anthony Chu
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 |
--------------------------------------------------------------------------------
/Negotiate/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "authLevel": "anonymous",
5 | "type": "httpTrigger",
6 | "direction": "in",
7 | "name": "Request",
8 | "methods": [
9 | "post"
10 | ]
11 | },
12 | {
13 | "type": "http",
14 | "direction": "out",
15 | "name": "Response"
16 | },
17 | {
18 | "type": "signalRConnectionInfo",
19 | "direction": "in",
20 | "name": "ConnectionInfo",
21 | "hubName": "resources"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/Negotiate/run.ps1:
--------------------------------------------------------------------------------
1 | using namespace System.Net
2 |
3 | param($Request, $TriggerMetadata, $ConnectionInfo)
4 |
5 | Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
6 | StatusCode = [HttpStatusCode]::OK
7 | ContentType = "application/json"
8 | Body = $ConnectionInfo
9 | })
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Real-time Azure Resource Monitor
2 |
3 | Powered by PowerShell [Azure Functions](https://docs.microsoft.com/azure/azure-functions/?WT.mc_id=functionsresourcemonitor-github-antchu), [Azure Event Grid](https://docs.microsoft.com/azure/event-grid/?WT.mc_id=functionsresourcemonitor-github-antchu), and [Azure SignalR Service](https://docs.microsoft.com/azure/azure-signalr/?WT.mc_id=functionsresourcemonitor-github-antchu)
4 |
5 | ## How does this work?
6 |
7 | 1. Resources are changed in an Azure subscription
8 | 1. Azure Event Grid reacts to event and triggers an Azure Function
9 | 1. PowerShell Azure Function retrieves more information about the resource
10 | 1. Function uses Azure SignalR Service output binding to send messages
11 | 1. Web client receives messages over WebSocket
12 |
13 | **Check out [PowerShell for Azure Functions](https://docs.microsoft.com/azure/azure-functions/functions-create-first-function-powershell?WT.mc_id=functionsresourcemonitor-github-antchu) for more information**
14 |
15 | 
--------------------------------------------------------------------------------
/extensions.csproj:
--------------------------------------------------------------------------------
1 |
Event | 34 |Type | 35 |Resource Group | 36 |Resource | 37 |Location | 38 |Username | 39 |Timestamp | 40 | 41 | 42 |
---|---|---|---|---|---|---|
{{ resource.eventType | shortResourceType }} | 44 |{{ resource.resourceProvider }} | 45 |{{ resource.resourceGroupName }} | 46 |{{ resource.resourceName }} | 47 |{{ resource.location }} | 48 |{{ resource.username }} | 49 |{{ resource.timestamp }} | 50 |