-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathConnectorCheck.ps1
357 lines (267 loc) · 12.1 KB
/
ConnectorCheck.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
<#
.SYNOPSIS
Checks connectors and actions used in power platform solutions.
.DESCRIPTION
Examines an unpacked solution and reports on connectors and actions used. Deprecated actions are downloaded from https://connectorstatus.com. Deprecated actions used in the solution are output along with their location. Power Automate Cloud Flows and Power Fx (e.g. Canvas Apps) are checked.
TODO
Improve to report instances of deprecated triggers
NOTES/WARNINGS
1. The power apps solution structure is proprietary and subject to change.
2. The solution must be exported and unpacked, additionally any .msapp files within the solution must also be unpacked.
.PARAMETER solnFolder
Specifies the path to the exploded solution folder.
.PARAMETER skipCloudFlows
Indicates if checking of cloud flows should be not be performed.
.PARAMETER skipPowerFx
Indicates if checking of Canvas Apps/PowerFx should be not be performed.
.LINK
https://philcole.org/post/connector-check
.EXAMPLE
PS> ConnectorCheck.ps1 -solnfolder ./SolutionPackage
#>
param (
[Parameter(Mandatory = $true, HelpMessage = "Enter the path to the unpacked solution")]
[Alias("p", "path")]
[string]$solnfolder,
[switch]$skipCloudFlows = $false,
[switch]$skipPowerFx = $false
)
# We use a list of deprecated actions already generated in the cloud. See https://connectorstatus.com for details.
# If you want to produce your own list, then see https://philcole.org/post/deprecated-actions/ for one method.
Set-Variable -Name DeprecatedActionsUrl -Option Constant -Value "https://connectorstatus.com/Deprecated.json"
# Hash of deprecated actions read in a pre-generated JSON file containing deprecated actions
$deprecatedActions = @{}
# List of actions found in flows and Power Fx (canvas apps) scanned
$usedActions = [System.Collections.Generic.List[ActionUsage]]::new()
class ActionUsage {
# Prevent invalid values
[ValidateNotNullOrEmpty()][string]$Connector
[ValidateNotNullOrEmpty()][string]$Action
[ValidateNotNullOrEmpty()][string]$Filename
[ValidateNotNullOrEmpty()][string]$Type
[string]$ActionUniqueName
[string]$Description
[string]$ConnectionRef
[int]$LineNum
[bool]$Deprecated
# Have a constructor to force properties to be set
ActionUsage($Connector, $Action, $Filename, $Type) {
$this.Connector = $Connector
$this.Action = $Action
$this.Filename = $Filename
$this.Type = $Type
$this.ActionUniqueName = BuildKey -connector $Connector -action $Action
$this.Deprecated = IsActionDeprecated($this.ActionUniqueName)
}
}
Function ReadDeprecatedActions {
# Download the latest deprecated actions.
Write-Host "Downloading deprecated actions from $DeprecatedActionsUrl"
try {
$r = Invoke-WebRequest -Uri $DeprecatedActionsUrl
}
catch {
$_.Exception.Message
exit
}
#$deprecatedfilename = Join-Path ${PSScriptRoot} "Deprecated.json"
$r.Content | ConvertFrom-Json | Foreach-Object {
$connector = $_;
$uniqueName = $_.UniqueName;
$_.Actions | ForEach-Object {
$action = $_
$operationId = $_.OperationId
$key = BuildKey -connector ${uniqueName} -action ${operationId}
$deprecatedActions[$key] = [PSCustomObject]@{
Connector = $connector
Action = $action
}
}
}
Write-Host "Read" $deprecatedActions.Count "deprecated actions"
}
Function BuildKey {
param ([string]$connector, [string]$action)
return "${connector}|${action}"
}
Function IsActionDeprecated([string]$key) {
if ($deprecatedActions.ContainsKey($key)) {
return $true
}
return $false
}
Function Get-DeepProperty([object] $InputObject, [string] $Property) {
$path = $Property -split '\.'
$obj = $InputObject
$path | ForEach-Object { $obj = $obj.$_ }
$obj
}
Function RemoveLeadingString {
param ([string]$inputStr, [string]$leading)
if ($inputStr.StartsWith($leading)) {
return $inputStr.Replace($leading, "")
}
return $inputStr
}
Function CheckIsSolutionFolder {
$solnxml = Join-Path $solnfolder "Other"
$solnxml = Join-Path $solnxml "Solution.xml"
if (!( Test-Path $solnxml -PathType Leaf)) {
Write-Error "Not valid solution folder. $solnxml does not exist"
exit
}
}
Function ScanFlows {
$workflowfolder = Join-Path $solnfolder "Workflows"
if (!( Test-Path $workflowfolder -PathType Container)) {
Write-Host "No flows exist in solution"
return
}
Write-Host "Scanning flows in $workflowfolder"
# Perhaps we could check the metadata xml relating to the flow to check if it's a cloud flow.
# I think cloud flows are <Category>5</Category> (but need to check)
# We don't need to do that right now, because the all json files are cloud flows.
Get-ChildItem $workflowfolder -Filter *.json |
Foreach-Object {
$flowFilename = $_.FullName
Write-Host "Scanning flow"$_.Name
$flow = Get-Content $flowFilename | ConvertFrom-Json
$actions = $flow.properties.definition.actions
ScanFlowActions -depth 2 -actions $actions
}
}
Function ScanFlowActions {
param ([int]$depth, [object]$actions)
if ($actions.getType().Name -eq "PSCustomObject") {
# Stackoverflow (ahem) helped with navigating dynamics object
# https://stackoverflow.com/questions/27195254/dynamically-get-pscustomobject-property-and-values/27195828
$actionObjects = Get-Member -InputObject $actions -MemberType NoteProperty
foreach ($actionObject in $actionObjects) {
$actionBody = $actions | Select-Object -ExpandProperty $actionObject.Name
$actionName = $actionObject.Name
$type = $actionBody.type
$description = ""
if ($actionBody.PSobject.Properties.Name -contains "description") {
$description = $actionBody.description
}
# Check if this is using an OpenApiConnection
if ($type -eq "OpenApiConnection") {
$connectionRef = $actionBody.inputs.host.connectionName
$operationId = $actionBody.inputs.host.operationId
$propertyPath = "properties.connectionReferences." + $connectionRef + ".api.name"
$connector = Get-DeepProperty -InputObject $flow -Property $propertyPath
$connector = RemoveLeadingString -inputStr $connector -leading "shared_"
$connRefLogicalNamePath = "properties.connectionReferences." + $connectionRef + ".connection.connectionReferenceLogicalName"
$connRefLogicalName = Get-DeepProperty -InputObject $flow -Property $connRefLogicalNamePath
$type = "${connector}:${operationId} via ${connRefLogicalName}"
$key = BuildKey -connector $connector -action $operationId
if ($deprecatedActions.ContainsKey($key)) {
Write-Host "Deprecated action in flow ${flowFilename}: ${operationId} on connector ${connector} (connectionRef: ${connRefLogicalName}) in step ${actionName}: ${description}"
}
# Store action usage
$usage = [ActionUsage]::new($connector, $operationId, $flowFilename, "CloudFlow")
$usage.Description = "${actionName}: ${description}"
$usage.ConnectionRef = $connRefLogicalName
$usedActions.add($usage)
}
}
# $message = " " * $depth + "<${type}>: ${description}"
# Write-Host $message
# Does this action have an "actions" node below it?
$actionNodes = Get-Member -InputObject $actionBody -MemberType NoteProperty
$childActions = $actionNodes | Where-Object -Property Name -eq -Value "actions"
if ($null -ne $childActions) {
# We have child actions - recurse into actions list and drill through these actions
[int]$newDepth = $depth + 2
$childActionObject = $actionBody | Select-Object -ExpandProperty $childActions.Name
# Recurse
ScanFlowActions -depth $newDepth -actions $childActionObject
}
}
}
Function ScanAllCanvasApps {
# NOTE: We expect the canvas apps in the solution to have already been exploded with 'pac canvas unpac'
# An unpacked canvas app can be identified in a solution by the existnace of the 'CanvasManifest.json' file
Get-ChildItem -Path $solnfolder -Filter CanvasManifest.json -Recurse | ForEach-Object {
ScanUnpackedCanvasApp -folder $_.Directory.FullName
}
}
Function ScanUnpackedCanvasApp {
Param ([string]$folder)
Write-Host "Scanning CanvasApp in $folder"
# Read "Connections.json" file
$connectionsFilename = Join-Path $folder "Connections"
$connectionsFilename = Join-Path $connectionsFilename "Connections.json"
$connections = Get-Content $connectionsFilename | ConvertFrom-Json
$connectionObjects = Get-Member -InputObject $connections -MemberType NoteProperty
# Find all connectors used in Connections.json
$connectors = @{}
foreach ($connectionObject in $connectionObjects) {
$connection = $connections | Select-Object -ExpandProperty $connectionObject.Name
$connector = $connection.connectionRef.id
$dataSources = $connection.dataSources[0]
$connector = RemoveLeadingString -inputStr $connector -leading "/providers/microsoft.powerapps/apis/shared_"
$connectors.Add($dataSources, 1)
$displayName = $connection.connectionRef.displayName
Write-Verbose "Uses connector $connector ($displayName) $dataSources"
}
if ($connectors.Keys.Count -eq 0) {
Write-Verbose "No connectors found in canvas app"
return
}
# Build a regex to search the *.fx.yaml Power Fx files
$connectors = $connectors.keys | Join-String -Separator "|"
$regex = "((^|[^\w])(?<connector>(${connectors}))\.(?<action>\w+))+"
Write-Debug "Searching using regex: $regex"
[regex]$rx = $regex
# Search the Power Fx files, and see if they contain any usages of each dataSource
Get-ChildItem -Path $folder -Filter *.fx.yaml -Recurse -File | ForEach-Object {
$filepath = $_.FullName
$linenum = 1
$c = Get-Content -Path $filepath
$c | ForEach-Object {
$results = $rx.Match( $_ )
if ($results.Success) {
foreach ($actionmatch in $results) {
$connector = $actionmatch.Groups["connector"].Value
$operationId = $actionmatch.Groups["action"].Value
#Write-Host "${connector}:${operationId} in ${filepath}:${linenum}"
$usage = [ActionUsage]::new($connector, $operationId, $filepath, "Power Fx")
$usage.LineNum = $linenum
$usedActions.add($usage)
}
}
$linenum = $linenum + 1
}
}
}
## MAIN BODY
if ($skipCloudFlows -and $skipPowerFx) {
Write-Error "Nothing to do. Only one of skipCloudFlows or skipPowerFx can be set."
exit
}
CheckIsSolutionFolder
ReadDeprecatedActions
if ($skipCloudFlows -ne $true) {
ScanFlows
}
if ($skipPowerFx -ne $true) {
ScanAllCanvasApps
}
$numUsedActions = $($usedActions | Measure-Object).Count
Write-Output "`nSummary of $numUsedActions usages:`n"
$usedActions | Group-Object -Property ActionUniqueName -NoElement | Sort-Object -Property Count -Descending | ForEach-Object {
$deprecatedWarning = "";
if ($deprecatedActions.ContainsKey($_.Name)) {
$deprecatedWarning = "*** DEPRECATED *** "
}
$message = "{0,3} usages of {2}{1}" -f $_.Count, $_.Name, $deprecatedWarning
Write-Host $message
}
$deprecatedUsages = $usedActions | Where-Object -Property Deprecated -EQ -Value $true
$numDeprecatedUsages = $($deprecatedUsages | Measure-Object).Count
Write-Output "`nUsages of deprecated actions: $numDeprecatedUsages`n"
$deprecatedUsages | ForEach-Object {
$message = "{0}|{1} in {2} at {3}:{4}" -f $_.Connector, $_.Action, $_.Type, $_.Filename, $_.LineNum
Write-Host $message
}