Ironman Software Forums
Continue the conversion on the Ironman Software forums. Chat with over 1000 users about PowerShell, PowerShell Universal, and PowerShell Pro Tools.
In this post, we’ll discuss how ConvertTo-PowerShell
works.
CodeConversion
ModuleCodeConversion
ModuleUp until recently, ConvertTo-CSharp
and ConvertTo-PowerShell
were a part of PowerShell Pro Tools. We recently moved them out of PowerShell Pro Tools and into an open-source and free module called CodeConversion
. You can use the cmdlets found in it to perform the exact same operations that were available previously in the paid tool.
For example, you can convert C# code to PowerShell script. This is an example of intent-based conversion.
Install-Module CodeConversion
ConvertTo-PowerShell -Code 'System.Diagnostics.Process.GetProcesses()'
# Outputs: Get-Process
You can also convert standard structures, like loops, from C# to PowerShell. Take this class, for example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeConverter.Test.Languages.CSharp
{
public class Class
{
public void Method()
{
for(int i = 0; i < 100; i++)
{
var t = i;
}
}
}
}
Running the ConvertTo-PowerShell
cmdlet against this would yield the following.
function Method
{
for([int]$i = 0; $i -lt 100; $i++)
{
$t = $i
}
}
You can view more examples and the source for this module here.
Most of the syntax-based conversions are conducted using abstract syntax trees. We run the C# code or PowerShell script through their respective parsers to yield an abstract syntax tree. An abstract syntax tree (or AST) is a tree-based representation of the code. It allows for scripted analysis of static code without actually having to run it. ASTs are useful for tools like linters (like PSScriptAnalyzer) or IntelliSense.
Most languages have common structures that are consistent across syntax trees. For example, you’ll find variables, loops, functions or methods, strings and other constants across languages. We take advantage of this by generating an intermediate AST that is generic. So for example, we take the PowerShell AST for a script, convert it to the generic version of the AST and then use a code generator to take the generic AST and write it out as a C# file.
Vice versa, we can parse a C# code file into the C# AST, convert it to a generic AST and then write out PowerShell script. Because the code generation aspect accepts a generic AST, it’s possible to convert from additional languages. Currently, CodeConversion
only works between C# and PowerShell.
As a simple example of how this works, we can look at how we convert strings. Using the AstVisitor class, we can walk the syntax tree and start to generate a custom one.
For example, the below method visits a StringConstantExpressionAst
from within a PowerShell script. It then generates a generic StringConstant
for our generic AST type.
public override AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)
{
_currentNode = new StringConstant(stringConstantExpressionAst.Value);
return AstVisitAction.SkipChildren;
}
On code generation end, we take the generic StringConstant
and generate some C# code.
public override void VisitStringConstant(StringConstant node)
{
var escapedString = node.Value.Replace("\\", "\\\\");
Append("\"" + escapedString + "\"");
}
Another feature of the CodeConversion
module is converting based on code intent. This means that we have some rules to evaluate what the code is attempting to do and then generate code based on that rather than merely on syntax. Often languages will not have a direct conversion so it makes more sense to convert code this way.
We use another AST Visitor to determine intent. If it finds characteristics of that intent, it will generate an Intent
object in the AST rather than a syntax object.
On the PowerShell side, we provide a mapping file that includes JSON structures to define intent. For example, if we find the Get-Process
cmdlet being used (where there is no matching cmdlet in C#) we generate an intent called GetProcess
and provide a parameter name or ID.
{
"CommandName": "Get-Process",
"Intent": "GetProcess",
"ParameterMappings": {
"Name": "Name",
"Id": "Id"
}
},
On the C# side of things, we have a class for each type of intent. Rather than generating code based on syntax, it uses the intent to generate a completely different AST form. The Get-Process
call is processed by this intent writer. It takes the aguments provided (name and ID) and then generates the proper C# code for the intent.
public class GetProcessIntent : BaseIntentWriter
{
public override string Intent { get { return "GetProcess"; } }
public override string Write(Dictionary<string, string> arguments)
{
if (arguments.ContainsKey("Id"))
{
return $"System.Diagnostics.Process.GetProcessById({arguments["Id"]})";
}
if (arguments.ContainsKey("Name"))
{
return $"System.Diagnostics.Process.GetProcessesByName({arguments["Name"]})";
}
return $"System.Diagnostics.Process.GetProcesses()";
}
}
A code conversion tool like this requires directly mapping syntax from one language to another as well as the intent of any snippet of code. It can be highly accurate but it requires a lot of work to provide that accuracy. Tools such as GitHub Copilot are making headway in creating a system that will be able to adapt easier and eventually provide just as high of accuracy as a tool like this.
We are very much open to issues and pull requests on the CodeConversion
repository. Feel free to extend with new syntax, intents or even new languages.
Continue the conversion on the Ironman Software forums. Chat with over 1000 users about PowerShell, PowerShell Universal, and PowerShell Pro Tools.
Receive once-a-month updates about Ironman Software. You'll learn about our product updates and blogs related to PowerShell.