I am trying to get informations about MFA in my C# application. I already achieved to get satisfying results in Powershell but i'm struggling to make the same thing in C#.
My code in Powershell :
Get-MsolUser -SearchString c.test#mytenant.com |
Where-Object {$_.StrongAuthenticationRequirements -like “*”} |
Select-Object UserPrincipalName, DisplayName, #{n='MFA';e=
{$_.StrongAuthenticationRequirements.State}}, #{n='Methods'; e=
{($_.StrongAuthenticationMethods).MethodType}}, #{n='Default Method'; e=
{($_.StrongAuthenticationMethods).IsDefault}}
UserPrincipalName : c.test#mytenant.com
DisplayName : Cyril test
MFA : Enforced
Methods : {OneWaySMS, TwoWayVoiceMobile, PhoneAppOTP,
PhoneAppNotification}
Default Method : {False, False, False, True}
As you can see, i get the MFA status and methods used.
Now, i want to do the same in C#.
My function :
public static List<string> GetMFA(Runspace runspace, string nom)
{
List<string> listResult = new List<string>();
try
{
Command getLicenseCommand = new Command("Get-MsolUser");
getLicenseCommand.Parameters.Add(new CommandParameter("SearchString", nom));
var pipe = runspace.CreatePipeline();
pipe.Commands.Add(getLicenseCommand);
var props = new string[] { "displayname", "userprincipalname", "StrongAuthenticationRequirements" };
Command CommandSelect = new Command("Select-Object");
CommandSelect.Parameters.Add("Property", props);
pipe.Commands.Add(CommandSelect);
// Execute command and generate results and errors (if any).
Collection<PSObject> results = pipe.Invoke();
if (results.Count != 0)
{
var error = pipe.Error.ReadToEnd();
if (error.Count > 0)
{
throw new Exception(error[0].ToString());
}
foreach (PSObject resultat in results)
{
string dn = resultat.Properties["displayname"].Value.ToString();
string upn = resultat.Properties["userprincipalname"].Value.ToString();
string mfa = resultat.Properties["StrongAuthenticationRequirements"].Value.ToString();
string res = dn + '/' + upn + '/' + mfa;
listResult.Add(res);
}
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return listResult;
}
The "StrongAuthenticationRequirements" property isn't returning something like "Enforced" but
System.Collections.Generic.List`1[Microsoft.Online.Administration.StrongAuthenticationRequirement]
What am i missing here ?
I'm guessing the OP doesn't care any more, but for the next person who struggled with this like I have for the last few hours, I'm posting an answer.
In order to call these items, you must include the Microsoft.Online.Administration references which are included as DLLs within the MSOL PowerShell module package.
In Visual Studio:
Right Click References
Select "Add Reference"
Open the "Browse.."
Navigate to the installation location of the MSOL module. This may vary depending on if it was installed on the user/machine scope.
I added Microsoft.Online.Administration.PSModule
You will now be able to parse the results in c#.
Note: It was my experience that I needed to explicitly cast the items into a variable before I could use them.
Edit:
I removed the unnecessary reference to the resources.dll after further testing.
Related
I'm trying to write PowerShell code to create an Outlook rule to move an email, and it isn't working.
Note that I do not have access to the server, so the *-InboxRule cmdlets like New-InboxRule aren't available.
There's something wonky going on with the COM interop between PowerShell and Outlook, because it works perfectly in C#, but the identical code in PowerShell doesn't.
Here's the C# code, which works perfectly:
Microsoft.Office.Interop.Outlook.Application outlook = null;
try
{
outlook = (Microsoft.Office.Interop.Outlook.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Outlook.Application");
}
catch
{
}
if (outlook == null)
{
outlook = new Microsoft.Office.Interop.Outlook.Application();
}
var inbox = outlook.Application.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox);
var oMoveTarget = inbox.Folders["MoveTarget"];
// debugging
Console.WriteLine(inbox.FolderPath.ToString());
Console.WriteLine(oMoveTarget.FolderPath.ToString());
var rules = outlook.Session.DefaultStore.GetRules();
Console.WriteLine(string.Format("There are {0} rules", rules.Count));
var name = string.Format("Rule {0}", DateTime.Now.ToString("yyyyMMdd_HHmmss"));
var rule = rules.Create(name, Microsoft.Office.Interop.Outlook.OlRuleType.olRuleReceive);
// conditions
rule.Conditions.From.Recipients.Add("John Smith");
rule.Conditions.From.Recipients.ResolveAll();
rule.Conditions.From.Enabled = true;
// actions
rule.Actions.MoveToFolder.Folder = oMoveTarget;
rule.Actions.MoveToFolder.Enabled = true;
rules.Save(true);
Now, here's the identical (other than language syntax) PowerShell code:
try
{
$outlook = [Runtime.InteropServices.Marshal]::GetActiveObject("Outlook.Application");
}
catch
{
}
if ($outlook -eq $null)
{
$outlook = New-Object -ComObject Outlook.Application
}
$inbox = $outlook.Application.Session.GetDefaultFolder([Microsoft.Office.Interop.Outlook.OlDefaultFolders]::olFolderInbox);
$oMoveTarget = $inbox.Folders["MoveTarget"];
# debugging
[Console]::WriteLine($inbox.FolderPath.ToString());
[Console]::WriteLine($oMoveTarget.FolderPath.ToString());
$rules = $outlook.Session.DefaultStore.GetRules();
[Console]::WriteLine([string]::Format("There are {0} rules", $rules.Count));
$name = [string]::Format("Rule {0}", [DateTime]::Now.ToString("yyyyMMdd_HHmmss"));
$rule = $rules.Create($name, [Microsoft.Office.Interop.Outlook.OlRuleType]::olRuleReceive);
# conditions
$rule.Conditions.From.Recipients.Add("John Smith");
$rule.Conditions.From.Recipients.ResolveAll();
$rule.Conditions.From.Enabled = $true;
# actions
$rule.Actions.MoveToFolder.Folder = $oMoveTarget;
$rule.Actions.MoveToFolder.Enabled = $true;
$rules.Save($true);
While the C# code succeeds, the PS code fails with:
One or more rules cannot be saved because of invalid actions or conditions.
At line:1 char:1
+ $rules.Save($true);
+ ~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMException
Upon further investigation, it's basically caused by this line, which isn't actually doing anything:
$rule.Actions.MoveToFolder.Folder = $oMoveTarget;
When I run the C# equivalent, if I immediately turn around and look at that property, it is set. However, in PowerShell, it doesn't throw an error or anything... it just silently does nothing.
How can I get this to work? Please help!
You can do this with:
# actions
$action = $rule.Actions.MoveToFolder
$action.Enabled = $true
[Microsoft.Office.Interop.Outlook.MoveOrCopyRuleAction].InvokeMember("Folder",[Reflection.BindingFlags]::SetProperty, $null, $action, $oMoveTarget)
You can run C# code in powershell. Is there a requirement to rewrite it in powershell?
$code = #'
using System;
namespace Outlook
{
public class Outlook
{
public static void Main(){
Microsoft.Office.Interop.Outlook.Application outlook = null;
try
{
outlook = (Microsoft.Office.Interop.Outlook.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Outlook.Application");
}
catch
{
}
if (outlook == null)
{
outlook = new Microsoft.Office.Interop.Outlook.Application();
}
var inbox = outlook.Application.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox);
var oMoveTarget = inbox.Folders["TargetFolder"];
// debugging
Console.WriteLine(inbox.FolderPath.ToString());
Console.WriteLine(oMoveTarget.FolderPath.ToString());
var rules = outlook.Session.DefaultStore.GetRules();
Console.WriteLine(string.Format("There are {0} rules", rules.Count));
var name = string.Format("Rule {0}", DateTime.Now.ToString("yyyyMMdd_HHmmss"));
var rule = rules.Create(name, Microsoft.Office.Interop.Outlook.OlRuleType.olRuleReceive);
// conditions
rule.Conditions.From.Recipients.Add("John Smith");
rule.Conditions.From.Recipients.ResolveAll();
rule.Conditions.From.Enabled = true;
// actions
rule.Actions.MoveToFolder.Folder = oMoveTarget;
rule.Actions.MoveToFolder.Enabled = true;
rules.Save(true);
}
}
}
'#
$assemblies = ("Microsoft.Office.Interop.Outlook")
Add-Type -ReferencedAssemblies $assemblies -TypeDefinition $code -Language CSharp
[Outlook.Outlook]::Main()
Looking at other articles and even this StackOverflow question, I still can't get this to work. The machine is contactable on the network but still produces an IO error.
The is the code I'm using:
var environmentKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "gm-1015").OpenSubKey("Environment");
TextBox1.Text = environmentKey.ToString();
When I try to run it, I get:
System.IO.IOException: 'The network path was not found.
Which from Microsoft's website indicates that the machine is not accessible.
Am I missing something?
Came to the conclusion that the remote registry service wasn't enabled or turned on for the remote computer. Instead of enabling it which would open another door into the computer for any intruders, I use the below code:
PowerShell ps = PowerShell.Create()
.AddCommand("Invoke-Command")
.AddParameter("ComputerName", SearcherClass.CurrentComputer)
.AddParameter("Scriptblock", ScriptBlock.Create("Get-Tpm | Select-Object -ExpandProperty TPMPresent"));
This starts a new PowerShell instance and runs the Invoke-Command to eventually run the Get-Tpm command which ultimately returns the TPMPresent property
This is the actual code block that I used to evaluate and return a usable result to my form:
public static string GetTPM()
{
string output = "N/A";
try
{
PowerShell ps = PowerShell.Create()
.AddCommand("Invoke-Command")
.AddParameter("ComputerName", SearcherClass.CurrentComputer)
.AddParameter("Scriptblock", ScriptBlock.Create("Get-Tpm | Select-Object -ExpandProperty TPMPresent"));
foreach(PSObject i in ps.Invoke())
{
output = i.BaseObject.ToString();
}
if (output == "True")
{
output = "Present";
}
else
{
output = "None";
}
return output;
}
catch
{
return "Failed";
}
}
I started using CodeFluentRuntimeClient to replace Interop.MSScriptControl.dll.
I succeed here by tweeking a bit the dll to make it work.
We started using the dll in production. On one of the machines that we installed on it (windows server 2012), we are having a Sytem.AccessViolationException.
Here's the stack trace of the event viewer:
Do CodeFluent requieres any other dlls?
EDIT
Here's the code:
public dynamic EvaluateVBScript(string token, string key, string script, IDictionary<string, object> parameterValuePair = null)
{
try
{
using (ScriptEngine engine = new ScriptEngine(ScriptEngine.VBScriptLanguage))
{
List<object> parameters = new List<object>() { string.IsNullOrEmpty(token) ? string.Empty : ServiceManager.GetService<IServiceInstance>().GetService<IContextManager>(token).UserName };
string extraParameters = string.Empty;
if (parameterValuePair != null && parameterValuePair.Count > 0)
{
extraParameters = "," + string.Join(",", parameterValuePair.Select(e => e.Key));
foreach (var para in parameterValuePair)
parameters.Add(para.Value);
}
string parsedScript = string.Format(#"Function {0}(NecUserProfile {2})
{1}
End Function", key, script, extraParameters);
ParsedScript parsed = engine.Parse(parsedScript);
dynamic value = parsed.CallMethod(key, parameters.ToArray());
return (value != null) ? value.ToString() : string.Empty;
}
}
catch
{
throw;
}
}
After some tests, we found out that the client had an antivirus (Kaspersky) installed on his server. Even after disabling the antivirus, the access violation error was still occurring.
After uninstalling the antivirus, we were finally able to execute the JavaScript. We still don't know what rule was set in the antivirus that was blocking the script to be parsed.
I didn't test in the suggested solution by Simon Mounier. I don't know if it would have solved the problem.
The solution was to drop out the CodeFluent.Runtime.Client.dll and use directly the source code provided here. Also add MarshalAs(UnmanagedType.LPWStr)] around the string parameters that are going to be used by the parse function, like in here.
I am detecting whether or not I'm attempting a connection against localhost, and creating (or not) the WMI connection options as follows:
if (NetworkUtils.IsLocalIpAddress(machineName))
{
_scope = new ManagementScope(string.Format(#"\\{0}\root\cimv2", machineName));
}
else
{
_connectionOptions = new ConnectionOptions
{
Username = username,
Password = password,
Impersonation = ImpersonationLevel.Impersonate
};
_scope = new ManagementScope(string.Format(#"\\{0}\root\cimv2", machineName), _connectionOptions);
}
When I call _scope.Connect() in either case, it works. That is, no exception and IsConnected is true.
However, when I attempt to invoke a method in the local case, such as Win32_Share.Create I get errors. The following code always works for remote connections for me:
var winSharePath = new ManagementPath("Win32_Share");
var winShareClass = new ManagementClass(_scope, winSharePath, null);
var shareParams = winShareClass.GetMethodParameters("Create");
shareParams["Path"] = pathName.TrimEnd('\\');
shareParams["Name"] = shareName;
shareParams["Type"] = 0;
shareParams["Description"] = "CMC Bootstrap Share";
var outParams = winShareClass.InvokeMethod("Create", shareParams, null);
if ((uint) (outParams.Properties["ReturnValue"].Value) != 0)
{
throw new Exception("Unable to share directory. Error code: " +
outParams.Properties["ReturnValue"].Value);
}
I create the pathName directory just prior to invoking this method, so I guarantee pathName exists in all cases.
When executing locally ONLY on Windows Server 2008 & 2012, the above code throws the exception with error code 24. Executing against localhost on Windows 8 works just fine.
What is the correct way to specify "blank credentials" when invoking WMI methods against localhost, as I believe this is the underlying issue?
I tried the code below on my local PC and this works (shares my temp folder). Could you try the same please? Also, which is the patch & share name you're using?
string pathName = #"c:\temp\";
string shareName = "tempFolder";
var scope = new ManagementScope(string.Format(#"\\{0}\root\cimv2", "localhost"));
// your code below
var winSharePath = new ManagementPath("Win32_Share");
var winShareClass = new ManagementClass(scope, winSharePath, null);
var shareParams = winShareClass.GetMethodParameters("Create");
shareParams["Path"] = pathName.TrimEnd('\\');
shareParams["Name"] = shareName;
shareParams["Type"] = 0;
shareParams["Description"] = "CMC Bootstrap Share";
var outParams = winShareClass.InvokeMethod("Create", shareParams, null);
if ((uint)(outParams.Properties["ReturnValue"].Value) != 0)
{
throw new Exception("Unable to share directory. Error code: " +
outParams.Properties["ReturnValue"].Value);
}
the above code throws the exception with error code 24
That doesn't have anything to do with the error you mention in the title of your question. Error codes for Win32_Share.Create method are documented in this MSDN article. Return value 24 means "Unknown Device or Directory".
In other words, your pathName variable is wrong.
I can't seem to get this thing to run. I believe it's mainly related to the first parameter for ApplicationId. I can't figure out what ID to enter. I get a return result of 4 which means bad ID. All the samples out there were for 2007 and used SearchContext and is deprecated. Anyone?
public void CompileAudience(SPServiceContext serviceContext, AudienceManager audienceMgr, string AudienceName)
{
try
{
int RunJob = -1;
CustomMaintenanceTimerJobLogging.LogInfo(CustomMaintenanceTimerJobLogging.CategoryType.AudienceCompile, String.Format("Started compiling the audience '{0}' at {1}", AudienceName, DateTime.Now.ToShortTimeString()));
SPSecurity.RunWithElevatedPrivileges(delegate()
{
// Access the service proxy instance of search application proxy.
SearchServiceApplicationProxy searchApplicationProxy = serviceContext.GetDefaultProxy(typeof(SearchServiceApplicationProxy)) as SearchServiceApplicationProxy;
// Service Application Info object to retrieve the application id for the search service.
SearchServiceApplicationInfo searchApplicationInfo = searchApplicationProxy.GetSearchServiceApplicationInfo();
string[] args = new string[4];
args[0] = searchApplicationInfo.SearchServiceApplicationId.ToString();
args[1] = "1"; // 1=Start compile, 0=Stop
args[2] = "1"; // 1=Full, 0=Incremental
args[3] = AudienceName;
RunJob = AudienceJob.RunAudienceJob(args);
});
CustomMaintenanceTimerJobLogging.LogInfo(CustomMaintenanceTimerJobLogging.CategoryType.AudienceCompile, String.Format("Completed compiling the audience '{0}' at {1} with a Result Code of {2} (0 means no errors)", AudienceName, DateTime.Now.ToShortTimeString(), RunJob));
}
catch (Exception ex)
{
CustomMaintenanceTimerJobLogging.LogError(CustomMaintenanceTimerJobLogging.CategoryType.AudienceCompile, ex);
}
}
Please check out my blog post: http://blog.repsaj.nl/?p=258. That's all you need to get there. The application ID you need to pass isn't the search service id, but the user profile id. It takes some reflection to find that ID.