I've made a powershell Cmdlet for creating an AppFabric a region, the code:
[Cmdlet(VerbsCommon.New, "CacheRegion")]
public class NewCacheRegion : Cmdlet {
[Parameter(Mandatory = true, Position = 1)]
public string Cache { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public string Region { get; set; }
protected override void ProcessRecord() {
base.ProcessRecord();
DataCacheFactory factory = new DataCacheFactory(); // exception
DataCache cache = factory.GetCache(Cache);
try {
cache.CreateRegion(Region);
}
catch (DataCacheException ex) {}
}
}
It's installed with import-module appfabriccmdlet.dll and the code executes when running new-cacheregion.
but the line
DataCacheFactory factory = new DataCacheFactory();
throws an exception that server collection is empty which means that no dataCacheClient section is found in app.config. So I want to a client that but Im not sure in which config file to add the appfabric sections. I've tried finding out from what executable the cmdlet dll is running with
Assembly a = Assembly.GetEntryAssembly();
but that returns null.
So where do I need to put config sections that a cmdlet dll has access to?
never mind.
fixed it by programmatically adding the server without config
DataCacheServerEndpoint[] servers = new DataCacheServerEndpoint[1];
servers[0] = new DataCacheServerEndpoint("localhost", 22233);
DataCacheFactoryConfiguration conf = new DataCacheFactoryConfiguration();
conf.Servers = servers;
DataCacheFactory factory = new DataCacheFactory(conf);
Related
I'm creating a webjob in .net core 3.1. In this project I have a function that is timer activated which should read the number of messages in a queue Q1 and if empty, put a message in Q2 as well as trigger a rest call to an API.
In order to check how many messages are in the API I need to access the AzureWebJobsStorage in my appsettings.json and then the url which is also in the settings.
Program.cs
class Program
{
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
b.AddTimers();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
builder.ConfigureAppConfiguration((context, b) =>
{
b.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
});
builder.ConfigureServices((context, services) =>
{
var mySettings = new MySettings
{
AzureWebJobsStorage = context.Configuration.GetValue<string>("AzureWebJobsStorage"),
AzureWebJobsDashboard = context.Configuration.GetValue<string>("AzureWebJobsDashboard"),
url = context.Configuration.GetValue<string>("url"),
};
services.AddSingleton(mySettings);
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
}
Fuctions.cs
public class Functions
{
public static void UpdateChannels([QueueTrigger("Q1")] string message, ILogger logger)
{
logger.LogInformation(message);
}
public static void WhatIsThereToUpdate([QueueTrigger("Q2")] string message, ILogger logger)
{
logger.LogInformation(message);
}
public static void CronJob([TimerTrigger("0 * * * * *")] TimerInfo timer, [Queue("Q2")] out string message, ILogger logger, MySettings mySettings)
{
message = null;
// Get the connection string from app settings
string connectionString = mySettings.AzureWebJobsStorage;
logger.LogInformation("Connection String: " + connectionString);
// Instantiate a QueueClient which will be used to create and manipulate the queue
QueueClient queueClient = new QueueClient(connectionString, "Q1");
if (queueClient.Exists())
{
QueueProperties properties = queueClient.GetProperties();
// Retrieve the cached approximate message count.
int cachedMessagesCount = properties.ApproximateMessagesCount;
// Display number of messages.
logger.LogInformation($"Number of messages in queue: {cachedMessagesCount}");
if (cachedMessagesCount == 0)
message = "Hello world!" + System.DateTime.Now.ToString(); //here I would call the REST API as well
}
logger.LogInformation("Cron job fired!");
}
}
appsettings.json
{
"AzureWebJobsStorage": "constr",
"AzureWebJobsDashboard": "constr",
"url": "url"
}
My Settings
public class MySettings
{
public string AzureWebJobsStorage { get; set; }
public string AzureWebJobsDashboard { get; set; }
public string url { get; set; }
}
However when I run this I get the following error:
Error indexing method 'Functions.CronJob'
Microsoft.Azure.WebJobs.Host.Indexers.FunctionIndexingException: Error indexing method 'Functions.CronJob'
---> System.InvalidOperationException: Cannot bind parameter 'mySettings' to type MySettings. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
In addition to what is shown in the above codes I also tried using ConfigurationManager and Environment.GetEnvironmentVariable, both methods gave me null when I tried to read the values. For example ConfigurationManager.AppSettings.GetValues("AzureWebJobsStorage").
I also tried to register IConfiguration as a service services.AddSingleton(context.Configuration); and inject it in the parameters (instead of MySettings), but it also gave me the same binding error.
I'm really at a loss here, I've scoured the SO archives trying to find a solution and I think I tried everything I saw gave people positive results, but unfortunately I wasn't as lucky as the other posters.
Any guidance is much appreciated.
Edited to add my packages
In case it helps anyone, I'm using the following
Azure.Storage.Queues (12.4.0)
Microsoft.Azure.WebJobs.Extensions (3.0.6)
Microsoft.Azure.WebJobs.Extensions.Storage (4.0.2)
Microsoft.Extensions.Logging.Console (3.1.7)
When using DI, I suggest you use non-static method and constructor inject.
Here is the Functions.cs:
public class Functions
{
private readonly MySettings mySettings;
public Functions(MySettings _mySettings)
{
mySettings = _mySettings;
}
public void ProcessQueueMessage([TimerTrigger("0 */1 * * * *")] TimerInfo timer, [Queue("queue")] out string message, ILogger logger)
{
message = null;
string connectionString = mySettings.AzureWebJobsStorage;
logger.LogInformation("Connection String: " + connectionString);
}
}
No code change in other .cs file.
Here is the test result:
Problem
I am trying to pass arguments from the command line to my service class that inherits ServiceBase before the service installation. I found a way to accept parameters when using InstallUtil to run my service installer. However, the the main function running ServiceBase.Run(new Service()); triggers before those parameters can be accessed. Therefore, I don't know how to pass command line parameters into my Service() class before it is run.
I am using ConfigStream as a static class to read and store parameters from a text config file. My goal is to import the settings from the config file and apply them to my Service class before the installer is run. I'd like to be able to point to the location of that config file from an input in the command line.
Attempted Solutions
I've tried applying the parameters to the ConfigStream class within the OnBeforeInstall function of the Installer, but that still runs after the main function, so it doesn't apply the settings to my Service class.
I also tried following a tutorial from microsoft, but also had no luck. The parameters were never passed to Main. Tutorial: Create Windows Service - Add Optional Params
Main Class
public static class MainClass
{
///Function: Main
///File: ServiceWrapperV2.cs
///Author: Luke Maple
///Purpose: Main function of program, start up the service portion of program.
static void Main(String[] args)
{
// Test if input arguments were supplied
Console.WriteLine("Args: ");
Console.WriteLine(args);
if (args.Length > 0)
{
// set config location if provided
// otherwise assume in working directory
ConfigStream.setConfigstream(args[0]);
}
// run service
ServiceBase.Run(new Service());
}
}
Installer Attempt 1
[RunInstaller(true)]
public class ServiceWrapperInstaller : Installer
{
private ServiceProcessInstaller processInstaller;
private ServiceInstaller serviceInstaller;
public ServiceWrapperInstaller()
{
//inilizing installer objects
processInstaller = new ServiceProcessInstaller();
serviceInstaller = new ServiceInstaller();
//Service will use the windows local system acount. Service will be run as admin
processInstaller.Account = ServiceAccount.LocalSystem;
//sets service start mode to automatic. service will auto boot on restarts
serviceInstaller.StartType = ServiceStartMode.Automatic;
// set service parameters
// Console.WriteLine(ConfigStream.getSetting("Service Name"));
serviceInstaller.ServiceName = ConfigStream.getSetting("Service Name");
// Console.WriteLine("\nService Name: " + serviceInstaller.ServiceName + "\n");
serviceInstaller.Description = ConfigStream.getSetting("Service Description");
//passing object to be installed
Installers.Add(serviceInstaller);
Installers.Add(processInstaller);
}
private void _setConfigLocation()
{
if (Context.Parameters.ContainsKey("config"))
{
ConfigStream.setConfigstream(Context.Parameters["config"]);
}
}
protected override void OnBeforeInstall(IDictionary savedState)
{
_setConfigLocation();
base.OnBeforeInstall(savedState);
}
}
Installer Attempt 2
[RunInstaller(true)]
public class ServiceWrapperInstaller : Installer
{
private ServiceProcessInstaller processInstaller;
private ServiceInstaller serviceInstaller;
public ServiceWrapperInstaller()
{
//inilizing installer objects
processInstaller = new ServiceProcessInstaller();
serviceInstaller = new ServiceInstaller();
//Service will use the windows local system acount. Service will be run as admin
processInstaller.Account = ServiceAccount.LocalSystem;
//sets service start mode to automatic. service will auto boot on restarts
serviceInstaller.StartType = ServiceStartMode.Automatic;
// set service parameters
// Console.WriteLine(ConfigStream.getSetting("Service Name"));
serviceInstaller.ServiceName = ConfigStream.getSetting("Service Name");
// Console.WriteLine("\nService Name: " + serviceInstaller.ServiceName + "\n");
serviceInstaller.Description = ConfigStream.getSetting("Service Description");
//passing object to be installed
Installers.Add(serviceInstaller);
Installers.Add(processInstaller);
}
protected override void OnBeforeInstall(IDictionary savedState)
{
string parameter = "Config";
Context.Parameters["assemblypath"] = "\"" + Context.Parameters["assemblypath"] + "\" \"" + parameter + "\"";
base.OnBeforeInstall(savedState);
}
}
ConfigStream
public static class ConfigStream
{
// set class properties of ConfigStream
public static string config { get; private set; } = GlobalVariables.cwd + "\\" + GlobalVariables.configFileName;
// constructor with config as an argument
public static void setConfigstream(string config_location)
{
string config = config_location;
}
///Function: (static) getSetting
///Purpose: get requested value form formatted config file
public static string getSetting(string setting)
{
...
}
...
}
You can try something like this:
[RunInstaller(true)]
public partial class ServiceWrapperInstaller : Installer
{
private const string nameKey = "name";
private const string displayNameKey = "displayname";
private const string descriptionKey = "description";
protected override void OnBeforeInstall(IDictionary savedState)
{
// Set installer parameters
SetParameters();
base.OnBeforeInstall(savedState);
}
private void SetParameters()
{
// Set service name
_serviceInstaller.ServiceName = this.Context.Parameters[nameKey];
// Set the display name (if provided)
if (Context.Parameters.ContainsKey(displayNameKey))
{
_serviceInstaller.DisplayName = this.Context.Parameters[displayNameKey];
}
// Set the description (if provided)
if (Context.Parameters.ContainsKey(descriptionKey))
{
_serviceInstaller.Description = this.Context.Parameters[descriptionKey];
}
_serviceInstaller.StartType = ServiceStartMode.Automatic;
_serviceInstaller.DelayedAutoStart = true;
}
}
You can use it like this:
InstallUtil /u /name= /displayname=<\"Display Name\"> /description=<\"Description of Service\"
Is there a way to programmatically enable/disable an Azure function?
I can enable/disable a function using the portal under the "Manage" section, which causes a request to be sent to https://<myfunctionapp>.scm.azurewebsites.net/api/functions/<myfunction>
The JSON payload looks a bit like:
{
"name":"SystemEventFunction",
"config":{
"disabled":true,
"bindings":[
// the bindings for this function
]
}
// lots of other properties (mostly URIs)
}
I'm creating a management tool outside of the portal that will allow users to enable and disable functions.
Hoping I can avoid creating the JSON payload by hand, so I'm wondering if there is something in an SDK (WebJobs??) that has this functionality.
Further to #James Z.'s answer, I've created the following class in C# that allows you to programmatically disable / enable an Azure function.
The functionsSiteRoot constructor argument is the Kudu root of your Functions application, eg https://your-functions-web-app.scm.azurewebsites.net/api/vfs/site/wwwroot/
The username and password can be obtained from "Get publish profile" in the App Service settings for your Functions.
public class FunctionsHelper : IFunctionsHelper
{
private readonly string _username;
private readonly string _password;
private readonly string _functionsSiteRoot;
private WebClient _webClient;
public FunctionsHelper(string username, string password, string functionsSiteRoot)
{
_username = username;
_password = password;
_functionsSiteRoot = functionsSiteRoot;
_webClient = new WebClient
{
Headers = { ["ContentType"] = "application/json" },
Credentials = new NetworkCredential(username, password),
BaseAddress = functionsSiteRoot
};
}
public void StopFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: true);
}
public void StartFunction(string functionName)
{
SetFunctionState(functionName, isDisabled: false);
}
private void SetFunctionState(string functionName, bool isDisabled)
{
var functionJson =
JsonConvert.DeserializeObject<FunctionSettings>(_webClient.DownloadString(GetFunctionJsonUrl(functionName)));
functionJson.disabled = isDisabled;
_webClient.Headers["If-Match"] = "*";
_webClient.UploadString(GetFunctionJsonUrl(functionName), "PUT", JsonConvert.SerializeObject(functionJson));
}
private static string GetFunctionJsonUrl(string functionName)
{
return $"{functionName}/function.json";
}
}
internal class FunctionSettings
{
public bool disabled { get; set; }
public List<Binding> bindings { get; set; }
}
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
}
No, this is not possible currently. The disabled metadata property in function.json is what determines whether a function is enabled. The portal just updates that value when you enable/disable in the portal.
Not sure if it will meet your needs, but I'll point out that there is also a host.json functions array that can be used to control the set of functions that will be loaded (documented here). So for example, if you only wanted 2 of your 10 functions enabled, you could set this property to an array containing only those 2 function names (e.g. "functions": [ "QueueProcessor", "GitHubWebHook" ]), and only those will be loaded/enabled. However, this is slightly different than enable/disable in that you won't be able to invoke the excluded functions via the portal, whereas you can portal invoke disabled functions.
Further to #DavidGouge 's answer above, the code he posted does work, I just tested it and will be using it in my app. However it needs a couple of tweaks:
Remove the inheritance from IFunctionsHelper. I'm not sure what that interface is but it wasn't required.
Change the class definition for Binding as follows:
internal class Binding
{
public string name { get; set; }
public string type { get; set; }
public string direction { get; set; }
public string queueName { get; set; }
public string connection { get; set; }
public string accessRights { get; set; }
public string schedule { get; set; }
}
After that it would work.
P.S. I would have put this as a comment on the original answer, but I don't have enough reputation on Stack Overflow to post comments!
Using a combination of #Satya V's and #DavidGouge's solutions, I came up with this:
public class FunctionsHelper
{
private readonly ClientSecretCredential _tokenCredential;
private readonly HttpClient _httpClient;
public FunctionsHelper(string tenantId, string clientId, string clientSecret, string subscriptionId, string resourceGroup, string functionAppName)
{
var baseUrl =
$"https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.Web/sites/{functionAppName}/";
var httpClient = new HttpClient
{
BaseAddress = new Uri(baseUrl)
};
_httpClient = httpClient;
_tokenCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
}
private async Task SetAuthHeader()
{
var accessToken = await GetAccessToken();
_httpClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse($"Bearer {accessToken}");
}
private async Task<string> GetAccessToken()
{
return (await _tokenCredential.GetTokenAsync(
new TokenRequestContext(new[] {"https://management.azure.com/.default"}))).Token;
}
public async Task StopFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: true);
}
public async Task StartFunction(string functionName)
{
await SetFunctionState(functionName, isDisabled: false);
}
private async Task SetFunctionState(string functionName, bool isDisabled)
{
await SetAuthHeader();
var appSettings = await GetAppSettings();
appSettings.properties[$"AzureWebJobs.{functionName}.Disabled"] = isDisabled ? "1" : "0";
var payloadJson = JsonConvert.SerializeObject(new
{
kind = "<class 'str'>", appSettings.properties
});
var stringContent = new StringContent(payloadJson, Encoding.UTF8, "application/json");
await _httpClient.PutAsync("config/appsettings?api-version=2019-08-01", stringContent);
}
private async Task<AppSettings> GetAppSettings()
{
var res = await _httpClient.PostAsync("config/appsettings/list?api-version=2019-08-01", null);
var content = await res.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<AppSettings>(content);
}
}
internal class AppSettings
{
public Dictionary<string, string> properties { get; set; }
}
The problem with using the Kudu api to update the function.json file is that it will be overwritten on any subsequent deploy. This uses Azure's Rest Api to update the Configuration of the application. You will first need an Azure Service Principle to use the api though.
Using the Azure Cli, you can run az ad sp create-for-rbac to generate the Service Principle and get the client id and client secret. Because the UpdateConfiguration endpoint does not allow you to update a single value, and overwrites the entire Configuration object with the new values, you must first get all the current Configuration values, update the one you want, and then call the Update endpoint with the new Configuration keys and values.
I would imagine you can use Kudu REST API (specifically VFS) to update the disabled metadata property in function.json. Would that disable the function?
Here is the Kudu REST API. https://github.com/projectkudu/kudu/wiki/REST-API
The CLI command That is used to disable the Azure function through CLI - documented here
az functionapp config appsettings set --name <myFunctionApp> \
--resource-group <myResourceGroup> \
--settings AzureWebJobs.QueueTrigger.Disabled=true
I had captured fiddler while while running the above command.
Azure CLI works on the Python process The python process was issuing request to
https://management.azure.com to update appsetting.
got a reference to the same endpoint in the below REST Endpoint :
https://learn.microsoft.com/en-us/rest/api/appservice/webapps/updateapplicationsettings
Request URI :
PUT
https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/appsettings?api-version=2019-08-01
Headers :
Authorization: Bearer <> ;
Content-Type: application/json; charset=utf-8
Request Body:
{"kind": "<class 'str'>", "properties":JSON}
We can hardcode the properties or get it dynamically. For disabling the function, will have to update the JSON node of Properties : Azure.WebJobs.QueueTrigger.Disabled = True
To get properties you could use the endpoint, you could refer Web Apps - List Application Settings
The Output looks up as below :
Hope this helps :)
What about this: https://learn.microsoft.com/en-us/azure/azure-functions/disable-function?tabs=portal#localsettingsjson
This looks like the easiest solution for local development.
This one is a follow up question to Dependency Injection using Unity
So , as a set up I have a CustomConfiguration.cs file which is supposed to populate from a config section in my web.config file
This is the signature for the file
public class CustomConfiguration : ICustomProcessorConfig, IEmailConfig, IReportConfig
{
#region Properties
private CustomProcessorConfig ConfigSection { get; set; }
#endregion
#region Constructors (1)
public CustomConfiguration()
{
ConfigSection = ConfigurationManager.GetSection("customConfiguration") as ConfigSection;
}
#endregion Constructors
#region ICustomConfiguration Members
public string Text { get { return ConfigSection.Text; } }
public string Subject { get { return ConfigSection.Subject; } }
public string SmtpHost { get { return ConfigSection.EmailSettings.SmtpHost; } }
public int SmtpPort { get { return ConfigSection.EmailSettings.SmtpPort; } }
These are the 3 files I have for Email Generation :
public interface IEmailManager
{
void SendEmail(string toAddress, string fromAddress, string subject, string body, bool htmlBody);
}
public interface IEmailConfig
{
string SmtpHost { get; }
int SmtpPort { get; }
}
And Finally I have the Email Manager which inherits the IEmailManager interface
public class EmailManager: IEmailManager
{
#region Constructors (1)
public EmailManager(IEmailConfiguration configuration)
{
CurrentSmtpClient = new SmtpClient
{
Host = configuration.SmtpHost,
Port = configuration.SmtpPort,
Credentials =
new NetworkCredential(configuration.UserName, configuration.Password)
};
}
#endregion Constructors
// send Mail is also implemented
}
Coming back to the previous question I have my set up like :
Container = new UnityContainer();
Container.RegisterType<ICustomConfiguration,CustomConfiguration>(new ContainerControlledLifetimeManager());
Container.RegisterType<IEmailManager, EmailManager>(new ContainerControlledLifetimeManager());
Container.RegisterType<IReportGenerator, ReportGenerator>(new ContainerControlledLifetimeManager());
Configuration = Container.Resolve<ICustomConfiguration>();
Emailer = Container.Resolve<IEmailManager>();
ReportGenerator = Container.Resolve<IReportGenerator>();
I'm getting a ResolutionFailedExceptionsaying The parameter configuration could not be resolved when attempting to call constructor for EmailManager.
I had a different DI setup and I would need the configuration information from IEmailConfig still. Is there a way of going past this ? I need the config information to proceed with sending the email as you can guess with my setup.
Am I binding different repo to my Container ? Or how should I change my EmailManager code ?
You need to register the IEmailConfig interface with the CustomConfiguration class in the container.
Container.RegisterType<IEmailConfig , CustomConfiguration >(new ContainerControlledLifetimeManager());
IEmailConfiguration missing mapping in the unity container. Need to add the concrete class that maps this interface
From My MVC4 page I need to call a Powershell script. I don't really need to return any result from it, just make sure the script runs.
When I Debug in my computer, it works fine, but when I try after publishing, it just doesn't do anything or show any error.
This is the code in my Controller:
using (new Impersonator(ConfigurationManager.AppSettings["ImpersonatorUser"],
ConfigurationManager.AppSettings["ImpersonatorDomain"],
ConfigurationManager.AppSettings["ImpersonatorPassword"]))
{
var scr = new PSScriptParam("\\\\SERVER\\...\\Script.ps1", Param);
scr.Run();
}
The class PSScriptParam is just this:
public class PSScriptParam
{
public string Script { get; set; }
public string Param { get; set; }
public PSScriptParam(string Path, string param)
{
Param = param;
Script = Path;
}
public void Run()
{
try
{
Process _Proc = Process.Start("Powershell.exe", Script + " '" + Param + "'");
}
catch (Exception err)
{
System.IO.File.WriteAllText("\\\\SERVER\\...\\Error.txt", err.Message.ToString());
}
}
}
The impersonator is using a domain admin account, and the execution policy is set as unrestricted in the server (there is no problem running the script from the cmd).
Can anyone help?
Thanks in advance!
This problem was solved after applying pending updates in the server... Even if I'm not sure why it didn't work, updating Windows solved the problem.