From 52a2b4a034a3e0863efa1c96d799b93fccb4ebd7 Mon Sep 17 00:00:00 2001 From: bitsadmin Date: Thu, 23 May 2019 17:56:48 +0200 Subject: [PATCH 1/6] Added supported for more recent versions of the .NET Framework, 2 new Cmdlets and some other improvements --- CONTRIBUTING.md | 2 +- README.md | 7 +- Source/NoPowerShell.sln | 12 +- .../Archive/CompressArchiveCommand.cs | 102 ++++++++++++++++ .../Commands/Archive/ExpandArchiveCommand.cs | 92 ++++++++++++++ .../DnsClient/ResolveDnsNameCommand.cs | 2 +- .../Commands/Management/GetPSDriveCommand.cs | 112 ++++++++++++++++++ .../Commands/Management/GetProcessCommand.cs | 4 +- .../Commands/Management/RemoveItemCommand.cs | 2 +- .../NetTCPIP/TestNetConnectionCommand.cs | 97 ++++++++++++--- .../Commands/Utility/SelectObjectCommand.cs | 28 +++++ .../NoPowerShell/HelperClasses/Exceptions.cs | 4 + Source/NoPowerShell/NoPowerShell.csproj | 18 +++ Source/NoPowerShell/Program.cs | 2 +- 14 files changed, 453 insertions(+), 31 deletions(-) create mode 100644 Source/NoPowerShell/Commands/Archive/CompressArchiveCommand.cs create mode 100644 Source/NoPowerShell/Commands/Archive/ExpandArchiveCommand.cs create mode 100644 Source/NoPowerShell/Commands/Management/GetPSDriveCommand.cs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf8f47c..48fdfa3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ Execute the following steps to implement your own cmdlet: 1. Download Visual Studio Community from https://visualstudio.microsoft.com/downloads/ * In the installer select the **.NET desktop development** component. * From this component no optional modules are required for developing NoPowerShell modules. -2. Make sure to have the .NET 2 framework installed: OptionalFeatures -> '.NET Framework 3.5 (includes .NET 2.0 and 3.0)'. +2. Make sure to have the .NET 2 framework installed: `OptionalFeatures.exe` -> '.NET Framework 3.5 (includes .NET 2.0 and 3.0)'. 3. Clone this repository and create a copy of the **TemplateCommand.cs** file. * In case you are implementing a native PowerShell command, place it in folder the corresponding to the _Source_ attribute when executing in PowerShell: `Get-Command My-Commandlet`. * Moreover, use the name of the _Source_ attribute in the command's namespace. diff --git a/README.md b/README.md index d680af9..527d112 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,9 @@ When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe | Show all network interfaces | `Get-NetIPAddress -All` | | | Show the IP routing table | `Get-NetRoute` | | | Send 2 ICMP requests to IP address 1.1.1.1 with half a second of timeout | `Test-NetConnection -Count 2 -Timeout 500 1.1.1.1` | | +| Perform ping with maximum TTL specified | `ping -TTL 32 1.1.1.1` | | | Perform a traceroute with a timeout of 1 second and a maximum of 20 hops | `Test-NetConnection -TraceRoute -Timeout 1000 -Hops 20 google.com` | | +| Check for open port | `tnc bitsadm.in -Port 80` | | | List network shares on the local machine that are exposed to the network | `Get-SmbMapping` | | | Format output as a list | `Get-LocalUser \| fl` | | | Format output as a list showing only specific attributes | `Get-LocalUser \| fl Name,Description` | | @@ -81,7 +83,10 @@ When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe | List local shares | `Get-WmiObject -Namespace ROOT\CIMV2 -Query "Select * From Win32_Share Where Name LIKE '%$'"` | Alternative: `gwmi -Class Win32_Share -Filter "Name LIKE '%$'"` | | Show network interfaces | `Get-NetIPAddress` | Alternatives: `ipconfig`, `ifconfig` | | Show computer information | `Get-ComputerInfo` | Alternative: `systeminfo` | -| List installed hotfixes | `Get-HotFix` | The output of this cmdlet together with the output of the `Get-SystemInfo` cmdlet can be provided to [WES-NG](https://github.com/bitsadmin/wesng/) to determine missing patches | +| List installed hotfixes | `Get-HotFix` | The output of this cmdlet together with the output of the `Get-ComputerInfo` cmdlet can be provided to [WES-NG](https://github.com/bitsadmin/wesng/) to determine missing patches | +| List local drives | `Get-PSDrive` | | +| Compress folder to zip | `Compress-Archive -Path C:\MyFolder -DestinationPath C:\MyFolder.zip` | | +| Extract zip | `Expand-Archive -Path C:\MyArchive.zip -DestinationPath C:\Extracted` | Alternative: `unzip C:\MyArchive.zip` | ## Install in Cobalt Strike 1. Copy both NoPowerShell.exe and NoPowerShell.cna to the **scripts** subfolder of Cobalt Strike diff --git a/Source/NoPowerShell.sln b/Source/NoPowerShell.sln index 20c406d..ddb25af 100644 --- a/Source/NoPowerShell.sln +++ b/Source/NoPowerShell.sln @@ -21,26 +21,22 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug DLL|Any CPU.ActiveCfg = Debug DLL|Any CPU - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug DLL|Any CPU.Build.0 = Debug DLL|Any CPU + {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug DLL|Any CPU.ActiveCfg = Debug DLL|x86 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug DLL|x64.ActiveCfg = Debug DLL|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug DLL|x64.Build.0 = Debug DLL|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug DLL|x86.ActiveCfg = Debug DLL|x86 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug DLL|x86.Build.0 = Debug DLL|x86 - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug|Any CPU.ActiveCfg = Debug|x86 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug|x64.ActiveCfg = Debug|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug|x64.Build.0 = Debug|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug|x86.ActiveCfg = Debug|x86 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Debug|x86.Build.0 = Debug|x86 - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release DLL|Any CPU.ActiveCfg = Release DLL|Any CPU - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release DLL|Any CPU.Build.0 = Release DLL|Any CPU + {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release DLL|Any CPU.ActiveCfg = Release DLL|x86 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release DLL|x64.ActiveCfg = Release DLL|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release DLL|x64.Build.0 = Release DLL|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release DLL|x86.ActiveCfg = Release DLL|x86 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release DLL|x86.Build.0 = Release DLL|x86 - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release|Any CPU.ActiveCfg = Release|Any CPU - {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release|Any CPU.Build.0 = Release|Any CPU + {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release|Any CPU.ActiveCfg = Release|x86 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release|x64.ActiveCfg = Release|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release|x64.Build.0 = Release|x64 {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release|x86.ActiveCfg = Release|x86 diff --git a/Source/NoPowerShell/Commands/Archive/CompressArchiveCommand.cs b/Source/NoPowerShell/Commands/Archive/CompressArchiveCommand.cs new file mode 100644 index 0000000..7f87573 --- /dev/null +++ b/Source/NoPowerShell/Commands/Archive/CompressArchiveCommand.cs @@ -0,0 +1,102 @@ +#if MAJOR1 || MAJOR2 || MAJOR3 || (MAJOR4 && MINOR0) +#warning Compress-Archive requires at least .NET 4.5 +#else +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; +using System; +using System.IO.Compression; + +/* +Author: @bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands.Archive +{ + public class CompressArchiveCommand : PSCommand + { + public CompressArchiveCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain cmdlet parameters + string path = _arguments.Get("Path").Value; + string destinationPath = _arguments.Get("DestinationPath").Value; + string compressionLevel = _arguments.Get("CompressionLevel").Value; + CompressionLevel cl = CompressionLevel.Optimal; + + // Determine compression level + switch(compressionLevel.ToLowerInvariant()) + { + case "optimal": + cl = CompressionLevel.Optimal; + break; + case "nocompression": + cl = CompressionLevel.NoCompression; + break; + case "fastest": + cl = CompressionLevel.Fastest; + break; + default: + throw new ArgumentException(string.Format("Unknown compression level: {0}. Possible options: Optimal, NoCompression, Fastest.", compressionLevel)); + } + + // Compress + ZipFile.CreateFromDirectory(path, destinationPath, cl, false); + + // Return resulting filename + _results.Add( + new ResultRecord() + { + { "Path", destinationPath } + } + ); + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get + { + return new CaseInsensitiveList() + { + "Compress-Archive", + "zip" // Unofficial + }; + } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Path"), + new StringArgument("DestinationPath"), + new StringArgument("CompressionLevel", "Optimal") + }; + } + } + + public static new string Synopsis + { + get { return "Creates an archive, or zipped file, from specified files and folders."; } + } + + public static new ExampleEntries Examples + { + get + { + return new ExampleEntries() + { + new ExampleEntry("Compress folder to zip", "Compress-Archive -Path C:\\MyFolder -DestinationPath C:\\MyFolder.zip"), + }; + } + } + } +} +#endif \ No newline at end of file diff --git a/Source/NoPowerShell/Commands/Archive/ExpandArchiveCommand.cs b/Source/NoPowerShell/Commands/Archive/ExpandArchiveCommand.cs new file mode 100644 index 0000000..da10fcf --- /dev/null +++ b/Source/NoPowerShell/Commands/Archive/ExpandArchiveCommand.cs @@ -0,0 +1,92 @@ +#if MAJOR1 || MAJOR2 || MAJOR3 || (MAJOR4 && MINOR0) +#warning Expand-Archive requires at least .NET 4.5 +#else +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; +using System.IO; +using System.IO.Compression; + +/* +Author: @bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands.Archive +{ + public class ExpandArchiveCommand : PSCommand + { + public ExpandArchiveCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain cmdlet parameters + string path = _arguments.Get("Path").Value; + string destinationPath = _arguments.Get("DestinationPath").Value; + + // Validate path + if (!File.Exists(path)) + throw new ItemNotFoundException(path); + + // Determine destination path if not specified + if (string.IsNullOrEmpty(destinationPath)) + destinationPath = Path.GetFullPath(Path.Combine(".\\", Path.GetFileNameWithoutExtension(path))); + + // Extract + ZipFile.ExtractToDirectory(path, destinationPath); + + // Return resulting filename + _results.Add( + new ResultRecord() + { + { "Path", destinationPath } + } + ); + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get + { + return new CaseInsensitiveList() + { + "Expand-Archive", + "unzip" // Unofficial + }; + } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Path"), + new StringArgument("DestinationPath") + }; + } + } + + public static new string Synopsis + { + get { return "Extracts files from a specified archive (zipped) file."; } + } + + public static new ExampleEntries Examples + { + get + { + return new ExampleEntries() + { + new ExampleEntry("Extract zip", "Expand-Archive -Path C:\\MyArchive.zip -DestinationPath C:\\Extracted"), + new ExampleEntry("Extract zip current directory", "unzip C:\\MyArchive.zip"), + }; + } + } + } +} +#endif \ No newline at end of file diff --git a/Source/NoPowerShell/Commands/DnsClient/ResolveDnsNameCommand.cs b/Source/NoPowerShell/Commands/DnsClient/ResolveDnsNameCommand.cs index f75986c..27d1ea8 100644 --- a/Source/NoPowerShell/Commands/DnsClient/ResolveDnsNameCommand.cs +++ b/Source/NoPowerShell/Commands/DnsClient/ResolveDnsNameCommand.cs @@ -149,7 +149,7 @@ public static CommandResult GetRecords(string domain, string type) { string[] types = new string[RecordTypes.Count]; RecordTypes.Keys.CopyTo(types, 0); - throw new NoPowerShellException(string.Format("Invalid type specified. Specify one of the following: {0}.", string.Join(",", types))); + throw new NoPowerShellException("Invalid type specified. Specify one of the following: {0}.", string.Join(",", types)); } DnsRecordTypes queryType = (DnsRecordTypes)foundType; diff --git a/Source/NoPowerShell/Commands/Management/GetPSDriveCommand.cs b/Source/NoPowerShell/Commands/Management/GetPSDriveCommand.cs new file mode 100644 index 0000000..cc997f4 --- /dev/null +++ b/Source/NoPowerShell/Commands/Management/GetPSDriveCommand.cs @@ -0,0 +1,112 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; +using System; +using System.Collections.Generic; +using System.IO; + +/* +Author: @bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetPSDriveCommand : PSCommand + { + public GetPSDriveCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Enumerate harddrives + DriveInfo[] drives = DriveInfo.GetDrives(); + foreach (DriveInfo drive in drives) + { + _results.Add( + new ResultRecord() + { + { "Name", drive.Name[0].ToString() }, + { "Used (GB)", drive.IsReady ? ((drive.TotalSize - drive.TotalFreeSpace) / Math.Pow(1024,3)).ToString("0.00") : "" }, + { "Free (GB)", drive.IsReady ? (drive.TotalFreeSpace / Math.Pow(1024,3)).ToString("0.00") : "" }, + { "Provider", "FileSystem" }, + { "Root", drive.Name } + } + ); + } + + // Complement with environment and registry + _results.AddRange( + new List() + { + new ResultRecord() + { + { "Name", "Env" }, + { "Used (GB)", "" }, + { "Free (GB)", "" }, + { "Provider", "Environment" }, + { "Root", "" } + }, + new ResultRecord() + { + { "Name", "HKCU" }, + { "Used (GB)", "" }, + { "Free (GB)", "" }, + { "Provider", "Registry" }, + { "Root", "HKEY_CURRENT_USER" } + }, + new ResultRecord() + { + { "Name", "HKLM" }, + { "Used (GB)", "" }, + { "Free (GB)", "" }, + { "Provider", "Registry" }, + { "Root", "HKEY_LOCAL_MACHINE" } + } + } + ); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-PSDrive", "gdr" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + }; + } + } + + public static new string Synopsis + { + get { return "Gets drives in the current session."; } + } + + public static new ExampleEntries Examples + { + get + { + return new ExampleEntries() + { + new ExampleEntry + ( + "List drives", + new List() + { + "Get-PSDrive", + "gdr" + } + ) + }; + } + } + } +} diff --git a/Source/NoPowerShell/Commands/Management/GetProcessCommand.cs b/Source/NoPowerShell/Commands/Management/GetProcessCommand.cs index 231eb37..01e273c 100644 --- a/Source/NoPowerShell/Commands/Management/GetProcessCommand.cs +++ b/Source/NoPowerShell/Commands/Management/GetProcessCommand.cs @@ -55,9 +55,7 @@ public override CommandResult Execute(CommandResult pipeIn) if(_results.Count == 0) { - throw new NoPowerShellException( - string.Format("Cannot find a process with the name \"{0}\". Verify the process name and call the cmdlet again.", allNameArguments) - ); + throw new NoPowerShellException("Cannot find a process with the name \"{0}\". Verify the process name and call the cmdlet again.", allNameArguments); } return _results; diff --git a/Source/NoPowerShell/Commands/Management/RemoveItemCommand.cs b/Source/NoPowerShell/Commands/Management/RemoveItemCommand.cs index a9e0bd6..651d63e 100644 --- a/Source/NoPowerShell/Commands/Management/RemoveItemCommand.cs +++ b/Source/NoPowerShell/Commands/Management/RemoveItemCommand.cs @@ -25,7 +25,7 @@ public override CommandResult Execute(CommandResult pipeIn) // Determine if provided path is a file or a directory if (!File.Exists(path)) - throw new NoPowerShellException(string.Format("Cannot find path '{0}' because it does not exist.", path)); + throw new NoPowerShellException("Cannot find path '{0}' because it does not exist.", path); FileAttributes attr = File.GetAttributes(path); diff --git a/Source/NoPowerShell/Commands/NetTCPIP/TestNetConnectionCommand.cs b/Source/NoPowerShell/Commands/NetTCPIP/TestNetConnectionCommand.cs index 3a1cb80..c350471 100644 --- a/Source/NoPowerShell/Commands/NetTCPIP/TestNetConnectionCommand.cs +++ b/Source/NoPowerShell/Commands/NetTCPIP/TestNetConnectionCommand.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.Sockets; using System.Net.NetworkInformation; +using System.Text; /* Author: @bitsadmin @@ -15,6 +17,8 @@ namespace NoPowerShell.Commands.NetTCPIP { public class TestNetConnectionCommand : PSCommand { + private static readonly byte[] alphabet = Encoding.ASCII.GetBytes("abcdefghijklmnopqrstuvwabcdefghi"); + public TestNetConnectionCommand(string[] userArguments) : base(userArguments, SupportedArguments) { } @@ -27,22 +31,86 @@ public override CommandResult Execute(CommandResult pipeIn) int timeout = _arguments.Get("Timeout").Value; string computerName = _arguments.Get("ComputerName").Value; int hops = _arguments.Get("Hops").Value; + int ttl = _arguments.Get("TTL").Value; + int port = _arguments.Get("Port").Value; + + // Resolve host + string ip = ResolveIP(computerName); - // Traceroute or Ping - if (performTraceroute) - _results = PerformTraceroute(computerName, count, timeout, hops); + // ICMP + if(port == -1) + { + // Traceroute + if (performTraceroute) + _results = PerformTraceroute(ip, computerName, count, timeout, hops); + // Ping + else + _results = PerformPing(ip, computerName, count, timeout, ttl); + } + // TCP port else - _results = PerformPing(computerName, count, timeout); + { + _results = PerformPortTest(ip, computerName, port); + } + return _results; } - private static CommandResult PerformPing(string computerName, int count, int timeout) + private static string ResolveIP(string computerName) + { + IPHostEntry ip = null; + try + { + ip = Dns.GetHostEntry(computerName); + } + catch(SocketException) + { + throw new NoPowerShellException("Name resolution of {0} failed", computerName); + } + + return ip.AddressList[0].ToString(); + } + + private static CommandResult PerformPortTest(string ip, string computerName, int port) + { + CommandResult results = new CommandResult(1); + + if (port < 1 || port > 65535) + throw new NoPowerShellException("Cannot validate argument on parameter 'Port'. The {0} argument is greater than the maximum allowed range of 65535. Supply an argument that is less than or equal to 65535 and then try the command again.", port); + + bool connected = false; + try + { + TcpClient client = new TcpClient(ip, port); + string address = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString(); + string localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address.ToString(); + connected = true; + + results.Add(new ResultRecord() + { + { "ComputerName", computerName }, + { "RemoteAddress", address}, + { "RemotePort", port.ToString() }, + //{ "InterfaceAlias", string.Empty }, // TODO + { "SourceAddress", localAddress }, + { "TcpTestSucceeded", connected ? "True" : "False" } + }); + } + catch(SocketException) + { + throw new NoPowerShellException("TCP connect to ({0} : {1}) failed", ip, port); + } + + return results; + } + + private static CommandResult PerformPing(string ip, string computerName, int count, int timeout, int ttl) { CommandResult results = new CommandResult(count); Ping ping = new Ping(); - PingOptions options = new PingOptions(64, false); + PingOptions options = new PingOptions(ttl, false); bool succeeded = false; @@ -52,7 +120,7 @@ private static CommandResult PerformPing(string computerName, int count, int tim try { - reply = ping.Send(computerName, timeout); + reply = ping.Send(ip, timeout, alphabet, options); } catch(PingException) { @@ -91,15 +159,10 @@ private static CommandResult PerformPing(string computerName, int count, int tim return results; } - private static CommandResult PerformTraceroute(string computerName, int count, int timeout, int maxHops) + private static CommandResult PerformTraceroute(string ip, string computerName, int count, int timeout, int maxHops) { CommandResult results = new CommandResult(count); - // Fill buffer with a-z - byte[] buffer = new byte[32]; - for (int i = 0; i < buffer.Length; i++) - buffer[i] = Convert.ToByte(0x61 + (i % 26)); - Ping ping = new Ping(); List IPs = new List(maxHops); @@ -115,7 +178,7 @@ private static CommandResult PerformTraceroute(string computerName, int count, i try { - reply = ping.Send(computerName, timeout, buffer, options); + reply = ping.Send(ip, timeout, alphabet, options); } catch(PingException) { @@ -176,7 +239,9 @@ private static CommandResult PerformTraceroute(string computerName, int count, i new StringArgument("ComputerName"), new IntegerArgument("Count", 1, true), // Unofficial parameter new IntegerArgument("Timeout", 5000, true), // Unofficial parameter - new IntegerArgument("Hops", 30, true) + new IntegerArgument("TTL", 128, true), // Unofficial parameter + new IntegerArgument("Hops", 30, true), + new IntegerArgument("Port", -1) }; } } @@ -194,6 +259,8 @@ private static CommandResult PerformTraceroute(string computerName, int count, i { new ExampleEntry("Send 2 ICMP requests to IP address 1.1.1.1 with half a second of timeout", "Test-NetConnection -Count 2 -Timeout 500 1.1.1.1"), new ExampleEntry("Perform a traceroute with a timeout of 1 second and a maximum of 20 hops", "Test-NetConnection -TraceRoute -Timeout 1000 -Hops 20 google.com"), + new ExampleEntry("Perform ping with maximum TTL specified", "ping -TTL 32 1.1.1.1"), + new ExampleEntry("Check for open port", "tnc bitsadm.in -Port 80") }; } } diff --git a/Source/NoPowerShell/Commands/Utility/SelectObjectCommand.cs b/Source/NoPowerShell/Commands/Utility/SelectObjectCommand.cs index 698a6e5..48c1272 100644 --- a/Source/NoPowerShell/Commands/Utility/SelectObjectCommand.cs +++ b/Source/NoPowerShell/Commands/Utility/SelectObjectCommand.cs @@ -1,5 +1,6 @@ using NoPowerShell.Arguments; using NoPowerShell.HelperClasses; +using System.Collections.Generic; /* Author: @bitsadmin @@ -26,6 +27,33 @@ public override CommandResult Execute(CommandResult pipeIn) bool wildcardSelect = attributes[0] == "*"; bool firstSet = first > 0; + // Simply return pipeIn if empty + if (pipeIn.Count == 0) + return pipeIn; + + // Ignore casing + List correctAttributes = new List(); + foreach(string inputAttr in attributes) + { + bool found = false; + + // Locate case-insensitive match + foreach (string key in pipeIn[0].Keys) + { + if(inputAttr.Equals(key, System.StringComparison.InvariantCultureIgnoreCase)) + { + correctAttributes.Add(key); + found = true; + break; + } + } + + // Add original non-existent column if no matching column found + if (!found) + correctAttributes.Add(inputAttr); + } + attributes = correctAttributes.ToArray(); + int counter = 0; foreach (ResultRecord result in pipeIn) { diff --git a/Source/NoPowerShell/HelperClasses/Exceptions.cs b/Source/NoPowerShell/HelperClasses/Exceptions.cs index 5693d89..c5d3097 100644 --- a/Source/NoPowerShell/HelperClasses/Exceptions.cs +++ b/Source/NoPowerShell/HelperClasses/Exceptions.cs @@ -13,6 +13,10 @@ public NoPowerShellException() : base() public NoPowerShellException(string message) : base(message) { } + + public NoPowerShellException(string messageFormat, params object[] args) : base(string.Format(messageFormat, args)) + { + } } class CommandNotFoundException : NoPowerShellException diff --git a/Source/NoPowerShell/NoPowerShell.csproj b/Source/NoPowerShell/NoPowerShell.csproj index 94601a6..7f7a79a 100644 --- a/Source/NoPowerShell/NoPowerShell.csproj +++ b/Source/NoPowerShell/NoPowerShell.csproj @@ -34,10 +34,24 @@ Library $(DefineConstants);DLLBUILD + false + + x86 + + + x64 + + + $([System.String]::new('$(TargetFrameworkVersion.Substring(1).Replace(".",""))').PadRight(3,'0')) + $(DotNetVersion.SubString(0,1)) + $(DotNetVersion.SubString(1,1)) + $(DotNetVersion.SubString(2,1)) + $(DefineConstants);NET$(DotNetVersion);MAJOR$(MajorVersion);MINOR$(MinorVersion);SUB$(SubVersion) + ..\packages\UnmanagedExports.1.2.7\lib\net\RGiesecke.DllExport.Metadata.dll @@ -48,6 +62,7 @@ + @@ -60,6 +75,8 @@ + + @@ -73,6 +90,7 @@ + diff --git a/Source/NoPowerShell/Program.cs b/Source/NoPowerShell/Program.cs index 3c99cee..c89b6c8 100644 --- a/Source/NoPowerShell/Program.cs +++ b/Source/NoPowerShell/Program.cs @@ -15,7 +15,7 @@ namespace NoPowerShell { partial class Program { - public static readonly string VERSION = "1.22"; + public static readonly string VERSION = "1.23"; public static readonly string WEBSITE = "https://github.com/bitsadmin"; #if !DLLBUILD private static readonly string USAGE = "Usage: NoPowerShell.exe [Command] [Parameters] | [Command2] [Parameters2] etc.\r\n"; From 45bdde9dad31d5a4d091ef294804214cee42ab84 Mon Sep 17 00:00:00 2001 From: bitsadmin Date: Thu, 25 Jul 2019 15:43:59 +0200 Subject: [PATCH 2/6] 1 new cmdlet and various documentation improvements --- CONTRIBUTING.md | 3 +- README.md | 50 +++-- .../NetTCPIP/GetNetNeighborCommand.cs | 188 ++++++++++++++++++ Source/NoPowerShell/NoPowerShell.csproj | 2 + Source/NoPowerShell/ProgramDll.cs | 13 ++ 5 files changed, 240 insertions(+), 16 deletions(-) create mode 100644 Source/NoPowerShell/Commands/NetTCPIP/GetNetNeighborCommand.cs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48fdfa3..0d78113 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,9 @@ # Contributing Add your own cmdlets by submitting a pull request. -## Requirement +## Aim - Maintain .NET 2.0 compatibility in order to support the broadest range of operating systems +- In case for whatever reason .NET 2.0 compatibility is not possible, add the `#if` preprocessor directive to the class specifying the unsupported .NET versions (for examples check the `*-Archive` cmdlets) ## Instructions Use the TemplateCommand.cs file in the Commands folder to construct new cmdlets. The TemplateCommand cmdlet is hidden from the list of available cmdlets, but can be called in order to understand its workings. This command looks as follows: `Get-TemplateCommand [-MyFlag] -MyInteger [Int32] -MyString [Value]` and is also accessible via alias `gtc`. diff --git a/README.md b/README.md index 527d112..dfedc80 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # NoPowerShell NoPowerShell is a tool implemented in C# which supports executing PowerShell-like commands while remaining invisible to any PowerShell logging mechanisms. This .NET Framework 2 compatible binary can be loaded in Cobalt Strike to execute commands in-memory. No `System.Management.Automation.dll` is used; only native .NET libraries. An alternative usecase for NoPowerShell is to launch it as a DLL via rundll32.exe: `rundll32 NoPowerShell.dll,main`. -Moreover, this project makes it easy for everyone to extend its functionality using only a few lines of C# code. +This project makes it easy for everyone to extend its functionality using only a few lines of C# code. For more info, see [CONTRIBUTING.md](https://github.com/bitsadmin/nopowershell/blob/master/CONTRIBUTING.md). -Latest binaries available from the [Releases](https://github.com/bitsadmin/nopowershell/releases) page. +Latest binaries available from the [Releases](https://github.com/bitsadmin/nopowershell/releases) page. Bleeding edge code available in the [DEV](https://github.com/bitsadmin/nopowershell/tree/dev) branch. # Screenshots ## Running in Cobalt Strike @@ -13,13 +13,17 @@ Latest binaries available from the [Releases](https://github.com/bitsadmin/nopow ## Rundll32 version ![NoPowerShellDll via rundll32](https://raw.githubusercontent.com/bitsadmin/nopowershell/master/Pictures/NoPowerShellDll.png "NoPowerShellDll via rundll32") -# Usage -## Note -When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe character (`|`) with respectively a caret (`^`) or a backtick (`` ` ``), i.e.: - -- cmd.exe: `ls ^| select Name` -- PowerShell: ```ls `| select Name``` +# Why NoPowerShell +NoPowerShell is developed to be used with the `execute-assembly` command of Cobalt Strike. +Reasons to use NoPowerShell: +- Executes pretty stealthy +- Powerful functionality +- Provides the cmdlets you are already familiar with in PowerShell, so no need to learn yet another tool +- If you are not yet very familiar with PowerShell, the cmd.exe aliases are available as well (i.e. `ping` instead of `Test-NetConnection`) +- In case via `powerpick` or `powershell` cmdlets are not available, they _are_ available in `nps` (i.e. cmdlets from the ActiveDirectory module) +- Easily extensible with only a few lines of C# +# Usage ## Examples | Action | Command | Notes | | - | - | - | @@ -63,6 +67,7 @@ When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe | Recursively delete a folder | `Remove-Item -Recurse C:\Tmp\MyTools\` | | | Show all network interfaces | `Get-NetIPAddress -All` | | | Show the IP routing table | `Get-NetRoute` | | +| List ARP cache | `Get-NetNeighbor` | Alternative: `arp` | | Send 2 ICMP requests to IP address 1.1.1.1 with half a second of timeout | `Test-NetConnection -Count 2 -Timeout 500 1.1.1.1` | | | Perform ping with maximum TTL specified | `ping -TTL 32 1.1.1.1` | | | Perform a traceroute with a timeout of 1 second and a maximum of 20 hops | `Test-NetConnection -TraceRoute -Timeout 1000 -Hops 20 google.com` | | @@ -85,12 +90,12 @@ When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe | Show computer information | `Get-ComputerInfo` | Alternative: `systeminfo` | | List installed hotfixes | `Get-HotFix` | The output of this cmdlet together with the output of the `Get-ComputerInfo` cmdlet can be provided to [WES-NG](https://github.com/bitsadmin/wesng/) to determine missing patches | | List local drives | `Get-PSDrive` | | -| Compress folder to zip | `Compress-Archive -Path C:\MyFolder -DestinationPath C:\MyFolder.zip` | | -| Extract zip | `Expand-Archive -Path C:\MyArchive.zip -DestinationPath C:\Extracted` | Alternative: `unzip C:\MyArchive.zip` | +| Compress folder to zip | `Compress-Archive -Path C:\MyFolder -DestinationPath C:\MyFolder.zip` | Only available when compiled against .NET 4.5+ | +| Extract zip | `Expand-Archive -Path C:\MyArchive.zip -DestinationPath C:\Extracted` | Alternative: `unzip C:\MyArchive.zip`. Only available when compiled against .NET 4.5+ | ## Install in Cobalt Strike -1. Copy both NoPowerShell.exe and NoPowerShell.cna to the **scripts** subfolder of Cobalt Strike -2. Launch Cobalt Strike and load the .cna script in the Script Manager +1. Copy both `NoPowerShell.exe` and `NoPowerShell.cna` to the **scripts** subfolder of Cobalt Strike +2. Launch Cobalt Strike and load the `NoPowerShell.cna` script in the Script Manager 3. Interact with a beacon and execute commands using the `nps` command ## Launch via rundll32 @@ -99,6 +104,12 @@ When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe 3. The shortcut will now look like `rundll32 C:\Path\to\NoPowerShell.dll,main` 4. Double click the shortcut +## Note +When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe character (`|`) with respectively a caret (`^`) or a backtick (`` ` ``), i.e.: + +- cmd.exe: `ls ^| select Name` +- PowerShell: ```ls `| select Name``` + # Known issues - Pipeline characters need to surrounded by spaces - TLS 1.1+ is not supported by .NET Framework 2, so any site enforcing it will result in a connection error @@ -110,9 +121,6 @@ When using NoPowerShell from cmd.exe or PowerShell, you need to escape the pipe - Add support for ArrayArgument parameter - Add support for .NET code in commandline, i.e.: `[System.Security.Principal.WindowsIdentity]::GetCurrent().Name` -# Contributing -See [CONTRIBUTING.md](https://github.com/bitsadmin/nopowershell/blob/master/CONTRIBUTING.md). - # Requested NoPowerShell cmdlets | Cmdlet | Description | | - | - | @@ -138,6 +146,8 @@ Authors of additional NoPowerShell cmdlets are added to the table below. Moreove | Get-ADGroupMember | ActiveDirectory | | | Get-ADUser | ActiveDirectory | | | Get-ADComputer | ActiveDirectory | | +| Compress-Archive | Archive | Requires .NET 4.5+ | +| Expand-Archive | Archive | Requires .NET 4.5+ | | Get-Whoami | Additional | whoami.exe /ALL is not implemented yet | | Get-RemoteSmbShare | Additional | | | Get-Command | Core | | @@ -153,6 +163,7 @@ Authors of additional NoPowerShell cmdlets are added to the table below. Moreove | Get-ItemProperty | Management | | | Get-Process | Management | | | Stop-Process | Management | | +| Get-PSDrive | Management | | | Get-WmiObject | Management | | | Get-HotFix| Management | | | Invoke-WmiMethod | Management | Quick & dirty implementation | @@ -161,6 +172,7 @@ Authors of additional NoPowerShell cmdlets are added to the table below. Moreove | Get-NetIPAddress | NetTCPIP | | | Get-NetRoute | NetTCPIP | | | Test-NetConnection | NetTCPIP | | +| Get-NetNeighbor | NetTCPIP | No support for IPv6 yet | | Get-SmbMapping | SmbShare | | | Format-List | Utility | | | Format-Table | Utility | | @@ -168,4 +180,12 @@ Authors of additional NoPowerShell cmdlets are added to the table below. Moreove | Measure-Object | Utility | | Select-Object | Utility | +# Acknowledgements +Various NoPowerShell cmdlets include code created by other developers. +| Who | Website | Notes | +| Contributors of pinvoke.net | https://www.pinvoke.net/ | Various cmdlets use snippets from pinvoke | +| Michael Conrad | https://github.com/MichaCo/ | Parts of the Resolve-Dns cmdlet are based on the code of the DnsClient.Net project | +| Rex Logan | https://stackoverflow.com/a/1148861 | Most code of the Get-NetNeighbor cmdlet originates from his StackOverflow post | + + **Authored by Arris Huijgen ([@bitsadmin](https://twitter.com/bitsadmin/) - https://github.com/bitsadmin/)** \ No newline at end of file diff --git a/Source/NoPowerShell/Commands/NetTCPIP/GetNetNeighborCommand.cs b/Source/NoPowerShell/Commands/NetTCPIP/GetNetNeighborCommand.cs new file mode 100644 index 0000000..1b54633 --- /dev/null +++ b/Source/NoPowerShell/Commands/NetTCPIP/GetNetNeighborCommand.cs @@ -0,0 +1,188 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; +using System; +using System.Collections.Generic; +using System.Net; +using System.Runtime.InteropServices; + +/* +Author: @bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +// Most of this source originates from https://stackoverflow.com/a/1148861 by Rex Logan + +namespace NoPowerShell.Commands +{ + public class GetNetNeighborCommand : PSCommand + { + // Define the MIB_IPNETROW structure. + [StructLayout(LayoutKind.Sequential)] + struct MIB_IPNETROW + { + [MarshalAs(UnmanagedType.U4)] + public int dwIndex; + [MarshalAs(UnmanagedType.U4)] + public int dwPhysAddrLen; + [MarshalAs(UnmanagedType.U1)] + public byte mac0; + [MarshalAs(UnmanagedType.U1)] + public byte mac1; + [MarshalAs(UnmanagedType.U1)] + public byte mac2; + [MarshalAs(UnmanagedType.U1)] + public byte mac3; + [MarshalAs(UnmanagedType.U1)] + public byte mac4; + [MarshalAs(UnmanagedType.U1)] + public byte mac5; + [MarshalAs(UnmanagedType.U1)] + public byte mac6; + [MarshalAs(UnmanagedType.U1)] + public byte mac7; + [MarshalAs(UnmanagedType.U4)] + public int dwAddr; + [MarshalAs(UnmanagedType.U4)] + public int dwType; + } + + // Enum source: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rrasm/1dab4bfb-a5dc-4763-afdb-5ea211f42ce1 + // Updated value 2 from Invalid to Unreachable (as does PowerShell) + enum ARPType { Other = 1, Unreachable = 2, Dynamic = 3, Static = 4 } + + // Declare the GetIpNetTable function. + [DllImport("IpHlpApi.dll")] + [return: MarshalAs(UnmanagedType.U4)] + static extern int GetIpNetTable(IntPtr pIpNetTable, [MarshalAs(UnmanagedType.U4)]ref int pdwSize, bool bOrder); + + [DllImport("IpHlpApi.dll", SetLastError = true, CharSet = CharSet.Auto)] + internal static extern int FreeMibTable(IntPtr plpNetTable); + + // The insufficient buffer error. + const int ERROR_INSUFFICIENT_BUFFER = 122; + + public GetNetNeighborCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // The number of bytes needed. + int bytesNeeded = 0; + + // The result from the API call. + int result = GetIpNetTable(IntPtr.Zero, ref bytesNeeded, false); + + // Call the function, expecting an insufficient buffer. + if (result != ERROR_INSUFFICIENT_BUFFER) + throw new NoPowerShellException("Error in execution: {0}", result); + + // Allocate the memory, do it in a try/finally block, to ensure that it is released. + IntPtr buffer = IntPtr.Zero; + + // Try/finally. + try + { + // Allocate the memory. + buffer = Marshal.AllocCoTaskMem(bytesNeeded); + + // Make the call again. If it did not succeed, then raise an error. + result = GetIpNetTable(buffer, ref bytesNeeded, false); + + // If the result is not 0 (no error), then throw an exception. + if (result != 0) + throw new NoPowerShellException("Error in execution: {0}", result); + + // Now we have the buffer, we have to marshal it. We can read the first 4 bytes to get the length of the buffer. + int entries = Marshal.ReadInt32(buffer); + + // Increment the memory pointer by the size of the int. + IntPtr currentBuffer = new IntPtr(buffer.ToInt64() + Marshal.SizeOf(typeof(int))); + + // Allocate an array of entries. + MIB_IPNETROW[] table = new MIB_IPNETROW[entries]; + + // Cycle through the entries. + for (int index = 0; index < entries; index++) + { + // Call PtrToStructure, getting the structure information. + table[index] = (MIB_IPNETROW)Marshal.PtrToStructure( + new IntPtr(currentBuffer.ToInt64() + (index * Marshal.SizeOf(typeof(MIB_IPNETROW)))), typeof(MIB_IPNETROW) + ); + } + + // Iterate over records and add them to cmdlet result + for (int index = 0; index < entries; index++) + { + MIB_IPNETROW row = table[index]; + IPAddress ip = new IPAddress(BitConverter.GetBytes(row.dwAddr)); + ARPType type = (ARPType)row.dwType; + + _results.Add( + new ResultRecord() + { + { "ifIndex", row.dwIndex.ToString() }, + { "IPAddress", ip.ToString() }, + { "LinkLayerAddress", string.Format("{0:X2}-{1:X2}-{2:X2}-{3:X2}-{4:X2}-{5:X2}", row.mac0, row.mac1, row.mac2, row.mac3, row.mac4, row.mac5) }, + { "State", type.ToString() } + } + ); + } + } + finally + { + // Release the memory. + FreeMibTable(buffer); + } + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get + { + return new CaseInsensitiveList() + { + "Get-NetNeighbor", + "arp" // Unofficial + }; + } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + }; + } + } + + public static new string Synopsis + { + get { return "Gets neighbor cache entries."; } + } + + public static new ExampleEntries Examples + { + get + { + return new ExampleEntries() + { + new ExampleEntry + ( + "List ARP table entries", + new List() + { + "Get-NetNeighbor", + "arp" + } + ) + }; + } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell.csproj b/Source/NoPowerShell/NoPowerShell.csproj index 7f7a79a..6ea32cb 100644 --- a/Source/NoPowerShell/NoPowerShell.csproj +++ b/Source/NoPowerShell/NoPowerShell.csproj @@ -61,6 +61,7 @@ + @@ -94,6 +95,7 @@ + diff --git a/Source/NoPowerShell/ProgramDll.cs b/Source/NoPowerShell/ProgramDll.cs index 7398908..bf362b2 100644 --- a/Source/NoPowerShell/ProgramDll.cs +++ b/Source/NoPowerShell/ProgramDll.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; +using System.Windows.Forms; /* Author: @bitsadmin @@ -20,8 +21,20 @@ partial class Program [DllExport("main", CallingConvention = CallingConvention.StdCall)] public static void Main() { +#if DEBUG ProgramDll nps = new ProgramDll(); nps.NoPowerShellExecute(); +#else + try + { + ProgramDll nps = new ProgramDll(); + nps.NoPowerShellExecute(); + } + catch(Exception e) + { + MessageBox.Show(e.Message, "NoPowerShell", MessageBoxButtons.OK, MessageBoxIcon.Error); + } +#endif } public static void DllMain(string[] args) From 10228e6d630e1588fc7e2394c9ddc9366c1d37e5 Mon Sep 17 00:00:00 2001 From: bitsadmin Date: Thu, 25 Jul 2019 22:08:26 +0200 Subject: [PATCH 3/6] For NoPowerShell DLL changed from RGiesecke's UnmanagedExports to 3F's DllExport --- README.md | 1 + Source/DllExport.bat | 469 ++++++++++++++++++++++++ Source/NoPowerShell/NoPowerShell.csproj | 31 +- Source/NoPowerShell/ProgramDll.cs | 1 - Source/NoPowerShell/packages.config | 4 - 5 files changed, 497 insertions(+), 9 deletions(-) create mode 100644 Source/DllExport.bat delete mode 100644 Source/NoPowerShell/packages.config diff --git a/README.md b/README.md index dfedc80..a8e4ff6 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ Various NoPowerShell cmdlets include code created by other developers. | Contributors of pinvoke.net | https://www.pinvoke.net/ | Various cmdlets use snippets from pinvoke | | Michael Conrad | https://github.com/MichaCo/ | Parts of the Resolve-Dns cmdlet are based on the code of the DnsClient.Net project | | Rex Logan | https://stackoverflow.com/a/1148861 | Most code of the Get-NetNeighbor cmdlet originates from his StackOverflow post | +| PowerShell developers | https://github.com/PowerShell/ | Code of NoPowerShell DLL is largely based on the code handling the console input of PowerShell | **Authored by Arris Huijgen ([@bitsadmin](https://twitter.com/bitsadmin/) - https://github.com/bitsadmin/)** \ No newline at end of file diff --git a/Source/DllExport.bat b/Source/DllExport.bat new file mode 100644 index 0000000..42452f6 --- /dev/null +++ b/Source/DllExport.bat @@ -0,0 +1,469 @@ +@echo off +:: Copyright (c) 2016-2019 Denis Kuzmin [ entry.reg@gmail.com ] +:: https://github.com/3F/DllExport +if "%~1"=="/?" goto bl +set "aa=%~dpnx0" +set ab=%* +set ac=%* +if defined ab ( +if defined __p_call ( +set ac=%ac:^^=^% +) else ( +set ab=%ab:^=^^% +) +) +set wMgrArgs=%ac% +set ad=%ab:!=^!% +setlocal enableDelayedExpansion +set "ae=^" +set "ad=!ad:%%=%%%%!" +set "ad=!ad:&=%%ae%%&!" +set "af=1.6.4" +set "wAction=Configure" +set "ag=DllExport" +set "ah=tools/net.r_eg.DllExport.Wizard.targets" +set "ai=packages" +set "aj=https://www.nuget.org/api/v2/package/" +set "ak=build_info.txt" +set "al=!aa!" +set "wRootPath=!cd!" +set "am=" +set "an=" +set "ao=" +set "ap=" +set "aq=" +set "ar=" +set "as=" +set "at=" +set "au=" +set /a av=0 +if not defined ab ( +if defined wAction goto bm +goto bl +) +call :bn bg !ad! bh +goto bo +:bl +echo. +@echo DllExport - v1.6.4.15293 [ f864a40 ] +@echo Copyright (c) 2009-2015 Robert Giesecke +@echo Copyright (c) 2016-2019 Denis Kuzmin [ entry.reg@gmail.com ] GitHub/3F +echo. +echo Licensed under the MIT license +@echo https://github.com/3F/DllExport +echo. +echo Based on hMSBuild and includes GetNuTool core: https://github.com/3F +echo. +@echo. +@echo Usage: DllExport [args to DllExport] [args to GetNuTool core] +echo ------ +echo. +echo Arguments: +echo ---------- +echo -action {type} - Specified action for Wizard. Where {type}: +echo * Configure - To configure DllExport for specific projects. +echo * Update - To update pkg reference for already configured projects. +echo * Restore - To restore configured DllExport. +echo * Export - To export configured projects data. +echo * Recover - To re-configure projects via predefined/exported data. +echo * Unset - To unset all data from specified projects. +echo * Upgrade - Aggregates an Update action with additions for upgrading. +echo. +echo -sln-dir {path} - Path to directory with .sln files to be processed. +echo -sln-file {path} - Optional predefined .sln file to be processed. +echo -metalib {path} - Relative path from PkgPath to DllExport meta library. +echo -dxp-target {path} - Relative path to entrypoint wrapper of the main core. +echo -dxp-version {num} - Specific version of DllExport. Where {num}: +echo * Versions: 1.6.0 ... +echo * Keywords: +echo `actual` - Unspecified local/latest remote version; +echo ( Only if you know what you are doing ) +echo. +echo -msb {path} - Full path to specific msbuild. +echo -packages {path} - A common directory for packages. +echo -server {url} - Url for searching remote packages. +echo -proxy {cfg} - To use proxy. The format: [usr[:pwd]@]host[:port] +echo -pkg-link {uri} - Direct link to package from the source via specified URI. +echo -force - Aggressive behavior, e.g. like removing pkg when updating. +echo -mgr-up - Updates this manager to version from '-dxp-version'. +echo -wz-target {path} - Relative path to entrypoint wrapper of the main wizard. +echo -pe-exp-list {module} - To list all available exports from PE32/PE32+ module. +echo -eng - Try to use english language for all build messages. +echo -GetNuTool {args} - Access to GetNuTool core. https://github.com/3F/GetNuTool +echo -debug - To show additional information. +echo -version - Displays version for which (together with) it was compiled. +echo -build-info - Displays actual build information from selected DllExport. +echo -help - Displays this help. Aliases: -help -h +echo. +echo ------ +echo Flags: +echo ------ +echo __p_call - To use the call-type logic when invoking %~nx0 +echo. +echo -------- +echo Samples: +echo -------- +echo DllExport -action Configure +echo DllExport -action Restore -sln-file "Conari.sln" +echo DllExport -proxy guest:1234@10.0.2.15:7428 -action Configure +echo DllExport -action Configure -force -pkg-link http://host/v1.6.1.nupkg +echo. +echo DllExport -build-info +echo DllExport -debug -restore -sln-dir ..\ +echo DllExport -mgr-up -dxp-version 1.6.1 +echo DllExport -action Upgrade -dxp-version 1.6.1 +echo. +echo DllExport -GetNuTool -unpack +echo DllExport -GetNuTool /p:ngpackages="Conari;regXwild" +echo DllExport -pe-exp-list bin\Debug\regXwild.dll +goto bp +:bo +set /a aw=0 +:bq +set ax=!bg[%aw%]! +if [!ax!]==[-help] ( goto bl ) else if [!ax!]==[-h] ( goto bl ) else if [!ax!]==[-?] ( goto bl ) +if [!ax!]==[-debug] ( +set am=1 +goto br +) else if [!ax!]==[-action] ( set /a "aw+=1" & call :bs bg[!aw!] v +set wAction=!v! +for %%g in (Restore, Configure, Update, Export, Recover, Unset, Upgrade, Default) do ( +if "!v!"=="%%g" goto br +) +echo Unknown -action !v! +exit/B 1 +) else if [!ax!]==[-sln-dir] ( set /a "aw+=1" & call :bs bg[!aw!] v +set wSlnDir=!v! +goto br +) else if [!ax!]==[-sln-file] ( set /a "aw+=1" & call :bs bg[!aw!] v +set wSlnFile=!v! +goto br +) else if [!ax!]==[-metalib] ( set /a "aw+=1" & call :bs bg[!aw!] v +set wMetaLib=!v! +goto br +) else if [!ax!]==[-dxp-target] ( set /a "aw+=1" & call :bs bg[!aw!] v +set wDxpTarget=!v! +goto br +) else if [!ax!]==[-dxp-version] ( set /a "aw+=1" & call :bs bg[!aw!] v +set af=!v! +goto br +) else if [!ax!]==[-msb] ( set /a "aw+=1" & call :bs bg[!aw!] v +set ao=!v! +goto br +) else if [!ax!]==[-packages] ( set /a "aw+=1" & call :bs bg[!aw!] v +set ai=!v! +goto br +) else if [!ax!]==[-server] ( set /a "aw+=1" & call :bs bg[!aw!] v +set aj=!v! +goto br +) else if [!ax!]==[-proxy] ( set /a "aw+=1" & call :bs bg[!aw!] v +set at=!v! +goto br +) else if [!ax!]==[-pkg-link] ( set /a "aw+=1" & call :bs bg[!aw!] v +set ap=!v! +goto br +) else if [!ax!]==[-force] ( +set ar=1 +goto br +) else if [!ax!]==[-mgr-up] ( +set as=1 +goto br +) else if [!ax!]==[-wz-target] ( set /a "aw+=1" & call :bs bg[!aw!] v +set ah=!v! +goto br +) else if [!ax!]==[-pe-exp-list] ( set /a "aw+=1" & call :bs bg[!aw!] v +set aq=!v! +goto br +) else if [!ax!]==[-eng] ( +chcp 437 >nul +goto br +) else if [!ax!]==[-GetNuTool] ( +call :bt "accessing to GetNuTool ..." +for /L %%p IN (0,1,8181) DO ( +if "!ay:~%%p,10!"=="-GetNuTool" ( +set az=!ay:~%%p! +call :bu !az:~10! +set /a av=%ERRORLEVEL% +goto bp +) +) +call :bt "!ax! is corrupted: !ay!" +set /a av=1 +goto bp +) else if [!ax!]==[-version] ( +@echo v1.6.4.15293 [ f864a40 ] +goto bp +) else if [!ax!]==[-build-info] ( +set an=1 +goto br +) else if [!ax!]==[-tests] ( set /a "aw+=1" & call :bs bg[!aw!] v +set au=!v! +goto br +) else ( +echo Incorrect key: !ax! +set /a av=1 +goto bp +) +:br +set /a "aw+=1" & if %aw% LSS !bh! goto bq +:bm +call :bt "dxpName = " ag +call :bt "dxpVersion = " af +call :bt "-sln-dir = " wSlnDir +call :bt "-sln-file = " wSlnFile +call :bt "-metalib = " wMetaLib +call :bt "-dxp-target = " wDxpTarget +call :bt "-wz-target = " ah +if defined af ( +if "!af!"=="actual" ( +set "af=" +) +) +if z%wAction%==zUpgrade ( +call :bt "Upgrade is on" +set as=1 +set ar=1 +) +call :bv ai +set "ai=!ai!\\" +set "a0=!ag!" +set "wPkgPath=!ai!!ag!" +if defined af ( +set "a0=!a0!/!af!" +set "wPkgPath=!wPkgPath!.!af!" +) +if defined ar ( +if exist "!wPkgPath!" ( +call :bt "Removing old version before continue. '-force' key rule. " wPkgPath +rmdir /S/Q "!wPkgPath!" +) +) +set a1="!wPkgPath!\\!ah!" +call :bt "wPkgPath = " wPkgPath +if not exist !a1! ( +if exist "!wPkgPath!" ( +call :bt "Trying to replace obsolete version ... " wPkgPath +rmdir /S/Q "!wPkgPath!" +) +call :bt "-pkg-link = " ap +call :bt "-server = " aj +if defined ap ( +set aj=!ap! +if "!aj::=!"=="!aj!" ( +set aj=!cd!/!aj! +) +if "!wPkgPath::=!"=="!wPkgPath!" ( +set "a2=../" +) +set "a0=:!a2!!wPkgPath!|" +) +if defined ao ( +set a3=-msbuild "!ao!" +) +set a4=!a3! /p:ngserver="!aj!" /p:ngpackages="!a0!" /p:ngpath="!ai!" /p:proxycfg="!at!" +call :bt "GetNuTool call: " a4 +if defined am ( +call :bu !a4! +) else ( +call :bu !a4! >nul +) +) +if defined aq ( +"!wPkgPath!\\tools\\PeViewer.exe" -list -pemodule "!aq!" +set /a av=%ERRORLEVEL% +goto bp +) +if defined an ( +call :bt "buildInfo = " wPkgPath ak +if not exist "!wPkgPath!\\!ak!" ( +echo information about build is not available. +set /a av=2 +goto bp +) +type "!wPkgPath!\\!ak!" +goto bp +) +if not exist !a1! ( +echo Something went wrong. Try to use another keys. +set /a av=2 +goto bp +) +call :bt "wRootPath = " wRootPath +call :bt "wAction = " wAction +call :bt "wMgrArgs = " wMgrArgs +if defined ao ( +call :bt "Use specific MSBuild tools: " ao +set a5="!ao!" +goto bw +) +call :bx bi & set a5="!bi!" +if "!ERRORLEVEL!"=="0" goto bw +echo MSBuild tools was not found. Try with `-msb` key. +set /a av=2 +goto bp +:bw +if not defined a5 ( +echo Something went wrong. Use `-debug` key for details. +set /a av=2 +goto bp +) +if not defined au ( +call :bt "Target: " a5 a1 +!a5! /nologo /v:m /m:4 !a1! +) +:bp +if defined au ( +echo Running Tests ... "!au!" +call :bx bj +"!bj!" /nologo /v:m /m:4 "!au!" +exit/B 0 +) +if defined as ( +(copy /B/Y "!wPkgPath!\\DllExport.bat" "!al!" > nul) && ( echo Manager has been updated. & exit/B !av! ) || ( echo -mgr-up failed. & exit/B %ERRORLEVEL% ) +) +exit/B !av! +:bx +call :bt "Searching from .NET Framework - .NET 4.0, ..." +for %%v in (4.0, 3.5, 2.0) do ( +call :by %%v Y & if defined Y ( +set %1=!Y! +exit/B 0 +) +) +call :bt "msb -netfx: not found" +set "%1=" +exit/B 2 +:by +call :bt "check %1" +for /F "usebackq tokens=2* skip=2" %%a in ( +`reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\%1" /v MSBuildToolsPath 2^> nul` +) do if exist %%b ( +set a6=%%~b +call :bt ":msbfound " a6 +call :bz a6 bk +set %2=!bk! +exit/B 0 +) +set "%2=" +exit/B 0 +:bz +set %2=!%~1!\MSBuild.exe +exit/B 0 +:bt +if defined am ( +set a7=%1 +set a7=!a7:~0,-1! +set a7=!a7:~1! +echo.[%TIME% ] !a7! !%2! !%3! +) +exit/B 0 +:bv +call :b0 %1 +call :b1 %1 +exit/B 0 +:b0 +call :b2 %1 "-=1" +exit/B 0 +:b1 +call :b2 %1 "+=1" +exit/B 0 +:b2 +set a8=z!%1!z +if "%~2"=="-=1" (set "a9=1") else (set "a9=") +if defined a9 ( +set /a "i=-2" +) else ( +set /a "i=1" +) +:b3 +if "!a8:~%i%,1!"==" " ( +set /a "i%~2" +goto b3 +) +if defined a9 set /a "i+=1" +if defined a9 ( +set "%1=!a8:~1,%i%!" +) else ( +set "%1=!a8:~%i%,-1!" +) +exit/B 0 +:bn +set "a_=%~1" +set /a aw=-1 +:b4 +set /a aw+=1 +set %a_%[!aw!]=%~2 +shift & if not "%~3"=="" goto b4 +set /a aw-=1 +set %1=!aw! +exit/B 0 +:bs +set %2=!%1! +exit/B 0 +:bu +setlocal disableDelayedExpansion +@echo off +:: GetNuTool - Executable version +:: Copyright (c) 2015-2018 Denis Kuzmin [ entry.reg@gmail.com ] +:: https://github.com/3F/GetNuTool +set ba=gnt.core +set bb="%temp%\%random%%random%%ba%" +if "%~1"=="-unpack" goto b5 +set bc=%* +if defined __p_call if defined bc set bc=%bc:^^=^% +set bd=%__p_msb% +if defined bd goto b6 +if "%~1"=="-msbuild" goto b7 +for %%v in (4.0, 14.0, 12.0, 3.5, 2.0) do ( +for /F "usebackq tokens=2* skip=2" %%a in ( +`reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\%%v" /v MSBuildToolsPath 2^> nul` +) do if exist %%b ( +set bd="%%~b\MSBuild.exe" +goto b6 +) +) +echo MSBuild was not found. Try -msbuild "fullpath" args 1>&2 +exit/B 2 +:b7 +shift +set bd=%1 +shift +set be=%bc:!= #__b_ECL## % +setlocal enableDelayedExpansion +set be=!be:%%=%%%%! +:b8 +for /F "tokens=1* delims==" %%a in ("!be!") do ( +if "%%~b"=="" ( +call :b9 !be! +exit/B %ERRORLEVEL% +) +set be=%%a #__b_EQ## %%b +) +goto b8 +:b9 +shift & shift +set "bc=" +:b_ +set bc=!bc! %1 +shift & if not "%~2"=="" goto b_ +set bc=!bc: #__b_EQ## ==! +setlocal disableDelayedExpansion +set bc=%bc: #__b_ECL## =!% +:b6 +call :ca +%bd% %bb% /nologo /p:wpath="%~dp0/" /v:m /m:4 %bc% +set "bd=" +set bf=%ERRORLEVEL% +del /Q/F %bb% +exit/B %bf% +:b5 +set bb="%~dp0\%ba%" +echo Generating minified version in %bb% ... +:ca +%bb% +set a=PropertyGroup&set b=Condition&set c=ngpackages&set d=Target&set e=DependsOnTargets&set f=TaskCoreDllPath&set g=MSBuildToolsPath&set h=UsingTask&set i=CodeTaskFactory&set j=ParameterGroup&set k=Reference&set l=Include&set m=System&set n=Using&set o=Namespace&set p=IsNullOrEmpty&set q=return&set r=string&set s=delegate&set t=foreach&set u=WriteLine&set v=Combine&set w=Console.WriteLine&set x=Directory&set y=GetNuTool&set z=StringComparison&set _=EXT_NUSPEC +^