Monday, September 26, 2022

PowerShell: Search Particular Code In PowerApps

Hello Friends,

Welcome back with another post on PowerShell. Yes, PowerShell. Sometimes, we face scenario, where we have to find use of a particular control or code or flow in the entire app. If, the app is smaller, you may do it manually. However, if the app is too big as well as complex, then it would be a challenging task. Today, we going to provide a solution for this problem. We can perform this activity using PowerShell.

Let's start-

  1. Let's suppose, we have created below app in PowerApps.
  2. It is having one label, one text input and two buttons.
  3. Each button is calling a flow.
  4. Now, export this package and place it in a folder (for example folder named "SearchContent").
  5. Traverse this package as below-
    1. Package.zip >> Microsoft.PowerApps >> apps >> <Numeric ID> folder.
  6. Here, you will find JSONs, Images, MSAPP file and one more file.
  7. Copy the MSAPP file and paste at the root folder (SearchContent) location.
  8. Now copy the below PowerShell code and paste it in a notepad file.
    1. # Color Options: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, 
      # Color Options: DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
      
      ########################## INPUT PARAMETER ##########################
      	Param
      	(
      		[string] $solutionPath,
      		[string] $searchString
      	)
      
      ######################## FILE BASE  LOCATION ########################
      # Need to encode in proper UTF8
      	$locationPath = (Get-Location).Path
      	$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
      	[System.Environment]::CurrentDirectory = $locationPath
      
      ######################### GLOBAL  VARIABLES #########################
      	$solutionName = [System.IO.Path]::GetFileNameWithoutExtension($solutionPath)
      	$strNow = Get-Date -Format "yyyyMMdd_HHmmss"
      	$newSolutionFolder = "$($solutionName)_$($strNow)"
      	$resultTable = New-Object system.Data.DataTable 'OutPutDataTable'
      	$newcol = New-Object system.Data.DataColumn 'Sr No',([string]);
      	$resultTable.Columns.Add($newcol)
      	$newcol = New-Object system.Data.DataColumn 'Attribute Name',([string]);
      	$resultTable.Columns.Add($newcol)
      	$newcol = New-Object system.Data.DataColumn 'Attribute Value',([string]);
      	$resultTable.Columns.Add($newcol)
      	$intParentSrNo = 0
      
      ######################### SUPPORT FUNCTIONS #########################
      
      # ====> Parsing file and folder
      	function ParseFilesNFolders
      	{
      		param([string]$thisFolder)
      		$folderContent = Get-ChildItem -Path $thisFolder
      
      		foreach($content in $folderContent)
      		{
      			if($content.GetType() -eq [System.IO.DirectoryInfo])
      			{
      				ParseFilesNFolders -thisFolder "$thisFolder\$content"
      			}
      			else
      			{
      				$ext = [System.IO.Path]::GetExtension($content)
      
      				Switch($ext)
      				{
      					".json"
      					{
      						if($thisFolder -like "*Controls")
      						{
      							$row = $resultTable.NewRow()
      							$row.'Attribute Name' = ("")
      							$row.'Attribute Value' = ("")
      							$resultTable.Rows.Add($row)
      							$intParentSrNo++
      							$row = $resultTable.NewRow()
      							$row.'Sr No' = ($intParentSrNo)
      							$row.'Attribute Name' = ("File Name")
      							$row.'Attribute Value' = ("**** " + $content + " ****").ToUpper()
      							$resultTable.Rows.Add($row)
      
      							$json = Get-Content -Raw -Path "$thisFolder\$content" | ConvertFrom-Json
      							Get-LeafProperty $json
      						}
      					}
      					".msapp"
      					{
      						$msappFileName = [System.IO.Path]::GetFileNameWithoutExtension("$thisFolder\$content")
      						New-Item -Path ".\Temp\$newSolutionFolder" -Name $msappFileName -ItemType "directory" | Out-Null
      						Copy-Item "$thisFolder\$content" -Destination ".\Temp\$newSolutionFolder\$content"
      						Rename-Item ".\Temp\$newSolutionFolder\$content" "$msappFileName.zip"
      						Expand-Archive ".\Temp\$newSolutionFolder\$msappFileName.zip" -DestinationPath ".\Temp\$newSolutionFolder\$msappFileName"
      						Remove-Item -Path ".\Temp\$newSolutionFolder\$msappFileName.zip" -Force
      						Remove-Item -Path "$thisFolder\$content" -Force
      						ParseFilesNFolders -thisFolder ".\Temp\$newSolutionFolder\$msappFileName"
      						Compress-Archive -Path ".\Temp\$newSolutionFolder\$msappFileName\*" -DestinationPath "$thisFolder\$msappFileName.zip"
      						Rename-Item "$thisFolder\$msappFileName.zip" "$content"
      					}
      					Default
      					{
      					}
      				}
      			}
      		}
      	}
      
      	function Get-LeafProperty
      	{
      		param([Parameter(ValueFromPipeline)] [object] $InputObject, [string] $NamePath)
      		process
      		{
      			if ($null -eq $InputObject -or $InputObject -is [DbNull] -or $InputObject.GetType().IsPrimitive -or $InputObject.GetType() -in [string], [datetime], [datetimeoffset], [decimal], [bigint])
      			{
       				if($NamePath -like '*.Name' -and $NamePath -notlike '*.Template.Name')
      				{
      					if($NamePath -eq 'TopParent.Name')
      					{
      						$row = $resultTable.NewRow()
      						$row.'Attribute Name' = ("Screen Name")
      						$row.'Attribute Value' = ("**** " + ($InputObject).ToString() + " ***").ToUpper()
      						$resultTable.Rows.Add($row)
      					}
      					else
      					{
      						$row = $resultTable.NewRow()
      						$row.'Attribute Name' = ($NamePath)
      						$row.'Attribute Value' = ($InputObject)
      						$resultTable.Rows.Add($row)
      					}
      
      				}
      				if($InputObject -like '*' + $searchString + '*')
      				{
      					$row = $resultTable.NewRow()
      					$row.'Attribute Name' = ($NamePath)
      					$row.'Attribute Value' = ($InputObject)
      					$resultTable.Rows.Add($row)
      				}
      			}
      			elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [System.Collections.IDictionary])
      			{
      				$i = 0
      				foreach ($o in $InputObject)
      				{
      					Get-LeafProperty $o ($NamePath + '[' + $i++ + ']')
      				}
      			}
      			else
      			{
      				# A non-quasi-primitive scalar object or a dictionary:
      				# enumerate its properties / entries.
      
      				$props = if ($InputObject -is [System.Collections.IDictionary]) { $InputObject.GetEnumerator() } else { $InputObject.psobject.properties }
      				$sep = '.' * ($NamePath -ne '')
      				foreach ($p in $props)
      				{
      					Get-LeafProperty $p.Value ($NamePath + $sep + $p.Name)
      				}
      			}
      		}
      	}
      
      
      ###################### SCRIPT EXECUTION  POINT ######################
      
      	try
      	{
      		cls
      		Write-Host "PROCESS STARTED."
      		#Check if Output and Temp folder exist
      		If(Test-Path ".\Output")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Output" -ItemType "directory" | Out-Null
      		}
      
      		If(Test-Path ".\Temp")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Temp" -ItemType "directory" | Out-Null
      		}
      
      # Get file name and create folder in Output Temp file
      		New-Item -Path ".\Output" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      		New-Item -Path ".\Temp" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      
      # Copy file solution
      		Copy-Item $solutionPath ".\Output\$newSolutionFolder"
      
      # Parse file solution
      		ParseFilesNFolders -thisFolder ".\Output\$newSolutionFolder"
      	}
      	catch
      	{
      		# Write-Host "ERROR:" -ForegroundColor Red
      		# Write-Host $_.Exception.Message -ForegroundColor Red
      	}
      	$resultTable | Format-Table
      	Write-Host "PROCESS COMPLETED."
  9. Save the notepad file as "SearchContentInPowerAppsUsingDataTable.ps1".
  10. Now, open the PowerShell command prompt-
  11. Traverse to the folder "SearchContent', were you had saved the powershell script as wel as the MSAPP file.
  12. Now, use the below command
    1. .\SearchContentInPowerAppsUsingDataTable.ps1 -solutionPath .\N51de7fa8-8f94-4ee7-a1aa-3fa56fae5cc2-do
      cument.msapp -searchString "POC-"
      
  13. "-solutionPath" and "-searchString" are the input parameters of the Powershell script we have used here. "POC-" is the content we are searching for. Here you can use you own search string.
  14. Once done, press Enter.
  15. Above is the outcome of search. Let's interpret it. In PowerApps every code is saved against the attribute "InvariantScript".
  16. The "Name" attribute shown just before the "InvariantScript" is the name of control, in which the code is written.
  17. In above screenshot, we can see that there are two instances where our search string matches.
  18. It means, "Button1" and "Button2" are the controls, where we have to check our code.
  19. Now the next question, on which screen, these controls are placed?
  20. So the answer lies in the attribute "TopParent.Name".
  21. These controls are placed on "Screen1". In case, if we have multiple screens in the App, then this script, shows the controls screen wise. As we can see, in one of the other app, I had created, which was having multiple screens.
  22. If the app is too large and complex, then script execution may take time, In such cases, please be patient until the result displayed on screen. 
  23. This is how, you can find your code.
  24. If you want more precise results, where you can also look the attribute name upon which that particular code is written, then use below script:-
    1. # Color Options: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, 
      # Color Options: DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White
      
      ########################## INPUT PARAMETER ##########################
      	Param
      	(
      		[string] $solutionPath,
      		[string] $searchString
      	)
      
      ######################## FILE BASE  LOCATION ########################
      # Need to encode in proper UTF8
      	$locationPath = (Get-Location).Path
      	$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False
      	[System.Environment]::CurrentDirectory = $locationPath
      
      ######################### GLOBAL  VARIABLES #########################
      	$solutionName = [System.IO.Path]::GetFileNameWithoutExtension($solutionPath)
      	$strNow = Get-Date -Format "yyyyMMdd_HHmmss"
      	$newSolutionFolder = "$($solutionName)_$($strNow)"
      	$resultTable = New-Object system.Data.DataTable 'OutPutDataTable'	
      	$newcol = New-Object system.Data.DataColumn 'Attribute Name',([string]);
         	$resultTable.Columns.Add($newcol)
         	$newcol = New-Object system.Data.DataColumn 'Attribute Value',([string]);
         	$resultTable.Columns.Add($newcol)
       
      ######################### SUPPORT FUNCTIONS #########################
      
      # ====> Parsing file and folder
      	function ParseFilesNFolders
      	{
      		param([string]$thisFolder)
      		$folderContent = Get-ChildItem -Path $thisFolder
      
      		foreach($content in $folderContent)
      		{
      			if($content.GetType() -eq [System.IO.DirectoryInfo])
      			{
      				ParseFilesNFolders -thisFolder "$thisFolder\$content"
      			}
      			else
      			{
      				$ext = [System.IO.Path]::GetExtension($content)
      
      				Switch($ext)
      				{
      					".json"
      					{
      						if($thisFolder -like "*Controls")
      						{
                                  $row = $resultTable.NewRow()
                                  $row.'Attribute Name' = ("")
                                  $row.'Attribute Value' = ("")
                                  $resultTable.Rows.Add($row)
      
                                  $row = $resultTable.NewRow()
                                  $row.'Attribute Name' = ("File Name")
                                  $row.'Attribute Value' = ("**** " + $content + " ****")
                                  $resultTable.Rows.Add($row)
      
                                  $json = Get-Content -Raw -Path "$thisFolder\$content" | ConvertFrom-Json
      							Get-LeafProperty $json
      						}
      					}
      					".msapp"
      					{
      						$msappFileName = [System.IO.Path]::GetFileNameWithoutExtension("$thisFolder\$content")
      						New-Item -Path ".\Temp\$newSolutionFolder" -Name $msappFileName -ItemType "directory" | Out-Null
      						Copy-Item "$thisFolder\$content" -Destination ".\Temp\$newSolutionFolder\$content"
      						Rename-Item ".\Temp\$newSolutionFolder\$content" "$msappFileName.zip"
      						Expand-Archive ".\Temp\$newSolutionFolder\$msappFileName.zip" -DestinationPath ".\Temp\$newSolutionFolder\$msappFileName"
      						Remove-Item -Path ".\Temp\$newSolutionFolder\$msappFileName.zip" -Force
      						Remove-Item -Path "$thisFolder\$content" -Force
      						ParseFilesNFolders -thisFolder ".\Temp\$newSolutionFolder\$msappFileName"
      						Compress-Archive -Path ".\Temp\$newSolutionFolder\$msappFileName\*" -DestinationPath "$thisFolder\$msappFileName.zip"
      						Rename-Item "$thisFolder\$msappFileName.zip" "$content"
      					}
      					Default
      					{
      					}
      				}
      			}
      		}
      	}
      
      	function Get-LeafProperty
      	{
      		param([Parameter(ValueFromPipeline)] [object] $InputObject, [string] $NamePath)
      		process
      		{
      			if ($null -eq $InputObject -or $InputObject -is [DbNull] -or $InputObject.GetType().IsPrimitive -or $InputObject.GetType() -in [string], [datetime], [datetimeoffset], [decimal], [bigint])
      			{
      
      				if($NamePath -like 'TopParent.Name')
      				{
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($NamePath)
                          $row.'Attribute Value' = ($InputObject)
                          $resultTable.Rows.Add($row)
      
      				}
                      if($NamePath -like '*.Name' -and $NamePath -notlike '*.Template.Name')
      				{
                          $Global:NameAttributeKey = ($NamePath)
                          $Global:NameAttributeValue = ($InputObject)
      
      				}
                      if($NamePath -like '*.Property')
      				{
                          $Global:PropertyAttributeKey = ($NamePath)
                          $Global:PropertyAttributeValue = ($InputObject)
      
      				}
      
      				if($InputObject -like '*' + $searchString + '*')
      				{
      
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($Global:NameAttributeKey)
                          $row.'Attribute Value' = ($Global:NameAttributeValue)
                          $resultTable.Rows.Add($row)
      
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($Global:PropertyAttributeKey)
                          $row.'Attribute Value' = ($Global:PropertyAttributeValue)
                          $resultTable.Rows.Add($row)
      
      
                          $row = $resultTable.NewRow()
                          $row.'Attribute Name' = ($NamePath)
                          $row.'Attribute Value' = ($InputObject)
                          $resultTable.Rows.Add($row)
      				}
      			}
      			elseif ($InputObject -is [System.Collections.IEnumerable] -and $InputObject -isnot [System.Collections.IDictionary])
      			{
      
      				$i = 0
      				foreach ($o in $InputObject)
      				{
      					Get-LeafProperty $o ($NamePath + '[' + $i++ + ']')
      				}
      			}
      			else
      			{
      				# A non-quasi-primitive scalar object or a dictionary:
      				# enumerate its properties / entries.
      
      				$props = if ($InputObject -is [System.Collections.IDictionary]) { $InputObject.GetEnumerator() } else { $InputObject.psobject.properties }
      				$sep = '.' * ($NamePath -ne '')
      				foreach ($p in $props)
      				{
      					Get-LeafProperty $p.Value ($NamePath + $sep + $p.Name)
      				}
      			}
      		}
      	}
      
      #####################################################################
      
      
      ###################### SCRIPT EXECUTION  POINT ######################
      
          $Global:PropertyAttributeKey =''
          $Global:PropertyAttributeValue =''
          $Global:NameAttributeKey =''
          $Global:NameAttributeValue =''
          
      	try
      	{
              cls
      		Write-Host "PROCESS STARTED."
      		#Check if Output and Temp folder exist
      		If(Test-Path ".\Output")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Output" -ItemType "directory" | Out-Null
      		}
      
      		If(Test-Path ".\Temp")
      		{
      		}
      		else
      		{
      			New-Item -Path ".\" -Name "Temp" -ItemType "directory" | Out-Null
      		}
      
      # Get file name and create folder in Output Temp file
      		New-Item -Path ".\Output" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      		New-Item -Path ".\Temp" -Name $newSolutionFolder -ItemType "directory" | Out-Null
      
      # Copy file solution
      		Copy-Item $solutionPath ".\Output\$newSolutionFolder"
      
      # Parse file solution
      		ParseFilesNFolders -thisFolder ".\Output\$newSolutionFolder"
      	}
      	catch
      	{
      		# Write-Host "ERROR:" -ForegroundColor Red
      		# Write-Host $_.Exception.Message -ForegroundColor Red
      	}
          $resultTable | Format-Table
          Write-Host "PROCESS COMPLETED."
      
  25. Executing this script will give below output:-
  26. Here, you can see that only those controls are fetched which is having the code, we are looking for. Additionally, it is also showing the property upon which the code was written.
  27. Clearly, Button1 & Button2 on Screen1 having the "POC-" code upon their "OnSelect" property.
  28. Similarly, if we execute the script on a complex application having multiple screens, then the outcome would be-
  29. The screens "Screen_GroupUsers", "Screen_IndividualUser", "Screen_GetUserAccounts" are not having the code we were looking for. Screen "Screen_DynamicColumnDisplay" is having the code upon "OnSelect" property of button "btnLoadDynamicColumnsTable" and upon "Items" property of datatable "DataTable5". Similarly, search result of other screens as well.
With this, I am concluding this post.
Happy Coding !!!
Will see you again with some new topics.
Stay Safe !
Stay Healthy !

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.