ExtractAssociatedIcon gives exception in network share using powershell - powershell

I am not able to load Icon when accessing from a network share drive in powershell
$IconPath = $pwd.Path + "\Icons\InstallIcon-F.ico"
$HFForm.icon = [System.Drawing.Icon]::ExtractAssociatedIcon($IconPath)
I am getting this error:
Exception calling "ExtractAssociatedIcon" with "1" argument(s): "The given path's format is not supported."
$HFForm.icon = [System.Drawing.Icon]::ExtractAssociatedIcon <<<< ($IconPath)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException

My testing shows the same as PeterK's. If I use a drive letter its fine, but an unmapped network share is not.
I was able to make it work by mapping the network share to a drive letter. So:
$something = [system.drawing.icon]::extractassociatedicon("c:\windows\system32\notepad.exe")
caused no errors. Neither did:
$something = [system.drawing.icon]::extractassociatedicon(($test.fullname))
with $test.fullname just being a mapped network file path.
It is a good idea to expand out your variables too so we can see what you're actually passing in. Because if I browse to a network file share and expand $pwd.path:
Microsoft.PowerShell.Core\FileSystem::\\user-pc\users
You are almost certainly passing that in. So have a look. I haven't done much with how Powershell formats its display so I'm sure you can find the 'tty' equiv settings, but just in the meantime do this:
$IconPath = $pwd.Path.split('::')[2] + "\Icons\InstallIcon-F.ico"

Convert your icon to a base64 representation of it's binary data, then store it inside the script itself (as text).
Once it's encoded as base64, you can use this command to convert it back into an icon, bypassing the UNC path issue.
$HFForm.icon = [System.Convert]::FromBase64String('
AAABAAkAAAAAAAEAIABbfQEAlgAAAICAAAABACAAKAgBAPF9AQBgYAAAAQAgAKiUAAAZhgIASEgA
#
# There will be hundreds rows depending on your icon's size.
#
AMADAADAAwAA4AcAAPAPAADwDwAA+A8AAPgPAAD4DwAA/B8AAPwfAAA=')
BASE64 snippet.
#Be sure to edit the path to icon.
#
#Hint the result is copied to your clipboard - clip = clip.exe == google it.
$path = "A:\R2-D2-32x32.ico"
[convert]::ToBase64String((get-content $path -encoding byte)) | Clip
#After running the clip command, right click paste between the quotes
$HFForm.icon = [System.Convert]::FromBase64String('')

i have a solution for it.
At first you have to import the SHGetFileInfo Methode and create the structure SHFILEINFO.
$code = #"
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace System
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SHFILEINFO
{
public IntPtr hIcon;
public int iIcon;
public uint dwAttributes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string szDisplayName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
public string szTypeName;
};
public class SHGETFILEINFO
{
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes,ref SHFILEINFO psfi, uint cbSizeFileInfo, uint uFlags);
}
}
"#
Add-Type -TypeDefinition $code
Creates the structure object.
#Path to the exe.
$Path = \\test.de\tes
[System.SHFILEINFO]$FileinfoStruct = New-Object System.SHFILEINFO
Gets the size of the structure
$Size = [System.Runtime.InteropServices.Marshal]::SizeOf($FileinfoStruct)
Gets fills the structure Variable with the File infos.
[System.SHGETFILEINFO]::SHGetFileInfo($Path,0, [ref]$FileinfoStruct,$Size,0x000000100)
Creates the icon.
$ICON = [System.Drawing.Icon]::FromHandle($FileinfoStruct.hIcon)

Related

Changing Windows 10 cursor icon with Powershell without reseting

I'm making a startup script for Windows and have been wanting to change my cursor icons. However, I want to do it without a computer reset and through powershell.
Set-ItemProperty -Path "HKCU:\Control Panel\Cursors\Arrow" -Value
"F:\nutty-squirrels\callmezippy_squirrelUnavailble.cur"
I know that it's possible to change the cursor icon without reset using the GUI, but I can't seem to get it to work using scripts, as regedit does not update the cursor (or, at least, it hasn't through my testing.)
I'm thinking that reseting some process would allow the cursor changes to occur, but I have no idea what that process is. If anyone has any idea, it would be a great help!
The Set-ItemProperty call is missing the -Name argument and you need to call the WinAPI function SystemParametersInfo to notify the system about the settings change:
# Define a C# class for calling WinAPI.
Add-Type -TypeDefinition #'
public class SysParamsInfo {
[System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, uint pvParam, uint fWinIni);
const int SPI_SETCURSORS = 0x0057;
const int SPIF_UPDATEINIFILE = 0x01;
const int SPIF_SENDCHANGE = 0x02;
public static void CursorHasChanged() {
SystemParametersInfo(SPI_SETCURSORS, 0, 0, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
}
'#
# Change the cursor
Set-ItemProperty -Path 'HKCU:\Control Panel\Cursors' -Name 'Arrow' -Value '%SystemRoot%\cursors\aero_arrow_xl.cur'
# Notify the system about settings change by calling the C# code
[SysParamsInfo]::CursorHasChanged()

When attempting to use Import-Clixml on a List<T>, the return value is System.Object

Here is a simplified and complete code example:
class DediInfo {
[string]$ServiceName
[int]$QueryPort
DediInfo([string]$servicename, [int]$qPort){
$this.ServiceName = $servicename
$this.QueryPort = $qPort
}
}
class DediInfos {
hidden static [DediInfos] $_instance = [DediInfos]::new()
static [DediInfos] $Instance = [DediInfos]::GetInstance()
[System.Collections.Generic.List[DediInfo]]$Dedis = [System.Collections.Generic.List[DediInfo]]::New()
hidden static [DediInfos] GetInstance() {
return [DediInfos]::_instance
}
[void]SaveInfo(){
$this.Dedis | Export-Clixml "D:\test.xml"
}
[void]LoadInfo() {
$this.Dedis = Import-Clixml "D:\test.xml"
}
}
$dInfos = [DediInfos]::Instance
$dInfos.Dedis.Add([DediInfo]::New("service1", 15800))
$dInfos.Dedis.Add([DediInfo]::New("service2", 15801))
$dInfos.SaveInfo()
$dInfos.Dedis = [System.Collections.Generic.List[DediInfo]]::New()
$dInfos.LoadInfo()
And here is the Exception I am receiving:
Exception setting "Dedis": "Cannot convert the "System.Object[]" value of type "System.Object[]" to type "System.Collections.Generic.List`1[DediInfo]"."
At D:\test.ps1:25 char:9
+ $this.Dedis = Import-Clixml "D:\test.xml"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting
I've spent the past 4 hours trying different permutations:
adding [Serializable()] to the first class
using the ConvertTo-Xml on the List<> before saving and after reading from file
breaking out the load steps to dump into a new list first - which does load the data from file, but I can't put it back to the singleton's List<> because it loads as System.Object
Tried using saving to json (below) and same error message
$this.Dedis | ConvertTo-Json | Set-Content "D:\test.json"
$this.Dedis = Get-Content -Raw "D:\test.json" | ConvertFrom-Json
Lots of other attempts that I cannot remember any longer.
What I'm wanting is just a way to save the List<> to a file and then load it back in again on my next script's run. The example is very simplified; it saves, clears, and then tries to load again to show what I'm dealing with. Everywhere I've read is that the Import-Clixml should load the XML file back in as the original object, but I'm not having any luck.
Import-XML indeed load back the object, but does not quite respect the type.
Prior export, you had a list of DediInfo objects.
After import, you now have an array of Deserialized.DediInfo objects.
You can see that by doing an import and checking the base type of the first dediinfo object.
$Imported = Import-Clixml "D:\test.xml"
$Imported[0].psobject.TypeNames
It will show
Deserialized.DediInfo
Deserialized.System.Object
Hence, your conversion is failing because you are trying to cast it back to its original type, which is not possible.
You will have to build your list of DediInfo again after importing the XML.
Here's a simple modification to your LoadInfo that will work.
[void]LoadInfo() {
$this.Dedis = Foreach ($i in Import-Clixml "D:\test.xml") {
[DediInfo]::New($i.ServiceName, $i.QueryPort)
}
}

Is there a way to reference a COM dll in PowerShell?

I am trying to automate a task on win10 desktop in a software called AxisVM, which luckily has a good COM interface.
My problem is that my current employer does not allow any programming languages to be installed on their PCs, so I am trying to re-implement a short, but functioning python script in PowerShell.
If I was working in e.g. C# then I could reference the interop dll in my project to be able to work with the constants and data types required by AxisVM.
Is there way to have that in PowerShell too?
I need to pass by reference the following struct when I call a function:
RAccelerationValues = (
double avX // acceleration in local x direction [m/s2]
double avY // acceleration in local y direction [m/s2]
double avZ // acceleration in local z direction [m/s2]
double avXX // angular acceleration about local x direction [rad/s2]
double avYY // angular acceleration about local y direction [rad/s2]
double avZZ // angular acceleration about local z direction [rad/s2]
double avR // resultant acceleration [m/s2]
double avRR // resultant angular acceleration [rad/s2] )
I guess this is in a way similar to that if I was automating Excel and was connecting to Excel by $Excel = New-Object -ComObject Excel.Application, would there be a way to use the xl??? constants in PowerShell that are otherwise defined?
EDIT 1
This is what I could write using a lot of resources from the internet:
Add-Type -Path "C:\AxisVM_X5\Interop.AxisVM.FW4.dll"
Add-Type -Path "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.dll"
$Assem =#(
"C:\AxisVM_X5\Interop.AxisVM.FW4.dll",
"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7.2\System.dll"
)
$Source = #"
using System;
using ax = AxisVM;
namespace ConsoleApp1
{
public class Program
{
public static void Main(string[] args)
{
ax.AxisVMApplication axApp = new ax.AxisVMApplication();
while (((ax.IAxisVMApplication)axApp).Loaded == ax.ELongBoolean.lbFalse)
{
System.Threading.Thread.Sleep(1000);
}
axApp.Visible = ax.ELongBoolean.lbTrue; // AxisVM starts hidden, so make it visible
axApp.CloseOnLastReleased = ax.ELongBoolean.lbFalse; // Do not close AxisVM after this code
axApp.AskCloseOnLastReleased = ax.ELongBoolean.lbTrue; // Ask whether close AxisVM after this code
axApp.AskSaveOnLastReleased = ax.ELongBoolean.lbTrue;
axApp.ApplicationClose = ax.EApplicationClose.acEnableNoWarning;
ax.AxisVMModel axModel = axApp.Models.Item[1];
axModel.LoadFromFile("C:\\Users\\x\\Documents\\_AXIS_TESZT\\tesztgerenda.axs");
ax.AxisVMAcceleration axResAcc = axModel.Results.Acceleration;
axResAcc.AnalysisType = ax.EAnalysisType.atDynamic;
ax.RAccelerationValues resout = new ax.RAccelerationValues();
string lcNAme;
int num_of_timesteps = axModel.Results.TimeStepCount[ax.EAnalysisType.atDynamic, 1];
Console.WriteLine(num_of_timesteps);
axResAcc.LoadCaseId = 2;
axResAcc.TimeStep = 1;
axResAcc.NodalAccelerationByLoadCaseId(5, ref resout, out lcNAme);
Console.WriteLine(lcNAme);
}
}
}
"#
Add-Type -ReferencedAssemblies $Assem -TypeDefinition $Source -Language CSharp
[ConsoleApp1.Program]::Main()
...too bad I will never be able to use it because of the restrictions I have of running power shell scripts

How can I replicate New-SmbGlobalMapping in C# code?

I am writing a service which controls docker containers. I want to have the mounted volume as an Azure share, and thus need to use the SMB Global Mapping. If I use the usual WNetAddConnection2A then I can mount the share just fine in my code, but the containers cannot see it as it is not "global". I can't find source for the PowerShell New-SmbGlobalMapping command (is there a way to see it?) and I can't find a suitable API to call. I hope someone knows the magic incantation I can put in my .NET code.
I can't find source for the PowerShell New-SmbGlobalMapping command
(is there a way to see it?) and I can't find a suitable API to call. I
hope someone knows the magic incantation I can put in my .NET code.
PowerShell uses WMI
In your case, it calls
Create method of the MSFT_SmbMapping class (MSFT_SmbGlobalMapping exactly)
You can use WMI Code Creator to generate/test C# code
EDIT : Test with PowerShell.Create
Test as Admin ("requireAdministrator" in manifest) on Windows 10
Test code (C#, VS 2015) =>
// PowerShell calls CredUIPromptForCredentialsW to display the User/Password dialog (you can call it with P/Invoke if needed)
string sUser = "user#provider.com";
string sPassword = "myPassword";
System.Net.NetworkCredential networkCredential = new System.Net.NetworkCredential(sUser, sPassword, null);
System.Security.SecureString securePassword = new System.Security.SecureString();
foreach (var c in networkCredential.Password)
securePassword.AppendChar(c);
// Add reference to :
// C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll
// Add :
// using System.Management.Automation;
PSCredential psCredential = new PSCredential(networkCredential.UserName, securePassword);
// Error handling must be improved : if I pass an invalid syntax for "RemotePath" or not launched as Admin,
// nothing happens (no error, no result) (on Windows 10)
string sLocalPath = "Q:";
string sRemotePath = "\\\\DESKTOP-EOPIFM5\\Windows 7";
using (var ps = PowerShell.Create())
{
ps.AddCommand("New-SmbGlobalMapping");
ps.AddParameter("LocalPath", sLocalPath);
ps.AddParameter("RemotePath", sRemotePath);
ps.AddParameter("Credential", psCredential);
//ps.AddParameter("RequireIntegrity", false);
//ps.AddParameter("RequirePrivacy", false);
try
{
System.Collections.ObjectModel.Collection<PSObject> collectionResults = ps.Invoke();
foreach (PSObject psObl in collectionResults)
{
Console.WriteLine("Status : {0}", psObl.Members["Status"].Value.ToString());
Console.WriteLine("Local Path : {0}", psObl.Members["LocalPath"].Value.ToString());
Console.WriteLine("Remote Path : {0}\n", psObl.Members["RemotePath"].Value.ToString());
}
}
catch (ParameterBindingException pbe)
{
System.Console.WriteLine("\rNew-SmbGlobalMapping error : {0}: {1}",
pbe.GetType().FullName, pbe.Message);
}
}
// To get and remove the test mapping in PowerShell :
// Get-SmbGlobalMapping
// Remove-SmbGlobalMapping -RemotePath "\\DESKTOP-EOPIFM5\Windows 7" -Force

Why does ParseExact in my PowerShell script accept an int64 and not a string?

Although I was able to achieve my objective, I don't understand why and I am concerned I'm still doing something wrong.
The context is to retrieve a datetime value for comparison to another date using FTP to retrieve a file's timestamp. The datetime is received as a string using this:
$FTPrequest = [System.Net.FtpWebRequest]::Create($FTPTargetFile)
$FTPrequest.Method = [System.Net.WebRequestMethods+FTP]::GetDateTimestamp
$FTPrequest.Credentials = $Credentials
$response = $FTPrequest.GetResponse().StatusDescription
$tokens = $response.Split(" ")
if ($tokens[0] -eq 213) {
$Timestampdata = $tokens[1]
} else {
Write-Output "FTP timestamp request for " + $FTPTargetFile + " returned an error"
}
Almost every available resource I found made it clear that ParseExact uses a string to derive a datetime as in the following line of script:
$d = [DateTime]::ParseExact($Timestampdata,"yyyyMMddHHmmss",$null)
But whether in a script or at the command prompt, the line above consistently returns the following error:
Exception calling "ParseExact" with "3" argument(s): "String was not recognized
as a valid DateTime."
At C:\hw-sw\powershell\FTPupload option - getFileInfo.ps1:38 char:1
+ $d = [DateTime]::ParseExact($Timestampdata,"yyyyMMddHHmmss",$null)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : FormatException
There were 2 links I found (lin1, link2 - 2nd link addresses a slighty different problem but the point is still made) that suggested ParseExact() uses an intXX instead, as in the following line of script:
$d = [DateTime]::ParseExact([convert]::ToInt64($Timestampdata),"yyyyMMddHHmmss",$null)
And yes, the line above runs without error.
While I am grateful for having found a potential solution to my scripting problem, I am unsettled because I don't understand why ParseExact is working with an int64, and not a string as I would have expected.
Is there some setup in PowerShell that changes how it works? I believe I must be making some simple mistake, but I can't figure it out.
static datetime ParseExact(string s, string format, System.IFormatProvider provider)
static datetime ParseExact(string s, string format, System.IFormatProvider provider, System.Globalization.DateTimeStyles style)
static datetime ParseExact(string s, string[] formats, System.IFormatProvider provider, System.Globalization.DateTimeStyles style)
These are the overloaded methods for ParseExact on PSv5.1. You're passing it a $null provider.
Additional information
Your use case
After more reading, ensure your first argument doesn't have spaces or it will fail. Also make sure your current culture is correct for the behavior you're expecting.