简体   繁体   中英

Running a complete powershell script from within C#

I'm currently building a small application in C# to assist with configuration of our companies computers after staging them. This to automate some tasks that we currently do manually for no real reason.

One of the steps I'd like to automate is to pin items to the taskbar in Windows 10. We currently use the following powershell script, which can perform this task:

    Param($Target)

$KeyPath1  = "HKCU:\SOFTWARE\Classes"
$KeyPath2  = "*"
$KeyPath3  = "shell"
$KeyPath4  = "{:}"
$ValueName = "ExplorerCommandHandler"
$ValueData = (Get-ItemProperty("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\" +
  "Explorer\CommandStore\shell\Windows.taskbarpin")).ExplorerCommandHandler

$Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)
$Key3 = $Key2.CreateSubKey($KeyPath3, $true)
$Key4 = $Key3.CreateSubKey($KeyPath4, $true)
$Key4.SetValue($ValueName, $ValueData)

$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
$Item.InvokeVerb("{:}")

$Key3.DeleteSubKey($KeyPath4)
if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0) {
    $Key2.DeleteSubKey($KeyPath3)
}

I've attempted to import this powershellscript in C#, but can't seem to call it from within a function. When I try, windows throws various errors, and the items are not pinned to taskbar.

I've done the full idiot path and tried to enter it using strings and then using Powershell.Create() with those strings to replicate what the script does, to no avail.

Does anyone see some way to translate what this script does to C#? Or know of an alternative method to pin items to taskbar in windows 10 ?

Note: I'd like for the App to remain standalone - so I can't make calls to a network file. If possible, I'd also like to have this as a portable exe (using Costura.Fody NuGet Package), So I'd like to avoid just putting the script in the same folder and running it from C#, or use an installer.

edit: found a workable, albeit rather idiotic solution. I created a string[] containing the script mentioned above, then used streamwriter to create a .ps1 with the entire script, and executed that with Powershell.create. I then used another powershell.create to delete the file. Clunky, but workeable.

Pintotaskbar.cs:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation;
using System.Text;


    namespace ConfiguratorLibrary
    {
        public class PinToTaskBar
        {

            private string itempath;

            public string pinitem
            {
                get { return itempath; }
                set { itempath = value; }
            }


            public string Pin()
            {
                string[] psscript = { "$Target = ("+"\u0022" + $"{itempath}" + "\u0022" + ")" , "\n\r" , "$KeyPath1  = " + "\"HKCU:\\SOFTWARE\\Classes\"", "$KeyPath2 = " +"\"*\"", "$KeyPath3  = " + "\"shell\"",
                "$KeyPath4  = " + "\"{:}\"", "$ValueName = " + "\"ExplorerCommandHandler\"", "$ValueData = (Get-ItemProperty(" + "\"HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\\"" + " + " + "\"Explorer\\CommandStore\\shell\\Windows.taskbarpin\"" + ")).ExplorerCommandHandler",
                "\r\n", "$Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)", "$Key3 = $Key2.CreateSubKey($KeyPath3, $true)","$Key4 = $Key3.CreateSubKey($KeyPath4, $true)","$Key4.SetValue($ValueName, $ValueData)", "\r\n",
                "$Shell = New-Object -ComObject " + "\"Shell.Application\"", "$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)","$Item = $Folder.ParseName((Get-Item $Target).Name)",
                "$Item.InvokeVerb("+ "\"{:}\"" + ")", "\r\n", "$Key3.DeleteSubKey($KeyPath4)", "if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0) {","$Key2.DeleteSubKey($KeyPath3)",
                "}"};

                StringBuilder psout = new StringBuilder();

                using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"C:\pinitemstotaskbar.ps1"))
                {
                    foreach (string line in psscript)
                    {
                        file.WriteLine(line);
                    }
                }

                using (PowerShell ps = PowerShell.Create())
                {
                    ps.AddScript(@"cd C:\");
                    ps.AddScript("Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force ");
                    ps.AddScript(@".\pinitemstotaskbar.ps1");
                    ps.AddScript("rm pinitemstotaskbar.ps1");

                    Collection<PSObject> scriptoutput = ps.Invoke();

                    foreach (PSObject psoutp in scriptoutput)
                    {
                        if(psoutp != null)
                        {
                            psout.Append(psoutp);
                        }
                        else
                        {
                            psout.Append("Error");
                        }
                    }

                    String output = psout.ToString();
                    return output;

                }
            }

        }
    }

Forgot to mention: This Method takes the full path to the .exe of the application you wish to input as string.

Example, inputting string @"C:\\Windows\\system32\\Notepad.exe" as property Pinitem and then running the method would pin notepad to the taskbar. I'm currently using it to pin default user apps to the taskbar such as Outlook, Word and Excel - as well as some company-specific apps.

The below solution is quite different from the PowerShell snippet that you posted.

Overview: The Functionality is present in Class PinToTaskBar And Invoked from Class Program

Note: As mentioned in chat, it hasn't been tested

PS: I will attempt to convert your PS to C# when I get access to VS


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Management.Automation;
using System.Text;


    namespace ConfiguratorLibrary
    {
        public class PinToTaskBar
        {

            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
            internal static extern IntPtr LoadLibrary(string lpLibFileName);

            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
            internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);


            public static bool PinUnpinTaskbar(string filePath, bool pin)
            {
                if (!File.Exists(filePath)) throw new FileNotFoundException(filePath);
                int MAX_PATH = 255;
                var actionIndex = pin ? 5386 : 5387; // 5386 is the DLL index for"Pin to Tas&kbar", ref. win7dll.info/shell32_dll.html
                //uncomment the following line to pin to start instead
                //actionIndex = pin ? 51201 : 51394;
                StringBuilder szPinToStartLocalized = new StringBuilder(MAX_PATH);
                IntPtr hShell32 = LoadLibrary("Shell32.dll");
                LoadString(hShell32, (uint)actionIndex, szPinToStartLocalized, MAX_PATH);
                string localizedVerb = szPinToStartLocalized.ToString();

                string path = Path.GetDirectoryName(filePath);
                string fileName = Path.GetFileName(filePath);

                // create the shell application object
                dynamic shellApplication = Activator.CreateInstance(Type.GetTypeFromProgID("Shell.Application"));
                dynamic directory = shellApplication.NameSpace(path);
                dynamic link = directory.ParseName(fileName);

                dynamic verbs = link.Verbs();
                for (int i = 0; i < verbs.Count(); i++)
                {
                dynamic verb = verbs.Item(i);
                if (verb.Name.Equals(localizedVerb))
                {
                verb.DoIt();
                return true;
                }
                }
                return false;
            }

            static string originalImagePathName;
            static int unicodeSize = IntPtr.Size * 2;

            static void GetPointers(out IntPtr imageOffset, out IntPtr imageBuffer)
            {
                IntPtr pebBaseAddress = GetBasicInformation().PebBaseAddress;
                var processParameters = Marshal.ReadIntPtr(pebBaseAddress, 4 * IntPtr.Size);
                imageOffset = processParameters.Increment(4 * 4 + 5 * IntPtr.Size + unicodeSize + IntPtr.Size + unicodeSize);
                imageBuffer = Marshal.ReadIntPtr(imageOffset, IntPtr.Size);
            }

            internal static void ChangeImagePathName(string newFileName)
            {
            IntPtr imageOffset, imageBuffer;
            GetPointers(out imageOffset, out imageBuffer);

            //Read original data
            var imageLen = Marshal.ReadInt16(imageOffset);
            originalImagePathName = Marshal.PtrToStringUni(imageBuffer, imageLen / 2);

            var newImagePathName = Path.Combine(Path.GetDirectoryName(originalImagePathName), newFileName);
            if (newImagePathName.Length > originalImagePathName.Length) throw new Exception("new ImagePathName cannot be longer than the original one");

            //Write the string, char by char
            var ptr = imageBuffer;
            foreach(var unicodeChar in newImagePathName)
            {
            Marshal.WriteInt16(ptr, unicodeChar);
            ptr = ptr.Increment(2);
            }
            Marshal.WriteInt16(ptr, 0);

            //Write the new length
            Marshal.WriteInt16(imageOffset, (short) (newImagePathName.Length * 2));
            }

            internal static void RestoreImagePathName()
            {
            IntPtr imageOffset, ptr;
            GetPointers(out imageOffset, out ptr);

            foreach (var unicodeChar in originalImagePathName)
            {
            Marshal.WriteInt16(ptr, unicodeChar);
            ptr = ptr.Increment(2);
            }
            Marshal.WriteInt16(ptr, 0);
            Marshal.WriteInt16(imageOffset, (short)(originalImagePathName.Length * 2));
            }

            public static ProcessBasicInformation GetBasicInformation()
            {
            uint status;
            ProcessBasicInformation pbi;
            int retLen;
            var handle = System.Diagnostics.Process.GetCurrentProcess().Handle;
            if ((status = NtQueryInformationProcess(handle, 0,
            out pbi, Marshal.SizeOf(typeof(ProcessBasicInformation)), out retLen)) >= 0xc0000000)
            throw new Exception("Windows exception. status=" + status);
            return pbi;
            }

            [DllImport("ntdll.dll")]
            public static extern uint NtQueryInformationProcess(
            [In] IntPtr ProcessHandle,
            [In] int ProcessInformationClass,
            [Out] out ProcessBasicInformation ProcessInformation,
            [In] int ProcessInformationLength,
            [Out] [Optional] out int ReturnLength
            );

            public static IntPtr Increment(this IntPtr ptr, int value)
            {
            unchecked
            {
            if (IntPtr.Size == sizeof(Int32))
            return new IntPtr(ptr.ToInt32() + value);
            else
            return new IntPtr(ptr.ToInt64() + value);
            }
            }

            [StructLayout(LayoutKind.Sequential)]
            public struct ProcessBasicInformation
            {
                public uint ExitStatus;
                public IntPtr PebBaseAddress;
                public IntPtr AffinityMask;
                public int BasePriority;
                public IntPtr UniqueProcessId;
                public IntPtr InheritedFromUniqueProcessId;
            }


        }

        class TestClass
        {
            static void Main(string[] args)
            {
               var pin = new PinToTaskBar();
               try
                {

                    pin.ChangeImagePathName("explorer.exe");
                    pin.PinUnpinTaskbar(tempFilePath, true);
                }
                Catch(Exception ex)
                {
                    Console.WriteLine(ex.Message + ex.InnerException);
                }
                finally
                {
                    pin.RestoreImagePathName();
                }
            }
        }


    }

EDIT: I found reference to an article that explains functionality used

http://alexweinberger.com/main/pinning-network-program-taskbar-programmatically-windows-10/

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM