Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ TestsResults*.xml
PowerShell.sln.DotSettings.user
*.msp
StyleCop.Cache

src/Modules
!src/Modules/Microsoft.AzureFunctions.PowerShellWorker
33 changes: 9 additions & 24 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ param(

$NeededTools = @{
DotnetSdk = ".NET SDK latest"
PowerShellGet = "PowerShellGet latest"
Pester = "Pester latest"
}

function needsDotnetSdk () {
Expand All @@ -33,34 +31,12 @@ function needsDotnetSdk () {
return $false
}

function needsPowerShellGet () {
$modules = Get-Module -ListAvailable -Name PowerShellGet | Where-Object Version -ge 1.6.0
if ($modules.Count -gt 0) {
return $false
}
return $true
}

function needsPester () {
$modules = Get-Module -ListAvailable -Name Pester
if ($modules.Count -gt 0) {
return $false
}
return $true
}

function getMissingTools () {
$missingTools = @()

if (needsDotnetSdk) {
$missingTools += $NeededTools.DotnetSdk
}
if (needsPowerShellGet) {
$missingTools += $NeededTools.PowerShellGet
}
if (needsPester) {
$missingTools += $NeededTools.Pester
}

return $missingTools
}
Expand All @@ -87,8 +63,17 @@ if($Clean) {
}

# Build step

# Install using PSDepend if it's available, otherwise use the backup script
if ((Get-Module -ListAvailable -Name PSDepend).Count -gt 0) {
Invoke-PSDepend -Path src -Force
} else {
& "$PSScriptRoot/tools/InstallDependencies.ps1"
}

dotnet build -c $Configuration
dotnet publish -c $Configuration

Push-Location package
dotnet pack -c $Configuration
Pop-Location
Expand Down
20 changes: 20 additions & 0 deletions examples/PSCoreApp/GetAzureVmHttpTrigger/function.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"disabled": false,
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
29 changes: 29 additions & 0 deletions examples/PSCoreApp/GetAzureVmHttpTrigger/run.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Trigger the function by running Invoke-RestMethod:
# Get everything: Invoke-RestMethod -Uri http://localhost:7071/api/GetAzureVmHttpTrigger
# Specify parameters: Invoke-RestMethod `
# -Uri http://localhost:7071/api/MyHttpTrigger?Name=testVm&ResourceGroupName=TESTRESOURCEGROUP

# Input bindings are passed in via param block.
param($req, $TriggerMetadata)

$cmdletParameters = $req.Query

# If the cmdlet fails, we want it to throw an exception
$cmdletParameters.ErrorAction = "Stop"

try {
# Splat the parameters that were passed in via query parameters
$vms = Get-AzureRmVM @cmdletParameters
$response = [HttpResponseContext]@{
StatusCode = '200' # OK
Body = ($vms | ConvertTo-Json)
}
} catch {
$response = [HttpResponseContext]@{
StatusCode = '400' # Bad Request
Body = @{ Exception = $_.Exception }
}
}

# You associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name res -Value $response
3 changes: 2 additions & 1 deletion src/Messaging/RpcLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public void Dispose()

public async void Log(LogLevel logLevel, string message, Exception exception = null, bool isUserLog = false)
{
var invocationId = _invocationId ?? "N/A";
if (isUserLog)
{
// For user logs, we send them over Rpc with details about the invocation.
Expand All @@ -47,7 +48,7 @@ public async void Log(LogLevel logLevel, string message, Exception exception = n
RpcLog = new RpcLog()
{
Exception = exception?.ToRpcException(),
InvocationId = _invocationId,
InvocationId = invocationId,
Level = logLevel,
Message = message
}
Expand Down
39 changes: 39 additions & 0 deletions src/PowerShell/PowerShellManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Security;

using Microsoft.Azure.Functions.PowerShellWorker.Utility;
using Microsoft.Azure.WebJobs.Script.Grpc.Messages;
Expand Down Expand Up @@ -49,6 +50,39 @@ internal PowerShellManager(RpcLogger logger)
_pwsh.Streams.Warning.DataAdding += streamHandler.WarningDataAdding;
}

internal void AuthenticateToAzure()
{
// Try to authenticate to Azure
// TODO: The Azure Functions Host might supply these differently. This might change but works for the demo
string applicationId = Environment.GetEnvironmentVariable("ApplicationId");
string applicationSecret = Environment.GetEnvironmentVariable("ApplicationSecret");
string tenantId = Environment.GetEnvironmentVariable("TenantId");

if (string.IsNullOrEmpty(applicationId) ||
string.IsNullOrEmpty(applicationSecret) ||
string.IsNullOrEmpty(tenantId))
{
_logger.Log(LogLevel.Warning, "Required environment variables to authenticate to Azure were not present");
return;
}

// Build SecureString
var secureString = new SecureString();
foreach (char item in applicationSecret)
{
secureString.AppendChar(item);
}

using (ExecutionTimer.Start(_logger, "Authentication to Azure completed."))
{
_pwsh.AddCommand("Connect-AzureRmAccount")
.AddParameter("Credential", new PSCredential(applicationId, secureString))
.AddParameter("ServicePrincipal")
.AddParameter("TenantId", tenantId)
.InvokeAndClearCommands();
}
}

internal void InitializeRunspace()
{
// Add HttpResponseContext namespace so users can reference
Expand All @@ -57,6 +91,11 @@ internal void InitializeRunspace()

// Set the PSModulePath
Environment.SetEnvironmentVariable("PSModulePath", Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Modules"));

// TODO: remove this when we figure out why it fixed #48
_pwsh.AddCommand("Microsoft.PowerShell.Core\\Import-Module").AddParameter("Name", "AzureRm.Netcore").InvokeAndClearCommands();

AuthenticateToAzure();
}

internal Hashtable InvokeFunction(
Expand Down
48 changes: 24 additions & 24 deletions src/RequestProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,27 @@ internal async Task ProcessRequestLoop()
while (await _msgStream.MoveNext())
{
request = _msgStream.GetCurrentMessage();
switch (request.ContentCase)

using (_logger.BeginScope(request.RequestId, request.InvocationRequest?.InvocationId))
{
case StreamingMessage.ContentOneofCase.WorkerInitRequest:
response = ProcessWorkerInitRequest(request);
break;

case StreamingMessage.ContentOneofCase.FunctionLoadRequest:
response = ProcessFunctionLoadRequest(request);
break;

case StreamingMessage.ContentOneofCase.InvocationRequest:
response = ProcessInvocationRequest(request);
break;

default:
throw new InvalidOperationException($"Not supportted message type: {request.ContentCase}");
switch (request.ContentCase)
{
case StreamingMessage.ContentOneofCase.WorkerInitRequest:
response = ProcessWorkerInitRequest(request);
break;

case StreamingMessage.ContentOneofCase.FunctionLoadRequest:
response = ProcessFunctionLoadRequest(request);
break;

case StreamingMessage.ContentOneofCase.InvocationRequest:
response = ProcessInvocationRequest(request);
break;

default:
throw new InvalidOperationException($"Not supportted message type: {request.ContentCase}");
}
}

await _msgStream.WriteAsync(response);
}
}
Expand Down Expand Up @@ -156,14 +159,11 @@ internal StreamingMessage ProcessInvocationRequest(StreamingMessage request)

// Set the RequestId and InvocationId for logging purposes
Hashtable result = null;
using (_logger.BeginScope(request.RequestId, invocationRequest.InvocationId))
{
result = _powerShellManager.InvokeFunction(
functionInfo.ScriptPath,
functionInfo.EntryPoint,
triggerMetadata,
invocationRequest.InputData);
}
result = _powerShellManager.InvokeFunction(
functionInfo.ScriptPath,
functionInfo.EntryPoint,
triggerMetadata,
invocationRequest.InputData);

// Set out binding data and return response to be sent back to host
foreach (KeyValuePair<string, BindingInfo> binding in functionInfo.OutputBindings)
Expand Down
21 changes: 21 additions & 0 deletions src/requirements.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@{
# Packaged with the PowerShell Language Worker
'PowerShellGet' = @{
Version = '1.6.7'
Target = 'src/Modules'
}
'Microsoft.PowerShell.Archive' = @{
Version = '1.1.0.0'
Target = 'src/Modules'
}
'AzureRM.Netcore' = @{
Version = '0.13.1'
Target = 'src/Modules'
}

# Dev dependencies
'Pester' = @{
Version = 'latest'
Target = 'CurrentUser'
}
}
28 changes: 28 additions & 0 deletions tools/InstallDependencies.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#
# Copyright (c) Microsoft. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
#

$dependencies = Import-PowerShellDataFile "$PSScriptRoot/../src/requirements.psd1"

foreach ($key in $dependencies.Keys) {
$params = @{ Name = $key }

if ($dependencies[$key].Version -ne 'latest') {
# Save-Module doesn't have -Version so we have to specify Min and Max
$params.MinimumVersion = $dependencies[$key].Version
$params.MaximumVersion = $dependencies[$key].Version
}

if($dependencies[$key].Target -eq 'CurrentUser') {
$params.Scope = $dependencies[$key].Target
Install-Module @params
} else {
$params.Path = $dependencies[$key].Target
if (Test-Path "$($params.Path)/$key") {
Write-Host "'$key' - Module already installed"
} else {
Save-Module @params
}
}
}