how to create conditional arguments in a function? - powershell

I have the following function that hides a password middle chars and leaves 1st and last char visible
function Hide-ConnectionStringPassword {
param(
[parameter(Mandatory,ValueFromPipeline)]
[System.Data.SqlClient.SqlConnectionStringBuilder]$ConnectionString
)
[string]$FistChar = $ConnectionString.Password[0]
[string]$LastChar = $ConnectionString.Password[($ConnectionString.Password.Length - 1)]
[string]$Stars = '*' * ($ConnectionString.Password.Length - 2)
$ConnectionString.Password = $FistChar + $Stars + $LastChar
return $ConnectionString.ConnectionString
}
Usage:
Hide-ConnectionStringPassword 'Connection Timeout=120;User Id=UID1;Data Source=datasource.com;Password=password12!553;'
output:
Data Source=datasource.com;User
ID=UID1;Password=p************3;Connect Timeout=120
I have other connection strings that are JSON formatted, so the casting to sqlbuilder will not work on this type of input
{"Authentication
Kind":"UsernamePassword","Username":"someID1","Password":"Yu#gh456!ts","EncryptConnection":true}
One thing i could do is something like this:
$Json = '{"Authentication Kind":"UsernamePassword","Username":"someID1","Password":"Yu#gh456!ts","EncryptConnection":true}'
$Sql = $Json | ConvertFrom-Json
$Sql.Password
with
$Sql.gettype().name
i get
PSCustomObject
i would like to apply that in the function such that it checks string input is type pscustomobject so that it doesnt cast it as sqlbuilder
pseudocode:
function Hide-ConnectionStringPassword {
if ($input.gettype().name -ne 'PSCustomObject')
{
param(
[parameter(Mandatory,ValueFromPipeline)]
[System.Data.SqlClient.SqlConnectionStringBuilder]$ConnectionString
)}
else
{
param(
[parameter(Mandatory,ValueFromPipeline)]
$ConnectionString
)}
[string]$FistChar = $ConnectionString.Password[0]
[string]$LastChar = $ConnectionString.Password[($ConnectionString.Password.Length - 1)]
[string]$Stars = '*' * ($ConnectionString.Password.Length - 2)
$ConnectionString.Password = $FistChar + $Stars + $LastChar
return $ConnectionString.ConnectionString
}

You could just remove the hard typing in the parameter. Then move the if-else logic to the script Process {} or Begin {} script blocks.
function Hide-ConnectionStringPassword {
param(
[parameter(Mandatory,ValueFromPipeline)]
$ConnectionString
)
if ($ConnectionString.GetType().Name -ne "PSCustomObject") {
$ConnectionString = $ConnectionString -as [System.Data.SqlClient.SqlConnectionStringBuilder]
}
[string]$FistChar = $ConnectionString.Password[0]
[string]$LastChar = $ConnectionString.Password[($ConnectionString.Password.Length - 1)]
[string]$Stars = '*' * ($ConnectionString.Password.Length - 2)
$ConnectionString.Password = $FistChar + $Stars + $LastChar
return $ConnectionString.ConnectionString
}

Related

Possible to repeat an alias among different parameter sets in a powershell function?

Suppose I have:
function f {
[CmdletBinding(DefaultParameterSetName='x')]
param(
[Parameter(Mandatory,ParameterSetName='x')]
[Alias('a')]
[int]$Apple,
[Parameter(Mandatory,ParameterSetName='y')]
[Alias('b')]
[int]$Banana,
[Parameter(Mandatory,ParameterSetName='x')]
[Alias('b')]
[int]$Cherry
)
"Apple $Apple"
"Banana $Banana"
"Cherry $Cherry"
}
I'd like to be able to call f -Apple 1 -b 3 because including Apple means I'm certainly using Parameter Set x, but powershell complains that the alias b is declared multiple times.
Is it entirely impossible, or am I just missing a trick?
The non-trivial function I'm trying to write is a convenience wrapper for multiple external functions that have their own aliases, some of which can be the same for different named parameters, but the set of mandatory parameters would never be ambiguous.
I couldn't get it to work using regular params, but I found a workaround by defining Banana and Cherry as dynamic params. This way the alias b is only defined once, so PowerShell won't complain.
function f {
[CmdletBinding(DefaultParameterSetName='x')]
param(
[Parameter(Mandatory, ParameterSetName='x')]
[Alias('a')]
[int]$Apple
)
DynamicParam {
# If param 'Apple' exists, define dynamic param 'Cherry',
# else define dynamic param 'Banana', both using alias 'b'.
if( $PSBoundParameters.ContainsKey('Apple') ) {
$paramName = 'Cherry'
$paramSetName = 'x'
} else {
$paramName = 'Banana'
$paramSetName = 'y'
}
$aliasName = 'b'
$parameterAttribute = [System.Management.Automation.ParameterAttribute]#{
ParameterSetName = $paramSetName
Mandatory = $true
}
$aliasAttribute = [System.Management.Automation.AliasAttribute]::new( $aliasName )
$attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new()
$attributeCollection.Add( $parameterAttribute )
$attributeCollection.Add( $aliasAttribute )
$dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new(
$paramName, [Int32], $attributeCollection
)
$paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new()
$paramDictionary.Add($paramName, $dynParam)
$paramDictionary
}
process {
"--- Parameter Set '$($PSCmdlet.ParameterSetName)' ---"
if( $PSBoundParameters.ContainsKey('Apple') ) {
"Apple $Apple"
}
if( $PSBoundParameters.ContainsKey('Banana') ) {
# Dynamic params require special syntax to read
$Banana = $PSBoundParameters.Banana
"Banana $Banana"
}
if( $PSBoundParameters.ContainsKey('Cherry') ) {
# Dynamic params require special syntax to read
$Cherry = $PSBoundParameters.Cherry
"Cherry $Cherry"
}
}
}
Calling the function:
f -Apple 1 -b 3
f -b 2
Output:
--- Parameter Set 'x' ---
Apple 1
Cherry 3
--- Parameter Set 'y' ---
Banana 2

Change the paths to user folders(Shell Folders) with Powershell [duplicate]

As administrator, I want to change the default location of special folders (Documents, Music, Downloads…) to a different path. I can do this manually, but I would like to have a PowerShell script to do that.
Is there any PS Object that provides functions to do this? How can I do it?
Thanks.
PowerShell doesn't have a cmdlet that lets you do it out of the box (as far as I know), but you can use P/Invoke to call the SHSetKnownFolderPath shell function. I created a wrapper function called Set-KnownFolderPath that does it:
<#
.SYNOPSIS
Sets a known folder's path using SHSetKnownFolderPath.
.PARAMETER Folder
The known folder whose path to set.
.PARAMETER Path
The path.
#>
function Set-KnownFolderPath {
Param (
[Parameter(Mandatory = $true)]
[ValidateSet('3DObjects', 'AddNewPrograms', 'AdminTools', 'AppUpdates', 'CDBurning', 'ChangeRemovePrograms', 'CommonAdminTools', 'CommonOEMLinks', 'CommonPrograms', 'CommonStartMenu', 'CommonStartup', 'CommonTemplates', 'ComputerFolder', 'ConflictFolder', 'ConnectionsFolder', 'Contacts', 'ControlPanelFolder', 'Cookies', 'Desktop', 'Documents', 'Downloads', 'Favorites', 'Fonts', 'Games', 'GameTasks', 'History', 'InternetCache', 'InternetFolder', 'Links', 'LocalAppData', 'LocalAppDataLow', 'LocalizedResourcesDir', 'Music', 'NetHood', 'NetworkFolder', 'OriginalImages', 'PhotoAlbums', 'Pictures', 'Playlists', 'PrintersFolder', 'PrintHood', 'Profile', 'ProgramData', 'ProgramFiles', 'ProgramFilesX64', 'ProgramFilesX86', 'ProgramFilesCommon', 'ProgramFilesCommonX64', 'ProgramFilesCommonX86', 'Programs', 'Public', 'PublicDesktop', 'PublicDocuments', 'PublicDownloads', 'PublicGameTasks', 'PublicMusic', 'PublicPictures', 'PublicVideos', 'QuickLaunch', 'Recent', 'RecycleBinFolder', 'ResourceDir', 'RoamingAppData', 'SampleMusic', 'SamplePictures', 'SamplePlaylists', 'SampleVideos', 'SavedGames', 'SavedSearches', 'SEARCH_CSC', 'SEARCH_MAPI', 'SearchHome', 'SendTo', 'SidebarDefaultParts', 'SidebarParts', 'StartMenu', 'Startup', 'SyncManagerFolder', 'SyncResultsFolder', 'SyncSetupFolder', 'System', 'SystemX86', 'Templates', 'TreeProperties', 'UserProfiles', 'UsersFiles', 'Videos', 'Windows')]
[string]$KnownFolder,
[Parameter(Mandatory = $true)]
[string]$Path
)
# Define known folder GUIDs
$KnownFolders = #{
'3DObjects' = '31C0DD25-9439-4F12-BF41-7FF4EDA38722';
'AddNewPrograms' = 'de61d971-5ebc-4f02-a3a9-6c82895e5c04';
'AdminTools' = '724EF170-A42D-4FEF-9F26-B60E846FBA4F';
'AppUpdates' = 'a305ce99-f527-492b-8b1a-7e76fa98d6e4';
'CDBurning' = '9E52AB10-F80D-49DF-ACB8-4330F5687855';
'ChangeRemovePrograms' = 'df7266ac-9274-4867-8d55-3bd661de872d';
'CommonAdminTools' = 'D0384E7D-BAC3-4797-8F14-CBA229B392B5';
'CommonOEMLinks' = 'C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D';
'CommonPrograms' = '0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8';
'CommonStartMenu' = 'A4115719-D62E-491D-AA7C-E74B8BE3B067';
'CommonStartup' = '82A5EA35-D9CD-47C5-9629-E15D2F714E6E';
'CommonTemplates' = 'B94237E7-57AC-4347-9151-B08C6C32D1F7';
'ComputerFolder' = '0AC0837C-BBF8-452A-850D-79D08E667CA7';
'ConflictFolder' = '4bfefb45-347d-4006-a5be-ac0cb0567192';
'ConnectionsFolder' = '6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD';
'Contacts' = '56784854-C6CB-462b-8169-88E350ACB882';
'ControlPanelFolder' = '82A74AEB-AEB4-465C-A014-D097EE346D63';
'Cookies' = '2B0F765D-C0E9-4171-908E-08A611B84FF6';
'Desktop' = 'B4BFCC3A-DB2C-424C-B029-7FE99A87C641';
'Documents' = 'FDD39AD0-238F-46AF-ADB4-6C85480369C7';
'Downloads' = '374DE290-123F-4565-9164-39C4925E467B';
'Favorites' = '1777F761-68AD-4D8A-87BD-30B759FA33DD';
'Fonts' = 'FD228CB7-AE11-4AE3-864C-16F3910AB8FE';
'Games' = 'CAC52C1A-B53D-4edc-92D7-6B2E8AC19434';
'GameTasks' = '054FAE61-4DD8-4787-80B6-090220C4B700';
'History' = 'D9DC8A3B-B784-432E-A781-5A1130A75963';
'InternetCache' = '352481E8-33BE-4251-BA85-6007CAEDCF9D';
'InternetFolder' = '4D9F7874-4E0C-4904-967B-40B0D20C3E4B';
'Links' = 'bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968';
'LocalAppData' = 'F1B32785-6FBA-4FCF-9D55-7B8E7F157091';
'LocalAppDataLow' = 'A520A1A4-1780-4FF6-BD18-167343C5AF16';
'LocalizedResourcesDir' = '2A00375E-224C-49DE-B8D1-440DF7EF3DDC';
'Music' = '4BD8D571-6D19-48D3-BE97-422220080E43';
'NetHood' = 'C5ABBF53-E17F-4121-8900-86626FC2C973';
'NetworkFolder' = 'D20BEEC4-5CA8-4905-AE3B-BF251EA09B53';
'OriginalImages' = '2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39';
'PhotoAlbums' = '69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C';
'Pictures' = '33E28130-4E1E-4676-835A-98395C3BC3BB';
'Playlists' = 'DE92C1C7-837F-4F69-A3BB-86E631204A23';
'PrintersFolder' = '76FC4E2D-D6AD-4519-A663-37BD56068185';
'PrintHood' = '9274BD8D-CFD1-41C3-B35E-B13F55A758F4';
'Profile' = '5E6C858F-0E22-4760-9AFE-EA3317B67173';
'ProgramData' = '62AB5D82-FDC1-4DC3-A9DD-070D1D495D97';
'ProgramFiles' = '905e63b6-c1bf-494e-b29c-65b732d3d21a';
'ProgramFilesX64' = '6D809377-6AF0-444b-8957-A3773F02200E';
'ProgramFilesX86' = '7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E';
'ProgramFilesCommon' = 'F7F1ED05-9F6D-47A2-AAAE-29D317C6F066';
'ProgramFilesCommonX64' = '6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D';
'ProgramFilesCommonX86' = 'DE974D24-D9C6-4D3E-BF91-F4455120B917';
'Programs' = 'A77F5D77-2E2B-44C3-A6A2-ABA601054A51';
'Public' = 'DFDF76A2-C82A-4D63-906A-5644AC457385';
'PublicDesktop' = 'C4AA340D-F20F-4863-AFEF-F87EF2E6BA25';
'PublicDocuments' = 'ED4824AF-DCE4-45A8-81E2-FC7965083634';
'PublicDownloads' = '3D644C9B-1FB8-4f30-9B45-F670235F79C0';
'PublicGameTasks' = 'DEBF2536-E1A8-4c59-B6A2-414586476AEA';
'PublicMusic' = '3214FAB5-9757-4298-BB61-92A9DEAA44FF';
'PublicPictures' = 'B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5';
'PublicVideos' = '2400183A-6185-49FB-A2D8-4A392A602BA3';
'QuickLaunch' = '52a4f021-7b75-48a9-9f6b-4b87a210bc8f';
'Recent' = 'AE50C081-EBD2-438A-8655-8A092E34987A';
'RecycleBinFolder' = 'B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC';
'ResourceDir' = '8AD10C31-2ADB-4296-A8F7-E4701232C972';
'RoamingAppData' = '3EB685DB-65F9-4CF6-A03A-E3EF65729F3D';
'SampleMusic' = 'B250C668-F57D-4EE1-A63C-290EE7D1AA1F';
'SamplePictures' = 'C4900540-2379-4C75-844B-64E6FAF8716B';
'SamplePlaylists' = '15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5';
'SampleVideos' = '859EAD94-2E85-48AD-A71A-0969CB56A6CD';
'SavedGames' = '4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4';
'SavedSearches' = '7d1d3a04-debb-4115-95cf-2f29da2920da';
'SEARCH_CSC' = 'ee32e446-31ca-4aba-814f-a5ebd2fd6d5e';
'SEARCH_MAPI' = '98ec0e18-2098-4d44-8644-66979315a281';
'SearchHome' = '190337d1-b8ca-4121-a639-6d472d16972a';
'SendTo' = '8983036C-27C0-404B-8F08-102D10DCFD74';
'SidebarDefaultParts' = '7B396E54-9EC5-4300-BE0A-2482EBAE1A26';
'SidebarParts' = 'A75D362E-50FC-4fb7-AC2C-A8BEAA314493';
'StartMenu' = '625B53C3-AB48-4EC1-BA1F-A1EF4146FC19';
'Startup' = 'B97D20BB-F46A-4C97-BA10-5E3608430854';
'SyncManagerFolder' = '43668BF8-C14E-49B2-97C9-747784D784B7';
'SyncResultsFolder' = '289a9a43-be44-4057-a41b-587a76d7e7f9';
'SyncSetupFolder' = '0F214138-B1D3-4a90-BBA9-27CBC0C5389A';
'System' = '1AC14E77-02E7-4E5D-B744-2EB1AE5198B7';
'SystemX86' = 'D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27';
'Templates' = 'A63293E8-664E-48DB-A079-DF759E0509F7';
'TreeProperties' = '5b3749ad-b49f-49c1-83eb-15370fbd4882';
'UserProfiles' = '0762D272-C50A-4BB0-A382-697DCD729B80';
'UsersFiles' = 'f3ce0f7c-4901-4acc-8648-d5d44b04ef8f';
'Videos' = '18989B1D-99B5-455B-841C-AB7C74E4DDFC';
'Windows' = 'F38BF404-1D43-42F2-9305-67DE0B28FC23';
}
# Define SHSetKnownFolderPath if it hasn't been defined already
$Type = ([System.Management.Automation.PSTypeName]'KnownFolders').Type
if (-not $Type) {
$Signature = #'
[DllImport("shell32.dll")]
public extern static int SHSetKnownFolderPath(ref Guid folderId, uint flags, IntPtr token, [MarshalAs(UnmanagedType.LPWStr)] string path);
'#
$Type = Add-Type -MemberDefinition $Signature -Name 'KnownFolders' -Namespace 'SHSetKnownFolderPath' -PassThru
}
# Validate the path
if (Test-Path $Path -PathType Container) {
# Call SHSetKnownFolderPath
return $Type::SHSetKnownFolderPath([ref]$KnownFolders[$KnownFolder], 0, 0, $Path)
} else {
throw New-Object System.IO.DirectoryNotFoundException "Could not find part of the path $Path."
}
}
Use it like this:
Set-KnownFolderPath -KnownFolder 'Desktop' -Path 'C:\'
This is indeed a great function! However, I altered it a bit for my own use, perhaps you may find this useful as well.
# Validate the path
if(!(Test-Path $Path -PathType Container))
{
New-Item -Path $Path -type Directory -Force
}
# Call SHSetKnownFolderPath
$Type::SHSetKnownFolderPath([ref]$KnownFolders[$KnownFolder], 0, 0, $Path)
attrib +r $Path
$Leaf = Split-Path -Path "$Path" -Leaf
Move-Item "$HOME\$Leaf\*" $Path
# rd $HOME\$Leaf -recurse -Force
As you can see here, I first check the existence of the path and creating the path if it doesn't exist.
I left out the "return" command so I can still use the $Path variable.
With the attrib-command you can give the special folder it's original icon back.
Finally I move all the existing files from the old location to the new.
You can also optionally remove the old folder, which I would recommend for not having double special folders present in the User's Files.
The only tricky thing with this, is that Move-Item assumes the old location is at the $HOME variable. Also if you would use the function to change, let's say the Documents folder to C:\Users\John\Documents, it would also remove the contents of that folder, since the last lines of code deletes the $HOME-special folder variable.
But perhaps someone can find a more elegant solution to that obstacle.
Thanks again for this gem of a function!
The accepted answer here would require you to logout/login again for the change to take effect.
I've added another call to SHChangeNotify so that the change takes effect immediately. Tested on Windows 10
<#
.SYNOPSIS
Sets a known folders path using SHSetKnownFolderPath.
.PARAMETER Folder
The known folder whose path to set.
.PARAMETER Path
The path.
#>
function Set-KnownFolderPath {
Param (
[Parameter(Mandatory = $true)]
[ValidateSet('3DObjects', 'AddNewPrograms', 'AdminTools', 'AppUpdates', 'CDBurning', 'ChangeRemovePrograms', 'CommonAdminTools', 'CommonOEMLinks', 'CommonPrograms', 'CommonStartMenu', 'CommonStartup', 'CommonTemplates', 'ComputerFolder', 'ConflictFolder', 'ConnectionsFolder', 'Contacts', 'ControlPanelFolder', 'Cookies', 'Desktop', 'Documents', 'Downloads', 'Favorites', 'Fonts', 'Games', 'GameTasks', 'History', 'InternetCache', 'InternetFolder', 'Links', 'LocalAppData', 'LocalAppDataLow', 'LocalizedResourcesDir', 'Music', 'NetHood', 'NetworkFolder', 'OriginalImages', 'PhotoAlbums', 'Pictures', 'Playlists', 'PrintersFolder', 'PrintHood', 'Profile', 'ProgramData', 'ProgramFiles', 'ProgramFilesX64', 'ProgramFilesX86', 'ProgramFilesCommon', 'ProgramFilesCommonX64', 'ProgramFilesCommonX86', 'Programs', 'Public', 'PublicDesktop', 'PublicDocuments', 'PublicDownloads', 'PublicGameTasks', 'PublicMusic', 'PublicPictures', 'PublicVideos', 'QuickLaunch', 'Recent', 'RecycleBinFolder', 'ResourceDir', 'RoamingAppData', 'SampleMusic', 'SamplePictures', 'SamplePlaylists', 'SampleVideos', 'SavedGames', 'SavedSearches', 'SEARCH_CSC', 'SEARCH_MAPI', 'SearchHome', 'SendTo', 'SidebarDefaultParts', 'SidebarParts', 'StartMenu', 'Startup', 'SyncManagerFolder', 'SyncResultsFolder', 'SyncSetupFolder', 'System', 'SystemX86', 'Templates', 'TreeProperties', 'UserProfiles', 'UsersFiles', 'Videos', 'Windows')]
[string]$KnownFolder,
[Parameter(Mandatory = $true)]
[string]$Path
)
# Define known folder GUIDs
$KnownFolders = #{
'3DObjects' = '31C0DD25-9439-4F12-BF41-7FF4EDA38722';
'AddNewPrograms' = 'de61d971-5ebc-4f02-a3a9-6c82895e5c04';
'AdminTools' = '724EF170-A42D-4FEF-9F26-B60E846FBA4F';
'AppUpdates' = 'a305ce99-f527-492b-8b1a-7e76fa98d6e4';
'CDBurning' = '9E52AB10-F80D-49DF-ACB8-4330F5687855';
'ChangeRemovePrograms' = 'df7266ac-9274-4867-8d55-3bd661de872d';
'CommonAdminTools' = 'D0384E7D-BAC3-4797-8F14-CBA229B392B5';
'CommonOEMLinks' = 'C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D';
'CommonPrograms' = '0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8';
'CommonStartMenu' = 'A4115719-D62E-491D-AA7C-E74B8BE3B067';
'CommonStartup' = '82A5EA35-D9CD-47C5-9629-E15D2F714E6E';
'CommonTemplates' = 'B94237E7-57AC-4347-9151-B08C6C32D1F7';
'ComputerFolder' = '0AC0837C-BBF8-452A-850D-79D08E667CA7';
'ConflictFolder' = '4bfefb45-347d-4006-a5be-ac0cb0567192';
'ConnectionsFolder' = '6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD';
'Contacts' = '56784854-C6CB-462b-8169-88E350ACB882';
'ControlPanelFolder' = '82A74AEB-AEB4-465C-A014-D097EE346D63';
'Cookies' = '2B0F765D-C0E9-4171-908E-08A611B84FF6';
'Desktop' = 'B4BFCC3A-DB2C-424C-B029-7FE99A87C641';
'Documents' = 'FDD39AD0-238F-46AF-ADB4-6C85480369C7';
'Downloads' = '374DE290-123F-4565-9164-39C4925E467B';
'Favorites' = '1777F761-68AD-4D8A-87BD-30B759FA33DD';
'Fonts' = 'FD228CB7-AE11-4AE3-864C-16F3910AB8FE';
'Games' = 'CAC52C1A-B53D-4edc-92D7-6B2E8AC19434';
'GameTasks' = '054FAE61-4DD8-4787-80B6-090220C4B700';
'History' = 'D9DC8A3B-B784-432E-A781-5A1130A75963';
'InternetCache' = '352481E8-33BE-4251-BA85-6007CAEDCF9D';
'InternetFolder' = '4D9F7874-4E0C-4904-967B-40B0D20C3E4B';
'Links' = 'bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968';
'LocalAppData' = 'F1B32785-6FBA-4FCF-9D55-7B8E7F157091';
'LocalAppDataLow' = 'A520A1A4-1780-4FF6-BD18-167343C5AF16';
'LocalizedResourcesDir' = '2A00375E-224C-49DE-B8D1-440DF7EF3DDC';
'Music' = '4BD8D571-6D19-48D3-BE97-422220080E43';
'NetHood' = 'C5ABBF53-E17F-4121-8900-86626FC2C973';
'NetworkFolder' = 'D20BEEC4-5CA8-4905-AE3B-BF251EA09B53';
'OriginalImages' = '2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39';
'PhotoAlbums' = '69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C';
'Pictures' = '33E28130-4E1E-4676-835A-98395C3BC3BB';
'Playlists' = 'DE92C1C7-837F-4F69-A3BB-86E631204A23';
'PrintersFolder' = '76FC4E2D-D6AD-4519-A663-37BD56068185';
'PrintHood' = '9274BD8D-CFD1-41C3-B35E-B13F55A758F4';
'Profile' = '5E6C858F-0E22-4760-9AFE-EA3317B67173';
'ProgramData' = '62AB5D82-FDC1-4DC3-A9DD-070D1D495D97';
'ProgramFiles' = '905e63b6-c1bf-494e-b29c-65b732d3d21a';
'ProgramFilesX64' = '6D809377-6AF0-444b-8957-A3773F02200E';
'ProgramFilesX86' = '7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E';
'ProgramFilesCommon' = 'F7F1ED05-9F6D-47A2-AAAE-29D317C6F066';
'ProgramFilesCommonX64' = '6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D';
'ProgramFilesCommonX86' = 'DE974D24-D9C6-4D3E-BF91-F4455120B917';
'Programs' = 'A77F5D77-2E2B-44C3-A6A2-ABA601054A51';
'Public' = 'DFDF76A2-C82A-4D63-906A-5644AC457385';
'PublicDesktop' = 'C4AA340D-F20F-4863-AFEF-F87EF2E6BA25';
'PublicDocuments' = 'ED4824AF-DCE4-45A8-81E2-FC7965083634';
'PublicDownloads' = '3D644C9B-1FB8-4f30-9B45-F670235F79C0';
'PublicGameTasks' = 'DEBF2536-E1A8-4c59-B6A2-414586476AEA';
'PublicMusic' = '3214FAB5-9757-4298-BB61-92A9DEAA44FF';
'PublicPictures' = 'B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5';
'PublicVideos' = '2400183A-6185-49FB-A2D8-4A392A602BA3';
'QuickLaunch' = '52a4f021-7b75-48a9-9f6b-4b87a210bc8f';
'Recent' = 'AE50C081-EBD2-438A-8655-8A092E34987A';
'RecycleBinFolder' = 'B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC';
'ResourceDir' = '8AD10C31-2ADB-4296-A8F7-E4701232C972';
'RoamingAppData' = '3EB685DB-65F9-4CF6-A03A-E3EF65729F3D';
'SampleMusic' = 'B250C668-F57D-4EE1-A63C-290EE7D1AA1F';
'SamplePictures' = 'C4900540-2379-4C75-844B-64E6FAF8716B';
'SamplePlaylists' = '15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5';
'SampleVideos' = '859EAD94-2E85-48AD-A71A-0969CB56A6CD';
'SavedGames' = '4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4';
'SavedSearches' = '7d1d3a04-debb-4115-95cf-2f29da2920da';
'SEARCH_CSC' = 'ee32e446-31ca-4aba-814f-a5ebd2fd6d5e';
'SEARCH_MAPI' = '98ec0e18-2098-4d44-8644-66979315a281';
'SearchHome' = '190337d1-b8ca-4121-a639-6d472d16972a';
'SendTo' = '8983036C-27C0-404B-8F08-102D10DCFD74';
'SidebarDefaultParts' = '7B396E54-9EC5-4300-BE0A-2482EBAE1A26';
'SidebarParts' = 'A75D362E-50FC-4fb7-AC2C-A8BEAA314493';
'StartMenu' = '625B53C3-AB48-4EC1-BA1F-A1EF4146FC19';
'Startup' = 'B97D20BB-F46A-4C97-BA10-5E3608430854';
'SyncManagerFolder' = '43668BF8-C14E-49B2-97C9-747784D784B7';
'SyncResultsFolder' = '289a9a43-be44-4057-a41b-587a76d7e7f9';
'SyncSetupFolder' = '0F214138-B1D3-4a90-BBA9-27CBC0C5389A';
'System' = '1AC14E77-02E7-4E5D-B744-2EB1AE5198B7';
'SystemX86' = 'D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27';
'Templates' = 'A63293E8-664E-48DB-A079-DF759E0509F7';
'TreeProperties' = '5b3749ad-b49f-49c1-83eb-15370fbd4882';
'UserProfiles' = '0762D272-C50A-4BB0-A382-697DCD729B80';
'UsersFiles' = 'f3ce0f7c-4901-4acc-8648-d5d44b04ef8f';
'Videos' = '18989B1D-99B5-455B-841C-AB7C74E4DDFC';
'Windows' = 'F38BF404-1D43-42F2-9305-67DE0B28FC23';
}
# Define SHSetKnownFolderPath if it hasn't been defined already
$Type1 = ([System.Management.Automation.PSTypeName]'KnownFolders').Type
if (-not $Type1) {
$Signature = #'
[DllImport("shell32.dll")]
public extern static int SHSetKnownFolderPath(ref Guid folderId, uint flags, IntPtr token, [MarshalAs(UnmanagedType.LPWStr)] string path);
'#
$Type1 = Add-Type -MemberDefinition $Signature -Name 'KnownFolders' -Namespace 'SHSetKnownFolderPath' -PassThru
}
$Type2 = ([System.Management.Automation.PSTypeName]'ChangeNotify').Type
if (-not $Type2) {
$Signature = #'
[DllImport("Shell32.dll")]
public static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
'#
$Type2 = Add-Type -MemberDefinition $Signature -Name 'ChangeNotify' -Namespace 'SHChangeNotify' -PassThru
}
# Validate the path
if (Test-Path $Path -PathType Container) {
# Call SHSetKnownFolderPath
$r = $Type1::SHSetKnownFolderPath([ref]$KnownFolders[$KnownFolder], 0, 0, $Path)
$Type2::SHChangeNotify(0x8000000, 0x1000, 0, 0)
return $r
} else {
throw New-Object System.IO.DirectoryNotFoundException "Could not find part of the path $Path."
}
}
edit: runs on powershell 7, for powershell 5 and further issues/updates see github
Thanks for Eric's answer I am providing you my handicraft. github gist this was very interesting, I learned a lot.
Usage:
Import-Module ./KnownFolderPath.ps1 -Force
$Path=""
Get-KnownFolderPath Desktop ([ref]$Path)
echo $Path
Set-KnownFolderPath Desktop $ENV:USERPROFILE/Desktop
Get-KnownFolderPath Desktop ([ref]$Path)
echo $Path
0
D:\Desktop
0
0
C:\Users\user\Desktop
KnownFolderPath.ps1
<#
.SYNOPSIS
Provides Get and Set functions for KnownFolders
.EXAMPLE
PS> Set-KnownFolderPath Desktop $ENV:USERPROFILE/Desktop
.EXAMPLE
PS> $Path=""
PS> Get-KnownFolderPath Desktop ([ref]$Path)
.LINK
https://learn.microsoft.com/en-us/windows/win32/shell/known-folders
.LINK
https://stackoverflow.com/questions/25709398/set-location-of-special-folders-with-powershell
.LINK
https://gist.github.com/YoraiLevi/0f333d520f502fdb1244cdf0524db6d2
#>
using namespace System.Management.Automation
# Define known folder GUIDs
$KnownFolders = #{
'3DObjects' = '31C0DD25-9439-4F12-BF41-7FF4EDA38722';
'AddNewPrograms' = 'de61d971-5ebc-4f02-a3a9-6c82895e5c04';
'AdminTools' = '724EF170-A42D-4FEF-9F26-B60E846FBA4F';
'AppUpdates' = 'a305ce99-f527-492b-8b1a-7e76fa98d6e4';
'CDBurning' = '9E52AB10-F80D-49DF-ACB8-4330F5687855';
'ChangeRemovePrograms' = 'df7266ac-9274-4867-8d55-3bd661de872d';
'CommonAdminTools' = 'D0384E7D-BAC3-4797-8F14-CBA229B392B5';
'CommonOEMLinks' = 'C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D';
'CommonPrograms' = '0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8';
'CommonStartMenu' = 'A4115719-D62E-491D-AA7C-E74B8BE3B067';
'CommonStartup' = '82A5EA35-D9CD-47C5-9629-E15D2F714E6E';
'CommonTemplates' = 'B94237E7-57AC-4347-9151-B08C6C32D1F7';
'ComputerFolder' = '0AC0837C-BBF8-452A-850D-79D08E667CA7';
'ConflictFolder' = '4bfefb45-347d-4006-a5be-ac0cb0567192';
'ConnectionsFolder' = '6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD';
'Contacts' = '56784854-C6CB-462b-8169-88E350ACB882';
'ControlPanelFolder' = '82A74AEB-AEB4-465C-A014-D097EE346D63';
'Cookies' = '2B0F765D-C0E9-4171-908E-08A611B84FF6';
'Desktop' = 'B4BFCC3A-DB2C-424C-B029-7FE99A87C641';
'Documents' = 'FDD39AD0-238F-46AF-ADB4-6C85480369C7';
'Downloads' = '374DE290-123F-4565-9164-39C4925E467B';
'Favorites' = '1777F761-68AD-4D8A-87BD-30B759FA33DD';
'Fonts' = 'FD228CB7-AE11-4AE3-864C-16F3910AB8FE';
'Games' = 'CAC52C1A-B53D-4edc-92D7-6B2E8AC19434';
'GameTasks' = '054FAE61-4DD8-4787-80B6-090220C4B700';
'History' = 'D9DC8A3B-B784-432E-A781-5A1130A75963';
'InternetCache' = '352481E8-33BE-4251-BA85-6007CAEDCF9D';
'InternetFolder' = '4D9F7874-4E0C-4904-967B-40B0D20C3E4B';
'Links' = 'bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968';
'LocalAppData' = 'F1B32785-6FBA-4FCF-9D55-7B8E7F157091';
'LocalAppDataLow' = 'A520A1A4-1780-4FF6-BD18-167343C5AF16';
'LocalizedResourcesDir' = '2A00375E-224C-49DE-B8D1-440DF7EF3DDC';
'Music' = '4BD8D571-6D19-48D3-BE97-422220080E43';
'NetHood' = 'C5ABBF53-E17F-4121-8900-86626FC2C973';
'NetworkFolder' = 'D20BEEC4-5CA8-4905-AE3B-BF251EA09B53';
'OriginalImages' = '2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39';
'PhotoAlbums' = '69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C';
'Pictures' = '33E28130-4E1E-4676-835A-98395C3BC3BB';
'Playlists' = 'DE92C1C7-837F-4F69-A3BB-86E631204A23';
'PrintersFolder' = '76FC4E2D-D6AD-4519-A663-37BD56068185';
'PrintHood' = '9274BD8D-CFD1-41C3-B35E-B13F55A758F4';
'Profile' = '5E6C858F-0E22-4760-9AFE-EA3317B67173';
'ProgramData' = '62AB5D82-FDC1-4DC3-A9DD-070D1D495D97';
'ProgramFiles' = '905e63b6-c1bf-494e-b29c-65b732d3d21a';
'ProgramFilesX64' = '6D809377-6AF0-444b-8957-A3773F02200E';
'ProgramFilesX86' = '7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E';
'ProgramFilesCommon' = 'F7F1ED05-9F6D-47A2-AAAE-29D317C6F066';
'ProgramFilesCommonX64' = '6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D';
'ProgramFilesCommonX86' = 'DE974D24-D9C6-4D3E-BF91-F4455120B917';
'Programs' = 'A77F5D77-2E2B-44C3-A6A2-ABA601054A51';
'Public' = 'DFDF76A2-C82A-4D63-906A-5644AC457385';
'PublicDesktop' = 'C4AA340D-F20F-4863-AFEF-F87EF2E6BA25';
'PublicDocuments' = 'ED4824AF-DCE4-45A8-81E2-FC7965083634';
'PublicDownloads' = '3D644C9B-1FB8-4f30-9B45-F670235F79C0';
'PublicGameTasks' = 'DEBF2536-E1A8-4c59-B6A2-414586476AEA';
'PublicMusic' = '3214FAB5-9757-4298-BB61-92A9DEAA44FF';
'PublicPictures' = 'B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5';
'PublicVideos' = '2400183A-6185-49FB-A2D8-4A392A602BA3';
'QuickLaunch' = '52a4f021-7b75-48a9-9f6b-4b87a210bc8f';
'Recent' = 'AE50C081-EBD2-438A-8655-8A092E34987A';
'RecycleBinFolder' = 'B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC';
'ResourceDir' = '8AD10C31-2ADB-4296-A8F7-E4701232C972';
'RoamingAppData' = '3EB685DB-65F9-4CF6-A03A-E3EF65729F3D';
'SampleMusic' = 'B250C668-F57D-4EE1-A63C-290EE7D1AA1F';
'SamplePictures' = 'C4900540-2379-4C75-844B-64E6FAF8716B';
'SamplePlaylists' = '15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5';
'SampleVideos' = '859EAD94-2E85-48AD-A71A-0969CB56A6CD';
'SavedGames' = '4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4';
'SavedSearches' = '7d1d3a04-debb-4115-95cf-2f29da2920da';
'SEARCH_CSC' = 'ee32e446-31ca-4aba-814f-a5ebd2fd6d5e';
'SEARCH_MAPI' = '98ec0e18-2098-4d44-8644-66979315a281';
'SearchHome' = '190337d1-b8ca-4121-a639-6d472d16972a';
'SendTo' = '8983036C-27C0-404B-8F08-102D10DCFD74';
'SidebarDefaultParts' = '7B396E54-9EC5-4300-BE0A-2482EBAE1A26';
'SidebarParts' = 'A75D362E-50FC-4fb7-AC2C-A8BEAA314493';
'StartMenu' = '625B53C3-AB48-4EC1-BA1F-A1EF4146FC19';
'Startup' = 'B97D20BB-F46A-4C97-BA10-5E3608430854';
'SyncManagerFolder' = '43668BF8-C14E-49B2-97C9-747784D784B7';
'SyncResultsFolder' = '289a9a43-be44-4057-a41b-587a76d7e7f9';
'SyncSetupFolder' = '0F214138-B1D3-4a90-BBA9-27CBC0C5389A';
'System' = '1AC14E77-02E7-4E5D-B744-2EB1AE5198B7';
'SystemX86' = 'D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27';
'Templates' = 'A63293E8-664E-48DB-A079-DF759E0509F7';
'TreeProperties' = '5b3749ad-b49f-49c1-83eb-15370fbd4882';
'UserProfiles' = '0762D272-C50A-4BB0-A382-697DCD729B80';
'UsersFiles' = 'f3ce0f7c-4901-4acc-8648-d5d44b04ef8f';
'Videos' = '18989B1D-99B5-455B-841C-AB7C74E4DDFC';
'Windows' = 'F38BF404-1D43-42F2-9305-67DE0B28FC23';
}
# Settings KnownFolders to be in script scope breaks the validator when Import-module is used 2 times.
# New-Variable -Name KnownFolders -Value $KnownFolders -Scope Script -Force
class ValidKnownFoldersGenerator : IValidateSetValuesGenerator {
#Preferably I would hide this class but I don't know enough powershell to scope it out of global
[string[]] GetValidValues() {
$Values = $global:KnownFolders.Keys
echo $Values
return $Values
}
}
echo $KnownFolders
function Set-KnownFolderPath {
<#
.SYNOPSIS
Sets a known folder's path using SHSetKnownFolderPath.
.PARAMETER KnownFolder
The known folder whose path to set.
.PARAMETER Path
The path.
.INPUTS
None. You cannot pipe objects to Set-KnownFolderPath.
.OUTPUTS
Int. Set-KnownFolderPath returns an int with the return code of SHSetKnownFolderPath
.EXAMPLE
PS> Set-KnownFolderPath Desktop $ENV:USERPROFILE/Desktop
0
.EXAMPLE
PS> Set-KnownFolderPath -KnownFolder Desktop -Path $ENV:USERPROFILE/Desktop
0
.LINK
https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shsetknownfolderpath
.LINK
https://stackoverflow.com/questions/25709398/set-location-of-special-folders-with-powershell
#>
Param (
[Parameter(Mandatory = $true)]
[ValidateSet([ValidKnownFoldersGenerator])]
# [ValidateSet('3DObjects', 'AddNewPrograms', 'AdminTools', 'AppUpdates', 'CDBurning', 'ChangeRemovePrograms', 'CommonAdminTools', 'CommonOEMLinks', 'CommonPrograms', 'CommonStartMenu', 'CommonStartup', 'CommonTemplates', 'ComputerFolder', 'ConflictFolder', 'ConnectionsFolder', 'Contacts', 'ControlPanelFolder', 'Cookies', 'Desktop', 'Documents', 'Downloads', 'Favorites', 'Fonts', 'Games', 'GameTasks', 'History', 'InternetCache', 'InternetFolder', 'Links', 'LocalAppData', 'LocalAppDataLow', 'LocalizedResourcesDir', 'Music', 'NetHood', 'NetworkFolder', 'OriginalImages', 'PhotoAlbums', 'Pictures', 'Playlists', 'PrintersFolder', 'PrintHood', 'Profile', 'ProgramData', 'ProgramFiles', 'ProgramFilesX64', 'ProgramFilesX86', 'ProgramFilesCommon', 'ProgramFilesCommonX64', 'ProgramFilesCommonX86', 'Programs', 'Public', 'PublicDesktop', 'PublicDocuments', 'PublicDownloads', 'PublicGameTasks', 'PublicMusic', 'PublicPictures', 'PublicVideos', 'QuickLaunch', 'Recent', 'RecycleBinFolder', 'ResourceDir', 'RoamingAppData', 'SampleMusic', 'SamplePictures', 'SamplePlaylists', 'SampleVideos', 'SavedGames', 'SavedSearches', 'SEARCH_CSC', 'SEARCH_MAPI', 'SearchHome', 'SendTo', 'SidebarDefaultParts', 'SidebarParts', 'StartMenu', 'Startup', 'SyncManagerFolder', 'SyncResultsFolder', 'SyncSetupFolder', 'System', 'SystemX86', 'Templates', 'TreeProperties', 'UserProfiles', 'UsersFiles', 'Videos', 'Windows')]
[string]$KnownFolder,
[Parameter(Mandatory = $true)]
[string]$Path
)
# Define SHSetKnownFolderPath if it hasn't been defined already
$Type = ([System.Management.Automation.PSTypeName]'KnownFolders.SHSetKnownFolderPathPS').Type
if (-not $Type) {
# http://www.pinvoke.net/default.aspx/shell32/SHSetKnownFolderPath.html
$Signature = #'
[DllImport("shell32.dll")]
public extern static int SHSetKnownFolderPath(ref Guid folderId, uint flags, IntPtr token, [MarshalAs(UnmanagedType.LPWStr)] string path);
'#
$Type = Add-Type -MemberDefinition $Signature -Namespace 'KnownFolders' -Name 'SHSetKnownFolderPathPS' -PassThru
}
# Validate the path
if (Test-Path $Path -PathType Container) {
# Call SHSetKnownFolderPath
return $Type::SHSetKnownFolderPath([ref]$KnownFolders[$KnownFolder], 0, 0, $Path)
}
else {
throw New-Object System.IO.DirectoryNotFoundException "Could not find part of the path $Path."
}
}
function Get-KnownFolderPath {
<#
.SYNOPSIS
Gets a known folder's path using SHGetKnownFolderPath.
.PARAMETER KnownFolder
The known folder whose path to get.
.PARAMETER Path
The path.
.INPUTS
None. You cannot pipe objects to Get-KnownFolderPath.
.OUTPUTS
Int. Get-KnownFolderPath returns an int with the return code of SHGetKnownFolderPath
.EXAMPLE
PS> Get-KnownFolderPath Desktop ([ref]$Path)
0
.EXAMPLE
PS> $Path = ""
PS> Get-KnownFolderPath -KnownFolder Desktop -Path ([ref]$Path)
0
PS>$Path #Check the value of path
C:\Users\user\Desktop
.LINK
https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/nf-shlobj_core-shgetknownfolderpath
.LINK
https://stackoverflow.com/questions/25709398/set-location-of-special-folders-with-powershell
#>
Param (
[Parameter(Mandatory = $true)]
[ValidateSet([ValidKnownFoldersGenerator])]
# [ValidateSet('3DObjects', 'AddNewPrograms', 'AdminTools', 'AppUpdates', 'CDBurning', 'ChangeRemovePrograms', 'CommonAdminTools', 'CommonOEMLinks', 'CommonPrograms', 'CommonStartMenu', 'CommonStartup', 'CommonTemplates', 'ComputerFolder', 'ConflictFolder', 'ConnectionsFolder', 'Contacts', 'ControlPanelFolder', 'Cookies', 'Desktop', 'Documents', 'Downloads', 'Favorites', 'Fonts', 'Games', 'GameTasks', 'History', 'InternetCache', 'InternetFolder', 'Links', 'LocalAppData', 'LocalAppDataLow', 'LocalizedResourcesDir', 'Music', 'NetHood', 'NetworkFolder', 'OriginalImages', 'PhotoAlbums', 'Pictures', 'Playlists', 'PrintersFolder', 'PrintHood', 'Profile', 'ProgramData', 'ProgramFiles', 'ProgramFilesX64', 'ProgramFilesX86', 'ProgramFilesCommon', 'ProgramFilesCommonX64', 'ProgramFilesCommonX86', 'Programs', 'Public', 'PublicDesktop', 'PublicDocuments', 'PublicDownloads', 'PublicGameTasks', 'PublicMusic', 'PublicPictures', 'PublicVideos', 'QuickLaunch', 'Recent', 'RecycleBinFolder', 'ResourceDir', 'RoamingAppData', 'SampleMusic', 'SamplePictures', 'SamplePlaylists', 'SampleVideos', 'SavedGames', 'SavedSearches', 'SEARCH_CSC', 'SEARCH_MAPI', 'SearchHome', 'SendTo', 'SidebarDefaultParts', 'SidebarParts', 'StartMenu', 'Startup', 'SyncManagerFolder', 'SyncResultsFolder', 'SyncSetupFolder', 'System', 'SystemX86', 'Templates', 'TreeProperties', 'UserProfiles', 'UsersFiles', 'Videos', 'Windows')]
[string]$KnownFolder,
[Parameter(Mandatory = $true)]
[ref]$Path
)
# Define SHGetKnownFolderPathif it hasn't been defined already
$Type = ([System.Management.Automation.PSTypeName]'KnownFolders.SHGetKnownFolderPathPS').Type
if (-not $Type) {
# http://www.pinvoke.net/default.aspx/shell32/SHGetKnownFolderPath.html
$Signature = #'
[DllImport("shell32.dll")]
public extern static int SHGetKnownFolderPath(ref Guid folderId, uint flags, IntPtr token,[MarshalAs(UnmanagedType.LPWStr)] out string pszPath);
'#
$Type = Add-Type -MemberDefinition $Signature -Namespace 'KnownFolders' -Name 'SHGetKnownFolderPathPS' -PassThru
}
# I am not sure why I need to work around like this instead of passing $Path directly but the value doesn't propegate outside...
$_Path = ""
$code = $Type::SHGetKnownFolderPath([ref]$KnownFolders[$KnownFolder], 0, 0, [ref]$_Path)
$Path.value = $_Path
return $code
}

Powershell class method return being cast to new type

I am working on a new PowerShell class that takes the path to an XML file in my old format and converts it to my new format. The constructor takes the path argument and calls a method, which should return a custom object with either the XML or an error message explaining the absence of the XML. What is odd is that the XML portion of the custom object is XML before the method returns, but immediately after it has been cast to a string.
So this class
class pxT_DefinitionsMigrater {
# Properties
$XML = [xml.xmlDocument]::New()
$Errors = [System.Collections.Generic.List[string]]::new()
# Constructor
pxT_DefinitionsMigrater ([string]$xmlPath) {
if ($oldDefinition = $this.readXMLFile($xmlPath).xml) {
$this.XML = $oldDefinition.xml
Write-Host "pxT_DefinitionsMigrater: $($oldDefinition.xml.GetType().FullName)"
} else {
$this.Errors = $oldDefinition.error
}
}
# Methods
[psCustomObject] readXMLFile ([string]$xmlPath) {
$readXMLFile = [psCustomObject]#{
xml = $null
error = $null
}
$fileStream = $null
$xmlreader = $null
$importFile = [xml.xmlDocument]::New()
$xmlReaderSettings = [xml.xmlReaderSettings]::New()
$xmlReaderSettings.closeInput = $true
$xmlReaderSettings.prohibitDtd = $false
try {
$fileStream = [io.fileStream]::New($xmlPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
$xmlreader = [xml.xmlreader]::Create($fileStream, $xmlReaderSettings)
$importFile.Load($xmlreader)
} catch [System.Management.Automation.MethodInvocationException] {
if ($_.Exception.Message -match ': "(?<string>.*)"$') {
$readXMLFile.error = "Error loading XML; $($matches['string'])" # removes 'Exception calling "Load" with "1" argument(s):' from message
} else {
$readXMLFile.error = "Error loading XML; $($_.Exception.Message)"
}
} catch {
$readXMLFile.error = "Error loading XML; $($_.Exception.FullName) - $($_.Exception.Message)"
} finally {
if ($xmlreader) {
$xmlreader.Dispose()
}
if ($readXMLFile.error) {
$readXMLFile.xml = $null
} else {
$readXMLFile.xml = $importFile
}
}
Write-Host "readXMLFile: $($readXMLFile.xml.GetType().FullName)"
return $readXMLFile
}
}
Will echo
readXMLFile: System.Xml.XmlDocument
pxT_DefinitionsMigrater: System.String
I am contemplating moving the XML load code into the ctor since that is the only place it is needed, but the fact that the XML is getting cast to string seems like something I need to understand. What is causing this? And what do I need to do to make this work with the method, should I want to?

How to convert a string value to a dynamic data type in PowerShell?

Is it possible to assign a string value to a variable of a different type given that the data type is not known in advance? For example, in the sample below, how do I update the values of the $values hash without changing their data types:
$values = #{
"Boolean" = $true
"Int" = 5
"DateTime"= (Get-Date)
"Array" = #("A", "B", "C")
}
$stringValues = #{
"Boolean" = 'false'
"Int" = '10'
"DateTime"= '2019-01-02 14:45:59.146'
"Array" = '#("X", "Y", "Z")'
}
"INITIAL VALUES:"
foreach ($key in $values.Keys) {
($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}
"`nUPDATING..."
foreach ($key in $stringValues.Keys) {
$values[$key] = $stringValues[$key]
}
"`nUPDATED VALUES:"
foreach ($key in $values.Keys) {
($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}
OUTPUT:
INITIAL VALUES:
DateTime = 04/23/2019 16:54:13 (System.DateTime)
Array = A B C (System.Object[])
Boolean = True (System.Boolean)
Int = 5 (System.Int32)
UPDATING...
UPDATED VALUES:
DateTime = 2019-01-02 14:45:59.146 (System.String)
Array = #("X", "Y", "Z") (System.String)
Boolean = false (System.String)
Int = 10 (System.String)
I need the updated values to match the original data types and not just get converted to System.String.
I am flexible on the contents of the strings. E.g. a string holding a boolean false value may be $false/false/[boolean]false/[boolean]$false/etc or a string holding an array may use a different formatting (basically, whatever is easier to convert the string to a proper data type).
In essence, I want to simulate whatever the ConvertFrom-Json cmdlet does when it sets the object property from a JSON string, only in my case, I do not have a JSON structure.
(In case someone wonders what I'm trying to do: I am trying to add an INI file parser to my ConfigFile module, and no, I cannot just use a hash to return the INI settings; I need to load the values into the corresponding PSVariables and for this to work, I need to convert strings to proper data types.)
So you want to cast/convert the new value to the type of the old value.
The idea needs to cast from a variable,
here is a related question powershell-type-cast-using-type-stored-in-variable
The answer suggest:
You can roughly emulate a cast using the following method:
[System.Management.Automation.LanguagePrimitives]::ConvertTo($Value, $TargetType)
The following changed routine shows: it isn't that simple, especially when the new data needs overloads/other parameters in the conversion.
"UPDATING..."
foreach ($key in $stringValues.Keys) {
$values[$key] = [System.Management.Automation.LanguagePrimitives]::ConvertTo(
$stringValues[$key], $values[$key].gettype())
}
My German locale error message:
Ausnahme beim Aufrufen von "ConvertTo" mit 2 Argument(en): "Der Wert "2019-01-02 14:45.59.146" kann nicht in den Typ
"System.DateTime" konvertiert werden. Fehler: "Die Zeichenfolge wurde nicht als gültiges DateTime erkannt.""
In Zeile:2 Zeichen:5
+ $values[$key] = [System.Management.Automation.LanguagePrimitives] ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : PSInvalidCastException
And the unsufficient result:
DateTime = 04/24/2019 09:49:19 (System.DateTime)
Array = #("X", "Y", "Z") (System.Object[])
Boolean = True (System.Boolean)
Int = 10 (System.Int32)
You may elaborate yourself on this idea, handling old types/new data more individually.
You can use a custom class in lieu of a hashtable; unlike hashtable keys, the properties of custom classes can be specifically typed.
With scalar values, you can then simply let PowerShell perform the from-string conversion for you - except that Boolean strings need special treatment (see comments in source code below).
With arrays, things are trickier. The solution below uses [System.Management.Automation.PSParser]::Tokenize() to parse the string, but is currently limited to recognizing string and number literals.
Note: It is tempting to use Invoke-Expression on the entire array, but that would be a security risk, because it opens to the door to arbitrary code execution. While there are legitimate uses - such as on a string known to represent a number below - Invoke-Expression should generally be avoided.
(If you don't want to define classes, you can derive the types from the values of hashtable $values and use [System.Management.Automation.LanguagePrimitives]::ConvertTo() to convert the strings to those types, as shown in LotPings' answer, though note that arrays and Booleans still need special treatment as shown below.)
# Define a custom [Values] class
# with specifically typed properties.
class Values {
[bool] $Boolean
[int] $Int
[datetime] $DateTime
[Array] $Array
}
# Instantiate a [Values] instance
$values = [Values] #{
Boolean = $true
Int = 5
DateTime= (Get-Date)
Array = #("A", "B", "C")
}
$stringValues = #{
Boolean = 'false'
Int = '10'
DateTime = '2019-01-02 14:45:59.146'
Array = '#("X", "Y", "Z")'
}
"INITIAL VALUES:"
foreach ($key in $values.psobject.properties.Name) {
($key + " = " + $values.$key + " (" + $values.$key.GetType().FullName + ")")
}
""
"UPDATING..."
foreach ($key in $stringValues.Keys) {
switch ($key) {
'Array' {
# Parse the string representation.
# Assumptions and limitations:
# The array is flat.
# It is sufficient to only support string and numeric constants.
# No true syntax validation is needed.
$values.$key = switch ([System.Management.Automation.PSParser]::Tokenize($stringValues[$key], [ref] $null).Where( { $_.Type -in 'String', 'Number' })) {
{ $_.Type -eq 'String' } { $_.Content; continue }
{ $_.Type -eq 'Number' } { Invoke-Expression $_Content; continue }
}
continue
}
'Boolean' { # Boolean scalar
# Boolean strings need special treatment, because PowerShell considers
# any nonempty string $true
$values.$key = $stringValues[$key] -notin 'false', '$false'
continue
}
default { # Non-Boolean scalar
# Let PowerShell perform automatic from-string conversion
# based on the type of the [Values] class' target property.
$values.$key = $stringValues[$key]
}
}
}
""
"UPDATED VALUES:"
foreach ($key in $values.psobject.properties.Name) {
($key + " = " + $values.$key + " (" + $values.$key.GetType().FullName + ")")
}
This yields:
INITIAL VALUES:
Boolean = True (System.Boolean)
Int = 5 (System.Int32)
DateTime = 04/24/2019 14:45:29 (System.DateTime)
Array = A B C (System.Object[])
UPDATING...
UPDATED VALUES:
Boolean = True (System.Boolean)
Int = 10 (System.Int32)
DateTime = 01/02/2019 14:45:59 (System.DateTime)
Array = X Y Z (System.Object[])
Agreed on the Write-Host thing. It should really only be used to leverage color output and some specific format cases. Output to the screen is the default as you'll see in my response.
You could do the below, but that date string is a bit odd, well, for me, well, I've not seen anyone use that format. So, I modified it for US style, but change as needed for your language.
$values = #{
'Boolean' = $true
'Int' = 5
'DateTime'= (Get-Date)
'Array' = #('A', 'B', 'C')
}
$stringValues = #{
'Boolean' = 'false'
'Int' = '10'
'DateTime'= '2019-01-02 14:45:59'
'Array' = "#('X', 'Y', 'Z')"
}
'INITIAL VALUES:'
foreach ($key in $values.Keys)
{
"$key = $($values[$key]) $($values[$key].GetType())"
}
"`nUPDATING..."
foreach ($key in $stringValues.Keys)
{
switch ($key)
{
Boolean {[Boolean]$values[$key] = $stringValues['$'+$key]}
Int {[Int]$values[$key] = $stringValues[$key]}
DateTime {[DateTime]$values[$key] = $stringValues[$key]}
Array {[Array]$values[$key] = $stringValues[$key]}
default {'The value could not be determined.'}
}
}
"`nUPDATED VALUES:"
foreach ($key in $values.Keys)
{
"$key = $($values[$key]) $($values[$key].GetType())"
}
# Results
INITIAL VALUES:
DateTime = 04/24/2019 01:44:17 datetime
Array = A B C System.Object[]
Boolean = True bool
Int = 5 int
UPDATING...
UPDATED VALUES:
DateTime = 01/02/2019 14:45:59 datetime
Array = #("X", "Y", "Z") System.Object[]
Boolean = False bool
Int = 10 int
Thanks to #LotPings, #mklement0, and #postanote for giving me a few ideas, so here is the solution I will go with:
$values = #{
"Boolean" = $true
"Int" = 5
"DateTime"= (Get-Date)
"Array" = #("A", "B", "C")
}
$stringValues = #{
"Boolean" = 'false'
"Int" = '10'
"DateTime"= '2019-01-31 14:45:59.005'
"Array" = 'X,Y,Z'
}
"INITIAL VALUES:"
foreach ($key in $values.Keys) {
($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}
"`nUPDATING..."
foreach ($key in $stringValues.Keys) {
$value = $stringValues[$key]
if ($values[$key] -is [Array]) {
$values[$key] = $value -split ','
}
elseif (($values[$key] -is [Boolean]) -or ($values[$key] -is [Switch])) {
$values[$key] = $value -notin 'false', '$false', '0', ''
}
else {
$values[$key] = [System.Management.Automation.LanguagePrimitives]::ConvertTo($value, $values[$key].GetType())
}
}
"`nUPDATED VALUES:"
foreach ($key in $values.Keys) {
($key + " = " + $values[$key] + " (" + $values[$key].GetType().FullName + ")")
}
OUTPUT:
INITIAL VALUES:
DateTime = 04/25/2019 09:32:31 (System.DateTime)
Array = A B C (System.Object[])
Boolean = True (System.Boolean)
Int = 5 (System.Int32)
UPDATING...
UPDATED VALUES:
DateTime = 01/31/2019 14:45:59 (System.DateTime)
Array = X Y Z (System.String[])
Boolean = False (System.Boolean)
Int = 10 (System.Int32)
I adjusted the format of the array in the string value (which, as I mentioned in the question, was an option). The actual code that will use this will be a bit different, but the basic idea is here. The only caveat that I noticed is that the array data type gets changed from object[] to string[]. Ideally, I'd like to keep it as-is, but it would not change the functionality of the code, so it is fine. Thanks again to all for the ideas and corrections, and if you come up with better alternatives, feel free to post.

Problems returning hashtable

So if I have the following code:
function DoSomething {
$site = "Something"
$app = "else"
$app
return #{"site" = $($site); "app" = $($app)}
}
$siteInfo = DoSomething
$siteInfo["site"]
Why doesn't $siteInfo["site"] return "Something"?
I can state just....
$siteInfo
And it will return
else
Key: site
Value: Something
Name: site
Key: app
Value: else
Name: app
What am I missing?
In PowerShell, functions return any and every value that is returned by each line in the function; an explicit return statement is not needed.
The String.IndexOf() method returns an integer value, so in this example, DoSomething returns '2' and the hashtable as array of objects as seen with .GetType().
function DoSomething {
$site = "Something"
$app = "else"
$app.IndexOf('s')
return #{"site" = $($site); "app" = $($app)}
}
$siteInfo = DoSomething
$siteInfo.GetType()
The following example shows 3 ways to block unwanted output:
function DoSomething {
$site = "Something"
$app = "else"
$null = $app.IndexOf('s') # 1
[void]$app.IndexOf('s') # 2
$app.IndexOf('s')| Out-Null # 3
# Note: return is not needed.
#{"site" = $($site); "app" = $($app)}
}
$siteInfo = DoSomething
$siteInfo['site']
Here is an example of how to wrap multiple statements in a ScriptBlock to capture unwanted output:
function DoSomething {
# The Dot-operator '.' executes the ScriptBlock in the current scope.
$null = .{
$site = "Something"
$app = "else"
$app
}
#{"site" = $($site); "app" = $($app)}
}
DoSomething
#Rynant VERY helpful post, thank you for providing examples on hiding function output!
My proposed solution:
function DoSomething ($a,$b){
#{"site" = $($a); "app" = $($b)}
}
$c = DoSomething $Site $App