Setup Azure Function from Powershell - function

In Azure CLI, there is az functionapp, but no such equivalent can be found in Powershell AzureRM-library nor Az-library.
Using raw Azure resources, I've attempted something like this to create a function app on my Application Service Plan:
New-AzResource -ResourceType 'Microsoft.Web/Sites' `
-ResourceGroupName "MyRgName" `
-Location "westeurope" `
-ResourceName "MyFunctionName" `
-kind 'functionapp' `
-Properties #{ServerFarmId="abc-123"; alwaysOn=$True;} `
-ApiVersion '2018-11-01' `
-Force;
It almost works, but doesn't create a 100% working Function App. Azure Portal will spit lots of errors and warnings, for example from missing Host Keys.
Alternatives:
ARM-templates. What to put into a template to successfully create Azure Function? I have no idea. The one generated by Azure Portal is useless.
Azure Portal: Not really handy approach for environment setup from Azure DevOps release pipeline, but it will create a fully working Function App.
The question is: How to create a Function App from a Powershell script?

I am doing the exact same thing to create a dev sandbox environment.
Provisioning function apps is a gap in the Az Powershell module but it does appear to be possible.
I provisioned my function app by following the steps here https://clouddeveloper.space/2017/10/26/deploy-azure-function-using-powershell/ but changed it to use an existing app service plan instead of consumption plan.
$AppServicePlan = "abc-123"
$AppInsightsKey = "your key here"
$ResourceGroup = "MyRgName"
$Location = "westeurope"
$FunctionAppName = "MyFunctionName"
$AzFunctionAppStorageAccountName = "MyFunctionAppStorageAccountName"
$FunctionAppSettings = #{
ServerFarmId="/subscriptions/<GUID>/resourceGroups/$ResourceGroup/providers/Microsoft.Web/serverfarms/$AppServicePlan";
alwaysOn=$True;
}
# Provision the function app service
New-AzResource -ResourceGroupName $ResourceGroup -Location $Location -ResourceName $FunctionAppName -ResourceType "microsoft.web/sites" -Kind "functionapp" -Properties $FunctionAppSettings -Force | Out-Null
$AzFunctionAppStorageAccountKey = Get-AzStorageAccountKey -ResourceGroupName $ResourceGroup -AccountName $AzFunctionAppStorageAccountName | Where-Object { $_.KeyName -eq "Key1" } | Select-Object Value
$AzFunctionAppStorageAccountConnectionString = "DefaultEndpointsProtocol=https;AccountName=$AzFunctionAppStorageAccountName;AccountKey=$($AzFunctionAppStorageAccountKey.Value)"
$AzFunctionAppSettings = #{
APPINSIGHTS_INSTRUMENTATIONKEY = $AppInsightsKey;
AzureWebJobsDashboard = $AzFunctionAppStorageAccountConnectionString;
AzureWebJobsStorage = $AzFunctionAppStorageAccountConnectionString;
FUNCTIONS_EXTENSION_VERSION = "~2";
FUNCTIONS_WORKER_RUNTIME = "dotnet";
}
# Set the correct application settings on the function app
Set-AzWebApp -Name $FunctionAppName -ResourceGroupName $ResourceGroup -AppSettings $AzFunctionAppSettings | Out-Null

This might help:
To create an Azure Function we have dependency over "Storage Account", "Service Plan", "a resource group" and "Application Insight"(optional). Below i am initially defining variables. Post that i am checking if Resource group exist. If not it will create a new one. Post which i created Azure Storage Account, Service Plan and Application Insight. In Azure function we need to select Runtime Stack which can be "Java"/"DotNet"/"Python" etc. Here I am using "Dotnet". Azure Function requires Storage Account keys to link the same, which is extracted below under variables "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING" etc. To link AppInsight with Function we need to map Application_Insight_InstrumentationKey. Please follow inline comments :
#=============Defining All Variables=========
$location = 'Southeast Asia'
$resourceGroupName = 'functionrgnew1'
$storageAccount = 'functionsasdnewqq1'
$subscriptionId = '<id>'
$functionAppName = 'functionapppsdfsdnew1'
$appInsightsName = 'appinsightnameprdad'
$appServicePlanName = 'functionappplan'
$tier = 'Premium'
#========Creating Azure Resource Group========
$resourceGroup = Get-AzResourceGroup | Where-Object { $_.ResourceGroupName -eq $resourceGroupName }
if ($resourceGroup -eq $null)
{
New-AzResourceGroup -Name $resourceGroupName -Location $location -force
}
#selecting default azure subscription by name
Select-AzSubscription -SubscriptionID $subscriptionId
Set-AzContext $subscriptionId
#========Creating Azure Storage Account========
if(!(Test-AzureName -Storage $storageAccount))
{
New-AzStorageAccount -ResourceGroupName $resourceGroupName -AccountName $storageAccount -Location $location -SkuName "Standard_LRS"
}
#========Creating App Service Plan============
New-AzAppServicePlan -ResourceGroupName $resourceGroupName -Name $appServicePlanName -Location $location -Tier $tier
$functionAppSettings = #{
ServerFarmId="/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Web/serverfarms/$appServicePlanName";
alwaysOn=$True;
}
#========Creating Azure Function========
$functionAppResource = Get-AzResource | Where-Object { $_.ResourceName -eq $functionAppName -And $_.ResourceType -eq "Microsoft.Web/Sites" }
if ($functionAppResource -eq $null)
{
New-AzResource -ResourceType 'Microsoft.Web/Sites' -ResourceName $functionAppName -kind 'functionapp' -Location $location -ResourceGroupName $resourceGroupName -Properties $functionAppSettings -force
}
#========Creating AppInsight Resource========
New-AzApplicationInsights -ResourceGroupName $resourceGroupName -Name $appInsightsName -Location $location
$resource = Get-AzResource -Name $appInsightsName -ResourceType "Microsoft.Insights/components"
$details = Get-AzResource -ResourceId $resource.ResourceId
$appInsightsKey = $details.Properties.InstrumentationKey
#========Retrieving Keys========
$keys = Get-AzStorageAccountKey -ResourceGroupName $resourceGroupName -AccountName $storageAccount
$accountKey = $keys | Where-Object { $_.KeyName -eq 'Key1' } | Select-Object -ExpandProperty Value
$storageAccountConnectionString = 'DefaultEndpointsProtocol=https;AccountName='+$storageAccount+';AccountKey='+$accountKey
#========Defining Azure Function Settings========
$AppSettings =#{}
$AppSettings =#{'APPINSIGHTS_INSTRUMENTATIONKEY' = $appInsightsKey;
'AzureWebJobsDashboard' = $storageAccountConnectionString;
'AzureWebJobsStorage' = $storageAccountConnectionString;
'FUNCTIONS_EXTENSION_VERSION' = '~2';
'FUNCTIONS_WORKER_RUNTIME' = 'dotnet';
'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' = $storageAccountConnectionString;
'WEBSITE_CONTENTSHARE' = $storageAccount;}
Set-AzWebApp -Name $functionAppName -ResourceGroupName $resourceGroupName -AppSettings $AppSettings

The best way to do this from PowerShell is to use an ARM template, rather than try to create each resource individually. You can find an example template here. It also hooks up the app to github, but you can leave out that part if you just want an empty app

Related

Can't call piped properties in a function. Powershell

So I'm trying to create a "download" function that uses a piped object property to determine a download method (sftp or http). Then either create an sftp script for putty/winscp or curl the http url. I am defining objects as follows:
#WinSCP
$winscp = new-object psobject
$winscp | add-member noteproperty name "WinSCP"
$winscp | add-member noteproperty dltype "http"
$winscp | add-member noteproperty file "winscp.exe"
$winscp | add-member noteproperty url "https://cdn.winscp.net/files/WinSCP-5.17.8-Setup.exe"
$winscp | add-member noteproperty path "$env:ProgramFiles(x86)\WinSCP"
$winscp | add-member noteproperty install 'msiexec /i "$DataPath\$winscp.file" /quiet /norestart'
#Database
$db = new-object psobject
$db | add-member noteproperty name "Client Database"
$db | add-member noteproperty dltype "sftp"
$db | add-member noteproperty file "database_"
$db | add-member noteproperty ver "check"
$db | add-member noteproperty ext ".csv"
$db | add-member noteproperty dir "db"
#DatabaseVersion
$db_ver = new-object psobject
$db_ver | add-member noteproperty name "Database Version File"
$db_ver | add-member noteproperty dltype "sftp"
$db_ver | add-member noteproperty file "current_version.txt"
$db_ver | add-member noteproperty dir "db"
Currently I'm having issues with the $Input variable within the function. It can only be used once and does not translate into an if statement. Since it contains an object with multiple properties, it needs converted to a new object within the function first I think. I'm new to powershell and haven't found a way of doing this yet. Here is the function I made and am trying to use:
function Download () {
#HTTP Download Method
if ($input.dltype -eq "http") {
curl $input.url -O $DataPath\$input.file
#HTTP Success or Error
$curlResult = $LastExitCode
if ($curlResult -eq 0)
{
Write-Host "Successfully downloaded $input.name"
}
else
{
Write-Host "Error downloading $input.name"
}
pause
}
#SFTP Download Method
if ($input.dltype -eq "sftp") {
sftpPassCheck
#Detect if version required
if ($input.ver = "check") {
#Download the objects version file
"$+$Input+_ver" | Download
#Update the object's ver property
$input.ver = [IO.File]::ReadAllText("$DataPath\current_version.txt")
#Build the new filename
$input.file = "$input.file"+"$input.ver"+"$input.ext"
#Delete the version file
Remove-Item "$DataPath\current_version.txt"
}
& "C:\Program Files (x86)\WinSCP\WinSCP.com" `
/log="$DataPath\SFTP.log" /ini=nul `
/command `
"open sftp://ftpconnector:$script:sftp_pass#$input.ip/ -hostkey=`"`"ssh-ed25519 255 SETvoRlAT0/eJJpRhRRpBO5vLfrhm5L1mRrMkOiPS70=`"`" -rawsettings ProxyPort=0" `
"cd /$input.dir" `
"lcd $DataPath" `
"get $input.file" `
"exit"
#SFTP Success or Error
$winscpResult = $LastExitCode
if ($winscpResult -eq 0)
{
Write-Host "Successfully downloaded $input.name"
}
else
{
Write-Host "Error downloading $input.name"
}
}
}
I'm probably missing something simple but I'm clueless at this point. Oh usage should be:
WinSCP | download
The proper way to bind input from the pipeline to a function's parameters is to declare an advanced function - see about_Functions_Advanced_Parameters and the implementation in the bottom section of this answer.
However, in simple cases a filter will do, which is a simplified form of a function that implicitly binds pipeline input to the automatic $_ variable and is called for each input object:
filter Download {
if ($_.dltype -eq "http") {
# ...
}
}
$input is another automatic variable, which in simple (non-advanced) functions is an enumerator for all pipeline input being received and must therefore be looped over.
That is, the following simple function is the equivalent of the above filter:
function Download {
# Explicit looping over $input is required.
foreach ($obj in $input) {
if ($obj.dltype -eq "http") {
# ...
}
}
}
If you do want to turn this into an advanced function (note that I've changed the name to conform to PowerShell's verb-noun naming convention):
function Invoke-Download {
param(
# Declare a parameter explicitly and mark it as
# as pipeline-binding.
[Parameter(ValueFromPipeline, Mandatory)]
$InputObject # Not type-constraining the parameter implies [object]
)
# The `process` block is called for each pipeline input object
# with $InputObject referencing the object at hand.
process {
if ($InputObject.dltype -eq "http") {
# ...
}
}
}
mklement0 is spot on - $input is not really meant to used directly, and you're probably much better off explicitly declaring your input parameters!
In addition to the $InputObject pattern shown in that answer, you can also bind input object property values to parameters by name:
function Download
{
param(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[Alias('dltype')]
[string]$Protocol = 'http'
)
process {
Write-Host "Choice of protocol: $Protocol"
}
}
Notice that although the name of this parameter is $Protocol, the [Alias('dltype')] attribute will ensure that the value of the dltype property on the input object is bound.
The effect of this is:
PS ~> $WinSCP,$db |Download
Choice of protocol: http
Choice of protocol: sftp
Keep repeating this pattern for any required input parameter - declare a named parameter mapped to property names (if necessary), and you might end up with something like:
function Download
{
[CmdletBinding()]
param(
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateSet('sftp', 'http')]
[Alias('dltype')]
[string]$Protocol,
[Parameter(ValueFromPipelineByPropertyName = $true)]
[Alias('dir')]
[string]$Path = $PWD,
[Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('url','file')]
[string]$Uri
)
process {
Write-Host "Downloading $Uri to $Path over $Protocol"
}
}
Now you can do:
PS ~> $WinSCP,$db |Download
Downloading https://cdn.winscp.net/files/WinSCP-5.17.8-Setup.exe to C:\Program Files(x86)\WinSCP over http
Downloading database_ to db over sftp
We're no longer dependent on direct access to $input, $InputObject or $_, nice and clean.
Please see the about_Functions_Advanced_Parameters help file for more information about parameter declaration.

powershell variable into where-object does not return data

I am writing a script to ultimately check a block of servers for a certificate by FriendlyName and then go back and delete them once confirmed. Right now I am just trying to get the initial check to work. Currently it is not returning any data. Can anyone help?
$ContentsPath = "C:\Servers.txt"
$Servers = Get-Content $ContentsPath
$CertDeletionFile = "C:\CertsDeleted.csv"
$Today = Get-Date
$Certificate = Read-Host -Prompt "What certificate would you like to
REMOVE?"
write-host $Certificate
function findCert {
param ([string]$Certificate)
Invoke-Command -ComputerName $Servers -ScriptBlock {Get-Childitem -Path
Cert:LocalMachine\My | where {$_.friendlyname -eq $Certificate } | Select-
Object -Property FriendlyName }
}
findCert
As Mathias R. Jessen comments, your findcert function needs a certificate name as a parameter, and you aren't passing anything when you call it, so it won't run properly.
You're also trying to use a local computer variable $Certificate, on a remote computer inside an invoke-command, and the remote computer can't get to that variable across the remoting.
I've rewritten it, with $using: which is a syntax that tells PS to send the value over the remoting session, and with renamed variables so it's more clear which part is accessing which variables:
$ContentsPath = 'C:\Servers.txt'
$Servers = Get-Content -LiteralPath $ContentsPath
$CertDeletionFile = 'C:\CertsDeleted.csv'
$Today = Get-Date
$typedCertificateName = Read-Host -Prompt "What certificate would you like to
REMOVE?"
write-host $typedCertificateName
function findCert {
param ([string]$Certificate)
Invoke-Command -ComputerName $Servers -ScriptBlock {
Get-Childitem -Path Cert:LocalMachine\My |
where-Object {$_.friendlyname -eq $using:Certificate } |
Select-Object -Property FriendlyName
}
}
findCert -Certificate $typedCertificateName

Add firewall rule to Azure Database for MySQL server from powershell

I have deployed Azure Database for MySQL server and I want to add a firewall rule to it. I need to do it from PowerShell because this step is part of the greater solution. I tried the following code:
$resource = Get-AzureRmResource -ResourceGroupName $ResourceGroup.Variables.ResourceGroup `
-ResourceType "Microsoft.DBforMySQL/servers" -ResourceName $MySQLServer.ResourceName
$props = $resource.Properties
$props | Add-Member #{ipV4FirewallSettings = [ordered] #{ "firewallRules" = #() } }
$props.ipV4FirewallSettings.firewallRules = $MySQLServer.FirewallRules
$props | Add-Member #{administratorLoginPassword = "Qwerty123!" }
Set-AzureRmResource -PropertyObject $props -ResourceGroupName $ResourceGroup.Variables.ResourceGroup `
-ResourceType "Microsoft.DBforMySQL/servers" -ResourceName $MySQLServer.ResourceName -Force
Where $MySQLServer.FirewallRules are from json file in the following format:
"FirewallRules" : [
{ "firewallRuleName" : "test", "rangeStart": "0.0.0.0", "rangeEnd": "0.0.0.0" },
{ "firewallRuleName" : "test2", "rangeStart": "0.0.0.1", "rangeEnd": "255.255.255.255" }
],
This code does not throw any error, but it's not adding rules to the resource.
I need a pointer to where I made a mistake or some documentation, how to handle such task properly.
You can use New-AzureRmResource command to add Mysql firewall rules:
PS C:\Users\jason> $b = New-Object Psobject -Property #{startIpAddress="172.0.0.1" ; endIpAddress="172.0.0.8"}
PS C:\Users\jason> $b
startIpAddress endIpAddress
-------------- ------------
172.0.0.1 172.0.0.8
PS C:\Users\jason> New-AzureRmResource -ResourceId "/subscriptions/b83c1ed3-xxxx-xxxx-xxxx-2b83a074c23f/resourceGroups/jasonmysql/providers/Microsoft.DBforMySQL/servers/jasonmysql/firewallRules/rule2" -Properties $b -ApiVer
sion 2017-04-30-preview -Force
Name : rule2
ResourceId : /subscriptions/b83c1ed3-xxxx-xxxx-xxxx-2b83a074c23f/resourceGroups/jasonmysql/providers/Microsoft.DBforMySQL/servers/jasonmysql/firewallRules/rule2
ResourceName : jasonmysql/rule2
ResourceType : Microsoft.DBforMySQL/servers/firewallRules
ResourceGroupName : jasonmysql
SubscriptionId : b83c1ed3-xxxx-xxxx-xxxx-2b83a074c23f
Properties : #{startIpAddress=172.0.0.1; endIpAddress=172.0.0.8}

Do Azure workflows need additional authentication considerations?

I'm unable to deploy availability sets in parallel from a simple table from Powershell ISE onto my MSDN subscription.
Table
Type RG Name Loc
AvSet NLG NLGUTCDCPWFEAVL01 eastus2
AvSet NLG NLGUTCDCPAPPAVL01 eastus2
AvSet NLG NLGUTCDCPCCDBAVL01 eastus2
This works when executed without a workflow.
$c=Import-Csv C:\Users\ayanm\Downloads\NLG.csv|? type -eq 'AVSet'
foreach ($b in $c)
{New-AzureRmAvailabilitySet -ResourceGroupName $b.RG -Name $b.name -Location $b.loc}
But when I try to put it in a workflow, it doesn't
Workflow Deploy-AVSet
{$c=Import-Csv C:\Users\ayanm\Downloads\NLG.csv|? type -eq 'AVSet'
foreach -Parallel ($b in $c)
{New-AzureRmAvailabilitySet -ResourceGroupName $b.RG -Name $b.name -Location $b.loc}
}
Error:
Microsoft.PowerShell.Utility\Write-Error : Run Login-AzureRmAccount to login.
At Deploy-AVSet:4 char:4
+ CategoryInfo : NotSpecified: (:) [Write-Error], RemoteException
+ FullyQualifiedErrorId : System.Management.Automation.RemoteException,Microsoft.PowerShell.Commands.WriteErrorCommand
Checked Powershell version; 5.1. Updated all modules. Rebooted computer. Is this an unsupported workflow activity?
https://blogs.technet.microsoft.com/heyscriptingguy/2013/01/02/powershell-workflows-restrictions/
The `Login-AzureRmAccount' cmdlet doesn't MSDN crediantial object. So I added an O365 account as an owner of the subscription and am able to deploy in parallel.
Workflow Deploy-AVSet
{$c=Import-Csv C:\Users\ayanm\Downloads\NLG.csv|? type -eq 'AVSet'
$cred= New-Object System.Management.Automation.PSCredential "name#domain.onmicrosoft.com",$(ConvertTo-SecureString "Password" -asplaintext -force)
foreach ($b in $c)
{AzureRM.Resources\Login-AzureRmAccount -Credential $cred
New-AzureRmAvailabilitySet -ResourceGroupName $b.RG -Name $b.name -Location $b.loc -PlatformFaultDomainCount $b.faultdomain -PlatformUpdateDomainCount $b.UpdateDomain
}
}

Enable Silverlight Plugin (NPAPI) in Chrome using registry key fix

I have created a Powershell script to add the npapi and Silverlight* registry keys to enable Silverlight in Google Chrome. The Powershell script works fine and adds the two registry keys, however the Silverlight plugin is still disabled in Chrome and when I load any Silverlight based sites I get the “Install Silverlight” popup. I have restarted the machine and still the Silverlight plugin is disabled.
However, if I go into the registry and delete just the npapi and Silverlight* registry keys and re-create them (String value - REG_SZ), when I reload the page in Chrome, Silverlight is now enabled and the site loads perfectly. I don’t understand what’s going on.
The powershell script creates these keys but only when I delete them and re-create them manually do they take effect and the Silverlight plugin is enabled. Then if I go into chrome://plugins, Chrome reports that the Silverlight plugin is “Enabled by Enterprise policy”. I have also run the script on another machine and the exact same thing happens. Has anyone else experienced this and does anyone know the fix or what I am doing wrong?
Powershell Script used to create the npapi and Silverlight* registry keys:
function Create-Path {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
,
[Parameter(ValueFromPipeline = $true, Mandatory = $false)]
[switch]$OverwriteIfExists
)
process {
If(($OverwriteIfExists.IsPresent) -or (-not (Test-Path $Path))) {
New-Item $Path -Force | out-null
}
}
}
function Get-RegistryKeyWithValue {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
,
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$Value
)
process {
$properties = Get-Item $Path | select -ExpandProperty Property
$properties | %{
$property = Get-ItemProperty -Path $Path -Name $_
if ($property.$_ -eq $Value) {
write-output $property
}
}
}
}
function Get-NextKeyInPath {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
)
process {
try {
write-output ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1) | out-string
} catch {
write-output "1"
}
}
}
function Create-ChromeEnabledPluginPolicy {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$Value
)
begin {
$ChromePluginPolicyPath = "HKLM:\SOFTWARE\Policies\Google\Chrome\EnabledPlugins"
Create-Path $ChromePluginPolicyPath
}
process {
if (-not (Get-RegistryKeyWithValue -Path $ChromePluginPolicyPath -Value $Value)) {
$keyName = Get-NextKeyInPath -Path $ChromePluginPolicyPath
New-ItemProperty -path $ChromePluginPolicyPath -Name $keyName -Value $Value -PropertyType String
}
}
}
"npapi", "Silverlight*" | Create-ChromeEnabledPluginPolicy
The code:
process {
try {
write-output ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1) | out-string
} catch {
write-output "1"
}
}
Seems to return something more than a single string.
Amending to the following resolves the issue:
process {
try {
[int]$i = ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1)
write-output ([string]$i)
} catch {
write-output "1"
}
}
A simplified demo of the issue & solution:
Run the following code: New-ItemProperty -Path 'HKLM:\SOFTWARE\JohnLBevan' -Name (1 | out-string) -PropertyType String -Value 'test'
Now open regedit and create a key with name 1; it succeeds (i.e. you have two keys called 1; so clearly some control/non displayable character is being added in our PS script).
If you try to add a third key called 1 using either method (regedit or powershell) you'll get an error due to a key with that name already existing (showing that there is a unique check in place; it's just our original 1s aren't unique)
If you try either of the following code snippets, things work as expected:
New-ItemProperty -Path'HKLM:\SOFTWARE\JohnLBevan' -Name "1" -PropertyType String -Value 'test'
New-ItemProperty -Path'HKLM:\SOFTWARE\JohnLBevan' -Name [string]1 -PropertyType String -Value 'test'
(Disclosure: I work with #jwoods83, so had the advantage of seeing the issue / playing with it directly)