Passing variables to powershell from C# : Output file location variables - c#

I have recently began using C# to invoke powershell scripts, with some success :)
$spWeb = Get-SPWeb -Identity http://127.0.0.1
$listTemplate = [Microsoft.SharePoint.SPListTemplateType]::DocumentLibrary
$spWeb.Lists.Add($args, "", $listTemplate) > $myDirectory
$spWeb.Lists.Add returns a SharePoint GUID.
Question:
I simply wish to pass in the directory/filename in which the GUID will
be written. How could that be done?
I have posted on the MSDN forums here: http://goo.gl/5p0oz but have continued my search on stackoverflow due to a seemingly dead end thread. This post is a cumulative gathering of the information found through MSDN responses.

You can try using the Set-Content cmdlet instead, like this. You need to pass the $myFile as a string and Set-Content will do the rest for you -
Inside your script i.e. MyScript.ps1 here, you will have this piece of code -
param([string]$parameter, [string]$myFile)
try
{
$spWeb = Get-SPWeb -Identity http://127.0.0.1
$listTemplate = [Microsoft.SharePoint.SPListTemplateType]::DocumentLibrary
$guid = $spWeb.Lists.Add($parameter, "", $listTemplate)
$guid | Set-Content $myFile
}
catch
{
throw ("Failed. The error was: '{0}'." -f $_ )
}
How to Run:
Open Powershell Prompt and type this
.\MyScript.ps1 "someparam1" "D:\Output.txt"
C# Bit -
private PowerShell _ps;
_ps = PowerShell.Create();
var parameter = new List<string>
{
parameter,
myFile
};
var sr = new StreamReader(#"C:\MyScript.ps1");
_ps.AddScript(sr.ReadToEnd()).AddParameters(parameter);
_ps.Invoke();

Related

Get MFA informations in C# program

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.

Retrieve the list of Tasks for Build Definition using .NET client libraries for VSTS/TFS

I'm using the .NET client libraries for VSTS/TFS (https://learn.microsoft.com/en-us/vsts/integrate/concepts/dotnet-client-libraries?view=vsts) to retrieve a list of tasks for all Build Definitions for all Team Projects. I'm using the v16.139.0-preview version of the NuGet package Microsoft.TeamFoundation.ExtendedClient (I need to because I need to retrieve Release Definition workflow as well for which you need Microsoft.VisualStudio.Services.Release.Client which has a dependency requirement for the ExtendedClient). The server (on-prem) is a TFS 2017.2. In no way I'm able to retrieve the tasks/phases/process. This is my code:
VssConnection connection = new VssConnection(new Uri("http://tfsserver:8080/tfs/defaultcollection"), new VssClientCredentials());
ProjectHttpClient projectClient = connection.GetClient<ProjectHttpClient>();
IEnumerable<TeamProjectReference> projects = projectClient.GetProjects().Result;
BuildHttpClient buildClient = connection.GetClient<BuildHttpClient>();
foreach (var project in projects)
{
IPagedList<BuildDefinition> buildDefinitions = buildClient.GetFullDefinitionsAsync2(project: project.Name, name: null).Result;
foreach (BuildDefinition buildDefinition in buildDefinitions)
{
// get the tasks
}
}
I have tried to re-retrieve the Build Definition using buildClient.GetDefinitionAsync without additional effect
The "Steps" property (which is going to be deprecated) is always null
The "Process" property is empty
There is no "phases" property available (which seems logical looking at the options to have multiple phases in a Build Definition
There is a contract available for BuildDefinitionStep: https://learn.microsoft.com/en-us/vsts/extend/reference/client/api/tfs/build/contracts/builddefinitionstep?view=vsts
The REST API documentation doesn't have a property called "Step": https://learn.microsoft.com/en-us/rest/api/vsts/build/definitions/get?view=vsts-rest-4.1#builddefinition
Does anyone have an idea on how to solve this?
Just try below C# sample using .NET client libraries, test on TFS 2017.3 and VSTS, both work. (No TFS 2017.2 on my side, If I remember correctly, TFS 2017.2 has the similar build process with TFS 2015, It has no the "Process" and "phases" attribute. )
using System;
using Microsoft.TeamFoundation.Build.WebApi;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
namespace RetrieveTaskList
{
class Program
{
static void Main(string[] args)
{
//For TFS :
var tfsUrl = "http://ws-tfs2017:8080/tfs/DefaultCollection";
var buildClient = new BuildHttpClient(new Uri(tfsUrl), new VssAadCredential());
//For VSTS:
//var tfsUrl = "https://{account}.visualstudio.com";
//var buildClient = new BuildHttpClient(new Uri(tfsUrl), new VssBasicCredential(string.Empty, "PAT here"));
var definitions = buildClient.GetFullDefinitionsAsync(project: "ScrumProject");
foreach (var definition in definitions.Result)
{
Console.WriteLine(string.Format("\n {0} - {1}:", definition.Id, definition.Name));
// Get BuildDefinitionStep to array, each of which has a task property that contains things like the name of the task and the inputs.
var tasks = definition.Steps.ToArray();
//Get each step/task from the array
foreach (var task in tasks)
{
Console.WriteLine(task.DisplayName);
}
}
Console.ReadLine();
}
}
}
You can also use the REST API to retrieve the list of tasks from a build definition.
PowerShell for example:
Param(
[string]$baseurl = "http://server:8080/tfs/DefaultCollection",
[string]$projectName = "ProjectName",
[string]$buildDefinitionID = "26",
[string]$user = "domain\user",
[string]$token = "password"
)
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$uri = "$baseurl/$($projectName)/_apis/build/definitions/$buildDefinitionID"
Write-Host $uri
$result = (Invoke-RestMethod -Uri $uri -Method Get -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
$tasks = $result.process.phases.steps.displayName
foreach ($task in $tasks)
{
write-host $task
}
You can also try the REST Client, please reference this thread : Retrieve VSTS/TFS Build task name list
A bit late to the party, but if you're wanting to iterate over the Build tasks using the AzDO .NET Client library you need to cast the Process to a DesignerProcess/DockerProcess/YamlProcess.
var buildDefinitions = await _buildClient.GetFullDefinitionsAsync(project.Id);
foreach (var buildDefinition in buildDefinitions)
{
if (buildDefinition.Process != null && buildDefinition.Process is Microsoft.TeamFoundation.Build.WebApi.DesignerProcess designerProcess)
{
foreach (var phase in designerProcess.Phases)
foreach (var step in phase.Steps)
Console.WriteLine($"taskname={step.DisplayName}");
break;//lets exit the loop early
}
}
Demo repo, https://github.com/f2calv/azdo-api-net-client-issue
With the help of #Andy I was able to solve the problem. I used Fiddler call the REST Api (http://server:8080/tfs/DefaultCollection/MyProject/_apis/build/definitions/1) and read the JSON response. I discovered that the "build" property contains the collection of tasks. I fixed the PowerShell script provided by #Andy:
Param(
[string]$baseurl = "http://server:8080/tfs/DefaultCollection",
[string]$projectName = "MyProject",
[string]$buildDefinitionID = "530",
[string]$user = "domain\user",
[string]$token = "PersonalAccessToken"
)
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$uri = "$baseurl/$($projectName)/_apis/build/definitions/$buildDefinitionID"
Write-Host $uri
$result = (Invoke-RestMethod -Uri $uri -Method Get -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
foreach ($task in $result.build)
{
Write-Host $task.displayName
}

How do I recreate PowerShell $profile variable? It's empty in a custom Host

If you implement a custom PowerShell Host with System.Management.Automation (SMA), all of the automatic variables are avaialable, except it seems that $PROFILE is empty. How would one go about recreating it?
Is it always in UserProfile + \Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1? Or do I need to be worried about it being in other places?
To clarify, I only care about CurrentUserCurrentHost profile.
Details
Given this PowerShell script:
Write-Output "_Profiles_"
Write-Output "CurrentUserCurrentHost = '$($Profile.CurrentUserCurrentHost)'"
Write-Output "CurrentUserAllHosts = '$($Profile.CurrentUserAllHosts)'"
Write-Output "AllUsersCurrentHost = '$($Profile.AllUsersCurrentHost)'"
Write-Output "AllUsersAllHosts = '$($Profile.AllUsersAllHosts)'"
Running it against system PowerShell has the following output:
_Profiles_
CurrentUserCurrentHost = 'C:\Users\rob\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1'
CurrentUserAllHosts = 'C:\User\rob\Documents\WindowsPowerShell\profile.ps1'
AllUsersCurrentHost = 'C:\Windows\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1'
AllUsersAllHosts = 'C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1'
Running it with a Custom C# PowerShell Host (a System.Management.Automation.Host.PSHost implementation) shows:
_Profiles_
CurrentUserCurrentHost = ''
CurrentUserAllHosts = ''
AllUsersCurrentHost = ''
AllUsersAllHosts = ''
Background
https://github.com/chocolatey/choco/issues/667
This has been tested in PowerShell v3 / System.Managment.Automation (SMA) v3 but it could easily be proven out in other PowerShell versions.
As a possible fix, this is what I've come up with. However it does mean that $profile is always expected to be in the Documents folder.
var documentsFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments, Environment.SpecialFolderOption.DoNotVerify);
var currentUserCurrentHostProfile = _fileSystem.combine_paths(documentsFolder, "WindowsPowerShell\\Microsoft.PowerShell_profile.ps1");
var profileFix = #"
if ((Test-Path(""{0}"")) -and ($profile -eq $null -or $profile -eq '')) {{
$global:profile = ""{1}""
}}
".format_with(documentsFolder, currentUserCurrentHostProfile);
pipeline.Commands.Add(new Command(profileFix, isScript: true, useLocalScope: false));
This is done this way due to special accounts like LocalSystem that would not have a profile folder.
Note: .format_with is a string formatter, so the {{ and }} you see will be converted to { and } when it finishes the string formatting.

How can I fix my Powershell script which reads Assembly attributes but leaves an open file handle?

When I run the below script from within my VS2013 C# PreBuildEvent block, it locks the executable/assembly whose attributes I am querying: "open file handle" is the error I get.
When I run it interactively, it appears to work fine.
How can I add a 'using' statement or otherwise close the file handle?
thank you!
function Get-AssemblyProperty {
param
(
$anExe = "%temp%\my.exe",
[ValidateSet('System.Runtime.Versioning.TargetFrameworkAttribute',
'System.Reflection.AssemblyFileVersionAttribute',
'System.Reflection.AssemblyTitleAttribute',
'System.Reflection.AssemblyDescriptionAttribute',
'System.Reflection.AssemblyConfigurationAttribute',
'System.Reflection.AssemblyCompanyAttribute',
'System.Reflection.AssemblyProductAttribute',
'System.Reflection.AssemblyCopyrightAttribute',
'System.Reflection.AssemblyTrademarkAttribute',
'System.Runtime.InteropServices.ComVisibleAttribute',
'System.Runtime.InteropServices.GuidAttribute',
'System.Diagnostics.DebuggableAttribute',
'System.Runtime.CompilerServices.CompilationRelaxationsAttribute',
'System.Runtime.CompilerServices.RuntimeCompatibilityAttribute' )]
$anAttribute = "System.Reflection.AssemblyDescriptionAttribute"
)
$thisFcn = "Get-AssemblyProperties"
$assembly = [Reflection.Assembly]::LoadFile($anexe)
$ary = $assembly.GetCustomAttributesData()
Write-Debug "$thisFcn : Array = $ary ."
$row = $ary | Where-Object { $_.AttributeType -like "$anAttribute" }
Write-Debug "$thisFcn : Matching Row = $row ."
# Matching Row = [System.Reflection.AssemblyDescriptionAttribute("Our application for Great Company")]
$ans = ( $row -split '"' )[1] # second
Write-Debug "$thisFcn : Answer = $ans ."
return $ans
}
Load the assembly from a byte array and it won't lock the file:
$bytes = [System.IO.File]::ReadAllBytes("C:\MyExe.exe")
[Reflection.Assembly]::Load($bytes)....
Have you tried to "$assembly.Dispose()"?
You might want to read this link

Creating new EventSource using Powershell differs to C#?

I am creating a new EventLog EventSource, and I use my own custom set of categories.
I currently do this in C# code with the following:
var eventSourceData = new EventSourceCreationData(sourceName, logName)
{
CategoryCount = AllCategories.Count(),
CategoryResourceFile = resourceFilePath,
ParameterResourceFile = resourceFilePath,
MessageResourceFile = resourceFilePath
};
EventLog.CreateEventSource(eventSourceData);
I have now converted this to a powershell script like this:
New-eventlog -logname $Log -ComputerName $ComputerName -Source $Source -CategoryResourceFile $categoryDllPath -MessageResourceFile $categoryDllPath -ErrorVariable Err -CategoryCount 20
Notr -CategoryCount 20 at the end, my script fails on this argument saying it is not a valid parameter. (Which it seems not to be)
So my question is, using Powershell how can I provide the CategoryCount so that the logging works correctly?
Thanks so much.
The error message is A parameter cannot be found that matches parameter name 'CategoryCount'.
As far as I can see, New-EventLog doesn't support CategoryCount.
But you can still use .NET classes directly in Powershell, so something like this should work:
$eventSourceData = new-object System.Diagnostics.EventSourceCreationData("$SourceName", "$logName")
$eventSourceData.CategoryCount = 20
$eventSourceData.CategoryResourceFile = $CategoryDllPath
$eventSourceData.MessageResourceFile = $CategoryDllPath
If (![System.Diagnostics.EventLog]::SourceExists($eventSourceData.Source))
{
[System.Diagnostics.EventLog]::CreateEventSource($eventSourceData)
}

Categories

Resources