Convert C# logic to powershell for TFS - powershell

I have a C# program which build me a TFS build definition. I want to do the same code in a powershell script. So far, I have been able code the script which will create me a new build definition in TFS. However, I have trouble setting Process section of the build definition. I need to convert the below code in C# to powershell and all attemps I have made did not work.
//Set process parameters
var process = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters);
//Set BuildSettings properties
BuildSettings settings = new BuildSettings();
settings.ProjectsToBuild = new StringList("$/Templates/Main/Service/application1");
settings.PlatformConfigurations = new PlatformConfigurationList();
settings.PlatformConfigurations.Add(new PlatformConfiguration("Any CPU", "Debug"));
process.Add("BuildSettings", settings);
buildDefinition.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);
First I loaded the assemblies I need to work with TFS. When I want to replicate the same C# code as,
var process = WorkflowHelpers.DeserializeProcessParameters(buildDefinition.ProcessParameters);
I did following in PowerShell
$process = New-Object Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers.
Above gave me an error saying "Constructor not found. Cannot find an appropriate constructor for type Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers"
I checked and there are no constructors for that. My question is what I am I doing wrong in writing the PowerShell script to achieve the same functionality as c# code. I am sure it's syntax error that I am doing and not aware of the correct way of doing it in PowerShell.

It would appear from your code snippet (and confirmed via MSDN) that the DeserializeProcessParameters is a static method on the WorkflowHelpers class. You would need to invoke it with the following syntax in PowerShell:
$process = [Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers]::DeserializeProcessParameters($buildDefinition.ProcessParameters)
It looks like the buildDefinition variable is declared earlier - so I just stuck a $ character on it to make it a legit PowerShell variable. Same thing with the process variable. I hope this helps!

Related

Getting target framework attribute in PowerShell Core

I'm looking for a way to retrieve the target framework attribute (e.g. .NETCoreApp,Version=v2.1) from a DLL when using PowerShell Core, ideally without loading the DLL directly into the main session.
I can do this in Windows PowerShell 5, as it has access to the ReflectionOnlyLoadFrom method...
$dllPath = 'C:\Temp\ADALV3\microsoft.identitymodel.clients.activedirectory.2.28.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll'
[Reflection.Assembly]::ReflectionOnlyLoadFrom($dllPath).CustomAttributes |
Where-Object {$_.AttributeType.Name -eq 'TargetFrameworkAttribute'} |
Select -ExpandProperty ConstructorArguments |
Select -ExpandProperty value
However, I realise that this approach isn't available in .NET Core.
Editor's note: Even though the documentation (as of this writing) misleadingly suggests that the ReflectionOnlyLoadFrom method is available in .NET Core, it is not, as explained here.
From what I've seen, it looks likely that I should be able to access the custom attributes that hold the target framework attribute by using an instance of the System.Reflection.Metadata.MetadataReader class that's available in .NET Core (a couple of examples of this in use can be found here: https://csharp.hotexamples.com/examples/System.Reflection.Metadata/MetadataReader/GetCustomAttribute/php-metadatareader-getcustomattribute-method-examples.html ). However, all the constructors for this type seem to use a Byte* type, as the following shows when running from PowerShell Core:
([type] 'System.Reflection.Metadata.MetadataReader').GetConstructors() | % {$_.GetParameters() | ft}
I have no idea how to create a Byte* type in any version of PowerShell. Perhaps there's a method in System.Reflection.Metadata that I should be using before creating the MetadataReader object, but I haven't found it yet.
Apologies for the length of this question, but I'm hoping by sharing my notes I'll help in tracking down the solution. Any advice on how this target framework information can be obtained using PowerShell Core?
After quite a bit of work, I managed to put together a PowerShell script that works (without external dependencies) in PowerShell Core that pulls in the target framework from a DLL:
$dllPath = 'C:\Temp\ADALV3\microsoft.identitymodel.clients.activedirectory.2.28.4\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll'
$stream = [System.IO.File]::OpenRead($dllPath)
$peReader = [System.Reflection.PortableExecutable.PEReader]::new($stream, [System.Reflection.PortableExecutable.PEStreamOptions]::LeaveOpen -bor [System.Reflection.PortableExecutable.PEStreamOptions]::PrefetchMetadata)
$metadataReader = [System.Reflection.Metadata.PEReaderExtensions]::GetMetadataReader($peReader)
$assemblyDefinition = $metadataReader.GetAssemblyDefinition()
$assemblyCustomAttributes = $assemblyDefinition.GetCustomAttributes()
$metadataCustomAttributes = $assemblyCustomAttributes | % {$metadataReader.GetCustomAttribute($_)}
foreach ($attribute in $metadataCustomAttributes) {
$ctor = $metadataReader.GetMemberReference([System.Reflection.Metadata.MemberReferenceHandle]$attribute.Constructor)
$attrType = $metadataReader.GetTypeReference([System.Reflection.Metadata.TypeReferenceHandle]$ctor.Parent)
$attrName = $metadataReader.GetString($attrType.Name)
$attrValBytes = $metadataReader.GetBlobContent($attribute.Value)
$attrVal = [System.Text.Encoding]::UTF8.GetString($attrValBytes)
if($attrName -eq 'TargetFrameworkAttribute') {Write-Output "AttributeName: $attrName, AttributeValue: $attrVal"}
}
$peReader.Dispose()
I'm mostly happy with it, the only issue I'd still like to sort out is that I'm getting some unhandled characters in the string output. I'll try to get rid of them.

creating GUI forms without variables

I am trying to find a way to create a form in PowerShell without using any variables unless they are temporarily or virtually assigned. I want to be able to run a command similar to this:
(New-Object System.Windows.Forms.Form).ShowDialog()
where I can enter in a code into an event that is triggered once the form is created. That event will then be responsible for creating all the objects and other events inside the form. Once the form is launched, I will not need any variables accept for the ones that are virtually assigned within the events.
This to avoid using too much system resources from assigning and endless amount of variables for each object in the form. The script that I am currently working on in PowerShell is very possibly going to be really big, and even if it is not a very large script, efficiency and clean code is always the key to writing a good program or script.
add-type -ass System.Windows.Forms
$x = (New-Object System.Windows.Forms.Form)
$x.Text = 'Message Box'
$x.Size = '300,150'
$x.Font = $x.Font.Name + ',12'
$x.Controls.Add((New-Object System.Windows.Forms.Label))
$x.Controls[-1].Size = $x.Size
$x.Controls[-1].Text = 'Here is a message for you'
$x.ShowDialog()
Remove-Variable x
It is very possible to access these objects still with the exact same kind of access when you define each object with a variable. It cost me many hours of research and just simply attempting random commands to find out how to do this. Here is all the commands you may need to relearn if you are interested in my solution:
# create item in form:
$x.Controls.Add((New-Object System.Windows.Forms.Button))
# access the last created item in the form:
$x.Controls[-1]
# change it's name to identify it easier
$x.Controls[-1].Name = 'button1'
# access the item by it's new name:
$x.Controls['button']
# delete the item by it's name:
$x.Controls.Remove($x.Controls['button1'])
If your familiar with form creation in PowerShell then this should all make sense to you and you should be familiar with how the rest of it works. Also, another note to make for those who are interested in what I am trying to do is that any of these commands can be done within an event by replacing $x with $this. If it is inside an event of an object inside the "controls" section of the form, then you would use $this.parent.
This is exactly what I mean by having the ability to create a form with virtually no variables. The only problem I am having with this is that I am unsure how to assign an event and call the method ShowDialog() at the same time.
I found an a very interesting solution to this, however I am not sure to what the limits are to this solution and it dose not quite work in the way that I would personally like it to.
file.ps1:
add-type -ass System.Windows.Forms
$x = (New-Object System.Windows.Forms.Form)
$x.Text = 'Message Box'
$x.Size = '300,150'
$x.Font = $x.Font.Name + ',12'
$x.Controls.Add((New-Object System.Windows.Forms.Label))
$x.Controls[-1].Size = $x.Size
$x.Controls[-1].Text = 'Here is a message for you'
$x
remove-variable x
command to execute the code:
(iex(Get-Content 'file.ps1'|out-string)).ShowDialog()

Add-CMDeploymentType warning

Im using poweshell to automate creating applications in SCCM 2012, distributing content and deploying them once created.
I have the following code:
New-CMApplication -name $appname -Manufacturer $manu -SoftwareVersion $ver
Which works fine.
However.
Add-CMDeploymentType -MsiInstaller -applicationName $appname -AutoIdentifyFromIntallationFile -InstallationFileLocation $content -ForceForUnknownPublisher $true
Gives me a warning " Failed to get install type's technology and it won't create the deployment type.
As far as I can tell from other sites, I shouldn't need to specifiy and more than that. I've experimented with adding more options on the end but none seem to make a difference.
There isnt very much out there about this error - has anyone got past it before?
I doubt that you'll get Add-CMDeploymentType to do much useful -- at least not in its current form. I once tried and gave up when I noticed that it is missing basic, essential parameters. The documentation does not even mention, for example, detection of any sort. There's not much point in using ConfigMgr Applications without detection, and there's not much point in scripting the creation of DeploymentTypes if you still have to define the detection criteria via the UI.
You might get the odd msi file configured using the Add-CMDeploymentType's AddDeploymentTypeByMsiInstallerX parameter set. In that case you'd be relying on ConfigMgr to work out the detection logic automagically. That may work, but I have had significant issues with the MSI Deployment. I'd avoid that if possible.
I'm not hopeful that the Add-CMDeploymentType will ever become usable. The object tree that underlies Applications is necessarily complex and really doesn't lend itself to interaction using simple PowerShell cmdlets. To completely configure an Application there are hundreds of properties on dozens of objects that you need to access. Many of those objects are contained in dictionary- and array-like collections that have their own special semantics for accessing them. You just can't simplify that into a handful of PowerShell cmdlets.
I'm using the types in the following .dlls to interface with ConfigMgr:
AdminUI.WqlQueryEngine.dll
Microsoft.ConfigurationManagement.ApplicationManagement.dll
Microsoft.ConfigurationManagement.ApplicationManagement.MsiInstaller.dll
As far as I can tell, that is the same API the admin console uses, so you can expect full functionality. You cannot make the same claims about the PowerShell cmdlets. So far I have found a way to access everything I've tried through that API using PowerShell. The basics of accessing that API is documented in the ConfigMgr SDK. It's fairly straightforward to figure out how those objects work using reflection and some experimentation.
When you retrieve an Application using Get-CMApplication you actually get the full object tree with it. The SDMPackageXML object contains a serialized copy of the Application, DeploymentTypes, detection, installers, etc. [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString() works to deserialize that object so you can inspect it for yourself.
I actually gave up on this - As you say - Add-CMDeployment type is completely useless. There was nothing online anywhere that described this error, or how to use it properly - an Application with no detection is pointless and adding it manually later defeats the point in trying to automate it.
PowerShell centre had some examples of how it could be used but neither of these worked...
This link was pretty useful and has everything I needed to create an application without powershell.
link
a bit long but the code was...
Public Sub create_SCCM_application(appname As String, version As String, content_location As String, filename As String, manu As String)
Try
Dim appID As ObjectId = New ObjectId("ScopeId_devscope", "Application_" & Guid.NewGuid().ToString())
Dim app As New Application(appID)
app.Title = appname
app.Version = "1.0"
app.Publisher = manu
app.SoftwareVersion = version
app.AutoInstall = True
Dim dinfo As New AppDisplayInfo
dinfo.Title = appname
dinfo.Version = version
dinfo.Language = Globalization.CultureInfo.CurrentCulture.Name
app.DisplayInfo.Add(dinfo)
Dim dtID As ObjectId = New ObjectId("ScopeId_devscope", "DeploymentType_" & Guid.NewGuid().ToString())
Dim dt As New DeploymentType(dtID, MsiInstallerTechnology.TechnologyId)
dt.Title = appname & " Deployment type"
dt.Version = "1.0"
app.DeploymentTypes.Add(dt)
Dim installer As MsiInstaller = dt.Installer
Dim fakecode As Guid = Guid.NewGuid
installer.ProductCode = "{" & fakecode.ToString & "}"
installer.InstallCommandLine = "msiexec /i " & filename
installer.UninstallCommandLine = "msiexec /x " & filename
installer.AllowUninstall = True
installer.ExecuteTime = 30
installer.MaxExecuteTime = 30
installer.ExecutionContext = ExecutionContext.System
installer.UserInteractionMode = UserInteractionMode.Hidden
installer.DetectionMethod = DetectionMethod.ProductCode
installer.ProductVersion = version
Dim appcont As Content = New Content
installer.Contents.Add(appcont)
appcont.Location = content_location
Dim msifile As New ContentFile
msifile.Name = "_temp.msi"
appcont.Files.Add(msifile)
Dim appxml As XDocument = SccmSerializer.Serialize(app, True)
Dim appinstance As ManagementObject = Nothing
Dim path As ManagementPath = New ManagementPath("SMS_Application")
Dim options As New ObjectGetOptions
Dim appClass As ManagementClass = Nothing
Dim scope As ManagementScope = New ManagementScope("\\devserver\root\Sms\Site_devsitecode")
appClass = New ManagementClass(scope, path, options)
appinstance = appClass.CreateInstance()
appinstance.Properties("SDMPackageXML").Value = appxml
appinstance.Put()
Catch x As System.Exception
Console.WriteLine(x.Message)
End Try
End Sub
Your question regarding the deployment type behaviour is also wierd - We have that same product and it works from an MSI deployment type.

Read missing Word template from Powershell

I have a series of Word documents which link to templates which no longer exist. This is causing problems for users trying to open them. I can get a list of the documents, loop through each one, and set the tempalte to null. While this will solve the problem, I can't determine what the template was before I changed it.
In cases where the template is not available on open, Word will replace the attached template with Normal.dot(x). However, the template I'm trying find is located in the document's Tempaltes dialog. Both AttachedTempalte() and get_AttachedTemplate().Name return Normal.dot when I know the document in question has a different template listed in the Templates dialog in word.
I can access this in VBA, and it's fustrating to not be able to do this in PS. Can anyone see where I'm messing up?
$word = new-object -comobject "Word.Application"
$doc = $word.Documents.Open({document path})
$word.Dialogs(Microsoft.Office.Interop.Word.WdWordDialog.wdDialogToolsTemplates).Template()
Returns:
Missing ')' in method call.
At :line:1 char:15
+ $word.Dialogs(M <<<< icrosoft.Office.Interop.Word.WdWordDialog.wdDialogToolsTemplates).Template()
Working VBA:
Dim doc as Word.Document
Dim strTemplate as String
Set doc = Documents.Open(Filename:=filename, Visible:=False)
doc.Activate
strTemplate = Word.Dialogs(wdDialogsToolsTemplates).Template
After which I can see the template name and path I should see in strTemplate.
I checked the ps script and adding $doc.Activate doesn't seem to help. I also noticed that the interop and VBA do not use the same wdDialog. PS uses wdDialogToolsTemplates and VBA using wdDialogsToolsTemplates. I checked the assembly in PS with the following
[Reflection.Assembly]::LoadWithPartialName("Microsoft.Office.Interop.Word") | out-null
[Enum]::GetNames("Microsoft.Office.Interop.Word.WdDialogs")
and confirmed the correct option is wdDialogToolsTemplates.
In powershell you must use the [] brackets to specify types and then the :: to specify the type member, so your 3rd line of powershell code should look like this:
$word.Dialogs([Microsoft.Office.Interop.Word.WdWordDialog]::wdDialogToolsTemplates).Template()
See these blog posts about powershell enums: Jeffrey Snover or Richard Siddaway.
I am trying to do something similar and the main aim is not to store any code inside a Word document.
PowerShell
I made a tiny bit of progress with the PowerShell route but I can't find a way to extract the path from the dialog boxes.
$objWord = New-Object -ComObject "Word.Application"
$objWord.Visible = $True
$objDoc = $objWord.Documents.Open("C:\path\to\document.doc")
$objToolsTemplates = $objWord.Dialogs.Item([Microsoft.Office.Interop.Word.WdWordDialog]::wdDialogToolsTemplates)
$objDocStats = $objWord.Dialogs.Item([Microsoft.Office.Interop.Word.WdWordDialog]::wdDialogDocumentStatistics)
Now $objToolsTemplates.Show() and $objToolsTemplates.Show() both cause the GUI to display dialogs containing the path to the original (unavailable) template but I can't find any way to extract that information programatically. According to MSDN WdWordDialog Enumeration documentation both of the dialogs should have a Template property.
VBScript
In the end I had to go with VBS instead. The following code will give you the path of the original (unavailable) template. It's still a script at least.
Option Explicit
Const wdDialogToolsTemplates = 87
Const wdDoNotSaveChanges = 0
Dim objWordApp
Dim objDoc
Dim dlgTemplate
Set objWordApp = CreateObject("Word.Application")
objWordApp.Visible = False
Set objDoc = objWordApp.Documents.Open("C:\path\to\document.doc")
Set dlgTemplate = objWordApp.Dialogs(wdDialogToolsTemplates)
Wscript.Echo dlgTemplate.Template
objDoc.Close(wdDoNotSaveChanges)
objWordApp.Quit

Windows Automation with Matlab

Hello I'm trying to control FastHenry2 via Windows automation with Matlab.
I'm new to that topic. If I understood everything right I have to open it:
ax=actxserver('FastHenry2.Document');
and than run the function I want with the handle?
ax.Run('coils1.inp')
but that doesn't work. Matlab can't find Run and uses run instead, which is some build in Function not related at all with the problem. Also all other Function this UI should support don't work either. For Example FastHenry2.IsRunning or FastHenry2.GetInductance. So I guess I do something wrong with the whole UI handling. FastHenry provides an example script using VBS which I attached bellow and since it works fine FastHenry should be installed right on computer. I'm thankful for every hint I can get.
so long
eactor
The following VBS example works fine
Dim FastHenry2
pathPos = InstrRev(Wscript.ScriptFullName, Wscript.ScriptName)
path = left(Wscript.ScriptFullName, pathPos-1)
Set FastHenry2 = CreateObject("FastHenry2.Document")
couldRun = FastHenry2.Run("""" + path + "coils1.inp""")
Do While FastHenry2.IsRunning = True
Wscript.Sleep 500
Loop
inductance = FastHenry2.GetInductance()
WScript.Echo "Coils1 mutual inductance is " + CStr(inductance(0, 0, 1))
FastHenry2.Quit
Set FastHenry2 = Nothing
You might need to use an alternate syntax. Instead of
FastHenry2.Run('coils1.inp')
try this.
invoke(FastHenry2, 'Run', 'coils2.inp')
I've had to do this for some methods on the Excel.Application ActiveX control. I'm not clear why the first syntax doesn't work in some cases.