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

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
}

Related

How to reverse traverse a nested hashtable in PowerShell

In a previous question, I was given a solution for finding the name of a key by its value. Unfortunately I neglected to consider the application of Security Groups
Old hashtable and working function
$Departments = #{
'Sales' = #{
'SAM' = 'Manager'
'SAP' = 'Person'
}
'IT' = #{
'ITM' = 'Manager'
'ITS' = 'Specialist'
'ITT' = 'Technician'
'ITC' = 'Consultant'
}
}
function Get-DepartmentOU {
Param (
[CmdletBinding()]
[Parameter(Mandatory = $true)]
[System.String]
$RoleCode
)
# Get the DictionaryEntry in the main Hashtable where the nested Hashtable value matches the role you are looking for.
$Department = $script:Departments.GetEnumerator() | Where-Object { $_.Value.ContainsKey($RoleCode) }
# Print the name of the DictionaryEntry (Your department) and retrieve the value from the Hashtable for the role.
"Department: $($Department.Name) Job Title: $($Department.Value[$RoleCode])"
}
$JobCode = "SAM"
$DepartmentInfo = Get-DepartmentOU -RoleCode $JobCode
$DepartmentInfo
OUTPUT: Department: Sales Job Title: Manager
The above works excellently, however I've now created a deeper hashtable and need to do the same thing, just another level to extract more information.
New hashtable
$Departments = #{
"Parts" = #{
"SG-Parts" = #{
"PAM" = "Parts Manager"
"PAA" = "Parts Advisor"
}
}
"Sales" = #{
"SG-Sales" = #{
"SAP" = "Sales Person"
"SAR" = "Receptionist"
}
"SG-Sales Managers" = #{
"SGM" = "General Manager"
"SAM" = "Sales Manager"
}
}
}
How should I change the working function to display the text contained in the key
OUTPUT: SG: SG-Sales Managers Department: Sales Job Title: Manager
This may help to visualize the data structure that #mathias-r.-jessen's 'flat role table/map' code generates:
$Rolemap = #{
"PAC" = #{
"Title" = "Parts Consultant"
"SG" = "SG-Parts"
"Department" = "Parts"
}
"PAA" = #{
"Title" = "Parts Advisor"
"SG" = "SG-Parts"
"Department" = "Parts"
}
"SGM" = #{
"Title" = "General Manager"
"SG" = "SG-Sales Managers"
"Department" = "Sales"
}
}
The original data structure is quicker for humans to modify as needed as it mimics a common small-scale Active Directory setup.
When using the original data format, each new role addition only has to be added to the correct 'group' within the hashtable. However, reversing the "SAM" = "Sales Manager" to "Sales Manager"="SAM" may help for legibility and logic / structure.
I used $RoleMap |Format-Custom and some manual typing to build the generated table visualization text.
Create a new flat role table from your OU-like hashtable structure:
$RoleMap = #{}
foreach($departmentCode in $Departments.psbase.Keys){
foreach($sgCode in $Departments[$departmentCode].psbase.Keys){
foreach($roleCode in $Departments[$departmentCode][$sgCode].psbase.Keys){
# Create a summary object that includes all 3 pieces of information
# Store in role table and use the "role code" as the key
$RoleMap[$roleCode] = [pscustomobject]#{
Title = $Departments[$departmentCode][$sgCode][$roleCode]
SG = $sgCode
Department = $departmentCode
}
}
}
}
Now you can avoid ...GetEnumerator() | Where-Object { ... } completely when resolving the role code:
function Get-DepartmentOU {
param(
[CmdletBinding()]
[Parameter(Mandatory = $true)]
[string]
$RoleCode
)
if($script:RoleMap.Contains($RoleCode)){
# `Where-Object` no longer needed!
$roleDescription = $script:RoleMap[$RoleCode]
"SG: $($roleDescription.SG) Department: $($roleDescription.Name) Job Title: $($roleDescription.Title)"
}
}

Is there another way to ask for credentials in Powershell but Get-Credential?

My Powershell Script asks for credentials using the following syntax, and it works all right:
$credentials = (Get-Credential -Message "Enter Password")
But the problem is that the window that pops to enter the user name and password has very little space and it is uncomfortable for the user to enter large user names (large server name backslash large user names), and that windows does not allow resizing.
I would like to give my users the ability to enter usernames and passwords in another bigger window.
Any Idea?
I had something similar to this so I thought why not share it. Give it a try, it does not look as pretty as Get-Credential but you can resize it.
It will return a PSCredential object, same as Get-Credential.
The OK button only becomes Enabled if Username and Password have text.
using namespace System.Windows.Forms
using namespace System.Drawing
Add-Type -AssemblyName System.Windows.Forms, System.Drawing
Add-Type -TypeDefinition '
public class DPIAware {
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
}
'
[System.Windows.Forms.Application]::EnableVisualStyles()
[void] [DPIAware]::SetProcessDPIAware()
function Get-CustomCredential {
[Application]::EnableVisualStyles()
#$DPI = [math]::round([dpi]::scaling(), 2) * 100
$bounds = [Screen]::PrimaryScreen.WorkingArea
#$bounds.Width = $bounds.Width / 100 * $DPI
#$bounds.Height = $bounds.Height / 100 * $DPI
$mainForm = [Form]#{
StartPosition = 'CenterScreen'
FormBorderStyle = 'Sizable'
Text = 'Get Custom Credential'
WindowState = 'Normal'
KeyPreview = $true
Font = [Font]::new('Calibri', 11, [FontStyle]::Regular)
Icon = [Icon]::ExtractAssociatedIcon((Get-Process -Id $PID).Path)
MinimumSize = [Size]::new($bounds.Width / 3.5, $bounds.Height / 4)
MaximumSize = [Size]::new($bounds.Width, $bounds.Height / 4)
MaximizeBox = $false
}
$mainForm.Size = $mainForm.MinimumSize
$credentialMsg = [Label]#{
Location = [Point]::new(10, 10)
Size = [Size]::new($mainForm.Width - 30, 30)
Text = 'Supply values for the following parameters:'
}
$mainForm.Controls.Add($credentialMsg)
$userLbl = [Label]#{
Location = [Point]::new(10, 60)
Size = [Size]::new(120, 30)
Text = 'Username'
}
$mainForm.Controls.Add($userLbl)
$userTxtBox = [TextBox]#{
Location = [Point]::new($userLbl.Width + 10, 60)
Size = [Size]::new($mainForm.Width - 160, 60)
}
$mainForm.Controls.Add($userTxtBox)
$passwordLbl = [Label]#{
Location = [Point]::new(10, $userLbl.Location.Y + 40)
Size = [Size]::new(120, 30)
Text = 'Password'
}
$mainForm.Controls.Add($passwordLbl)
$passwordTxtBox = [TextBox]#{
Location = [Point]::new($passwordLbl.Width + 10, $userTxtBox.Location.Y + 40)
Size = [Size]::new($mainForm.Width - 160, 60)
UseSystemPasswordChar = $true
Anchor = 'top, left'
}
$mainForm.Controls.Add($passwordTxtBox)
$cancelBtn = [Button]#{
Location = [Point]::new($mainForm.Width - 130, $passwordTxtBox.Location.Y + 50)
Size = [Size]::new(100, 45)
Text = '&Cancel'
Anchor = 'right, bottom'
}
$cancelBtn.Add_Click({
$mainForm.DialogResult = 'Cancel'
})
$mainForm.Controls.Add($cancelBtn)
$okBtn = [Button]#{
Location = [Size]::new($cancelBtn.Location.X - $cancelBtn.Width - 5, $passwordTxtBox.Location.Y + 50)
Size = $cancelBtn.Size
Text = '&OK'
Anchor = $cancelBtn.Anchor
Enabled = $false
}
$okBtn.Add_Click({
$mainForm.DialogResult = 'OK'
})
$mainForm.Controls.Add($okBtn)
$okBtnEnableEvent = {
if([string]::IsNullOrWhiteSpace($userTxtBox.Text) -or [string]::IsNullOrWhiteSpace($passwordTxtBox.Text)) {
$okBtn.Enabled = $false
return
}
$okBtn.Enabled = $True
}
$userTxtBox.Add_TextChanged($okBtnEnableEvent)
$passwordTxtBox.Add_Textchanged($okBtnEnableEvent)
$mainForm.Add_Resize({
$userTxtBox.Size = [Size]::new($this.Width - 160, 60)
$passwordTxtBox.Size = [Size]::new($this.Width - 160, 60)
})
$mainForm.AcceptButton = $okBtn
$mainForm.CancelButton = $cancelBtn
$mainForm.Add_Shown({ $this.Activate() })
if('OK' -eq $mainForm.ShowDialog()) {
$passw = ConvertTo-SecureString $passwordTxtBox.Text.Trim() -AsPlainText -Force
[System.Management.Automation.PSCredential]::new($userTxtBox.Text.Trim(), $passw)
}
$mainForm.Dispose()
}
$creds = Get-CustomCredential
Props to mklement0 for his nice feedback and help with code improvements.

How to identify the default audio device in Powershell?

I am looking for a solution to get the default audio device via Powershell.
In best case, it could work via embedded C#-code to directly use IMMDeviceEnumerator::GetDefaultAudioEndpoint (see here IMMDeviceEnumertor).
But if it is easier to get this via RegKeys, then this is also OK.
I have seen a couple of code-snippets reading the keys from HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render or \Capture, but I still struggle to identify the DEFAULT device.
It seems, when I do modify the order of devices, then I can simply search for active devices (DeviceState=1) and then sort by the values "Level:0", "Level:1" and "Level:2", but the level-values are not available on a system, where the user has not modied the order manually. What is the sort-criteria in such case?
This is the code-snippet to solve it via RegKeys, but as mentioned - not working for all situations:
$regAudio = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio"
$nameId = "{b3f8fa53-0004-438e-9003-51a46e139bfc},6"
$classId = "{a45c254e-df1c-4efd-8020-67d146a850e0},2"
$driverDetails = "{83da6326-97a6-4088-9453-a1923f573b29},3"
function get-DefaultDevice($type) {
$activeDevices = foreach($key in Get-ChildItem "$regAudio\$type\") {
foreach($item in Get-ItemProperty $key.PsPath) {
if ($item.DeviceState -eq $activeState) {$item}
}
}
$defaultDevice = $activeDevices | Sort-Object -Property "Level:0","Level:1","Level:2" | select -last 1
$details = Get-ItemProperty "$($defaultDevice.PSPath)\Properties"
$name = "$($details.$classId) ($($details.$nameId))"
return #{
name = $name
driver = $details.$driverDetails
}
}
$OsRender = get-DefaultDevice "Render"
$OsCapture = get-DefaultDevice "Capture"
Is there any way to get this info "in a smart way" (without any external DLLs, of course)?
Finally I figured it out and I am happy to share the working code-snippet:
cls
Add-Type #'
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDevice {
int a(); int o();
int GetId([MarshalAs(UnmanagedType.LPWStr)] out string id);
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IMMDeviceEnumerator {
int f();
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] class MMDeviceEnumeratorComObject { }
public static string GetDefault (int direction) {
var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
IMMDevice dev = null;
Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(direction, 1, out dev));
string id = null;
Marshal.ThrowExceptionForHR(dev.GetId(out id));
return id;
}
'# -name audio -Namespace system
function getFriendlyName($id) {
$reg = "HKLM:\SYSTEM\CurrentControlSet\Enum\SWD\MMDEVAPI\$id"
return (get-ItemProperty $reg).FriendlyName
}
$id0 = [audio]::GetDefault(0)
$id1 = [audio]::GetDefault(1)
write-host "Default Speaker: $(getFriendlyName $id0)"
write-host "Default Micro : $(getFriendlyName $id1)"
and in case you need a language-neutral international name of each device MMDEVICE-id and (optional) its driver then use this function:
# https://github.com/tpn/winsdk-10/blob/master/Include/10.0.16299.0/shared/devpkey.h
# https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/wasapi/mingw-include/mmdeviceapi.h
$regId = "{b3f8fa53-0004-438e-9003-51a46e139bfc},2"
$regName = "{b3f8fa53-0004-438e-9003-51a46e139bfc},6"
$regFormFactor = "{1da5d803-d492-4edd-8c23-e0c0ffee7f0e},0"
# https://learn.microsoft.com/en-us/windows/win32/api/mmdeviceapi/ne-mmdeviceapi-endpointformfactor
$formFactor = #(
"RemoteNetworkDevice",
"Speakers",
"LineLevel",
"Headphones",
"Microphone",
"Headset",
"Handset",
"UnknownDigitalPassthrough",
"SPDIF",
"DigitalAudioDisplayDevice",
"UnknownFormFactor"
)
function getInternationalNameAndDriver($id) {
$guid = $id.Substring(17)
$subKey = #("Render","Capture")[[int]::Parse($id[5])]
$reg = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\$subKey\$guid\Properties"
$details = get-ItemProperty $reg -ea 0
if ($details) {
$id = $details.$regId.subString(4)
$name = $details.$regName
$form = $formFactor[$details.$regFormFactor]
$hardware = get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Enum\$id"
$regDrv = $hardware.Driver
$driver = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Class\$regDrv"
$drvName = $driver.DriverDesc
$drvVersion = $driver.DriverVersion
}
return "$form ($name), driver: $drvName $drvVersion"
}
this gives you an output like this:
Default Speaker: Speakers (Realtek Audio), driver: Realtek Audio 6.0.1.6127
Default Micro : Microphone (Logitech BRIO), driver: USB Audio Device 10.0.22000.653

how to create conditional arguments in a function?

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
}

Is it possible to update a row in MS Access from PowerShell?

I have the following PowerShell code which adds a new line of data into a MS Access database (based on user input) and works perfectly.
if ($NewAccounts ="Y") {
$cursor = 3
$lock = 3
$Ado = New-Object -ComObject ADODB.Connection
$recordset = New-Object -ComObject ADODB.Recordset
$Ado.Open("Provider = Microsoft.ACE.OLEDB.12.0;Data Source=$Source")
$query = "Select * from [Sheet1]"
$recordset.Open($query, $ado, $cursor, $lock)
$recordset.AddNew()
$recordset.Fields.Item("Account") = $AccName
$recordset.Fields.Item("Serial") = $CGBSerial
$recordset.Fields.Item("SAExpiry") = $SAEDate.ToString("dd/MM/yyyy")
$recordset.Fields.Item("SAValidatedPerson") = $SAPerson
$recordset.Fields.Item("DataCollection") = $DCRun
$recordset.Fields.Item("DataCollectionDate") = $DCRunDate
$recordset.Fields.Item("DataCollectionPerson") = $DCPerson
$recordset.Fields.Item("Version") = $Version
$recordset.Fields.Item("VersionDateValidated") = Get-Date -Format d
$recordset.Fields.Item("VersionValidatedPerson") = $logontrim
$recordset.Update()
$recordset.Close()
$ado.Close()
}
However, I cannot seem to update a row in the database that already exists. Is it possible to update a row, rather than creating an entirely new row?
$recordset.AddNew() appends a new empty record to the recordset. To update an existing record you need to navigate to the record you want to modify first, and then change the values of that record.
$recordset.Open($query, $ado, $cursor, $lock)
while ($recordset.Fields.Item('Account').Value -ne $AccName) {
$recordset.MoveNext()
}
$recordset.Fields.Item('Serial') = $CGBSerial
...
$recordset.Update()
$recordset.Close()
However, you can't use MoveNext() with a static cursor, so you need to change the cursor type to adOpenForwardOnly ($cursor = 0).
Alternatively you could use a prepared statement:
$cn = New-Object -ComObject 'ADODB.Connection'
$cn.ConnectionString = "..."
$cn.Open()
$cmd = New-Object -ComObject 'ADODB.Command'
$cmd.CommandText = 'UPDATE [Sheet1] SET Serial=?, SAExpiry=?, ... WHERE Account=?'
$cmd.Parameters.Append($cmd.CreateParameter('#p1', 200, 1, 50, $CGBSerial))
$cmd.Parameters.Append($cmd.CreateParameter('#p2', 7, 1, $null, $SAEDate))
...
$cmd.Execute()
$cn.Close()