Issues running a Powershell script without running a VB script first - powershell

I was looking for a solution to pin a shortcut or program to the task in win 10 with PS. I found Pin program to taskbar using PS in Windows 10. The VB Script works,
If WScript.Arguments.Count < 1 Then WScript.Quit
'----------------------------------------------------------------------
Set objFSO = CreateObject("Scripting.FileSystemObject")
objFile = WScript.Arguments.Item(0)
sKey1 = "HKCU\Software\Classes\*\shell\{:}\\"
sKey2 = Replace(sKey1, "\\", "\ExplorerCommandHandler")
'----------------------------------------------------------------------
With WScript.CreateObject("WScript.Shell")
KeyValue = .RegRead("HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer" & _
"\CommandStore\shell\Windows.taskbarpin\ExplorerCommandHandler")
.RegWrite sKey2, KeyValue, "REG_SZ"
With WScript.CreateObject("Shell.Application")
With .Namespace(objFSO.GetParentFolderName(objFile))
With .ParseName(objFSO.GetFileName(objFile))
.InvokeVerb("{:}")
End With
End With
End With
.Run("Reg.exe delete """ & Replace(sKey1, "\\", "") & """ /F"), 0, True
End With
'----------------------------------------------------------------------
I can invoke VB script from PS but a helpful person converted the script to PS
Param($Target)
$KeyPath1 = "HKCU:\SOFTWARE\Classes"
$KeyPath2 = "*"
$KeyPath3 = "shell"
$KeyPath4 = "{:}"
$ValueName = "ExplorerCommandHandler"
$ValueData =
(Get-ItemProperty `
("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\" + `
"CommandStore\shell\Windows.taskbarpin")
).ExplorerCommandHandler
$Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)
$Key3 = $Key2.CreateSubKey($KeyPath3, $true)
$Key4 = $Key3.CreateSubKey($KeyPath4, $true)
$Key4.SetValue($ValueName, $ValueData)
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
$Item.InvokeVerb("{:}")
$Key3.DeleteSubKey($KeyPath4)
if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0) {
$Key2.DeleteSubKey($KeyPath3)
}
However this PS script will not run unless the VB script has been ran at least one time. Is there a way to make the PS script work without having to run the VB script?
The error I get when trying to run the PS script without running the VB script at least once before it:
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:41 char:5
+ $Key3 = $Key2.CreateSubKey($KeyPath3, $true)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:42 char:5
+ $Key4 = $Key3.CreateSubKey($KeyPath4, $true)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:43 char:5
+ $Key4.SetValue($KeyValue, $ValueData)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
You cannot call a method on a null-valued expression.
At \\server\Utilities\TaskbarPin.ps1:50 char:5
+ $Key3.DeleteSubKey($KeyPath4)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
I do not get an error after using the VB script once to do the task.

You should not have been impacted by this this way.
The code works as designed, but you have to call the path the exe fully.
I just converted it to a function and it is successful with no other dependencies.
Function Add-AppToTaskbar
{
[cmdletbinding()]
Param
(
[string]$Target
)
$KeyPath1 = "HKCU:\SOFTWARE\Classes"
$KeyPath2 = "*"
$KeyPath3 = "shell"
$KeyPath4 = "{:}"
$ValueName = "ExplorerCommandHandler"
$ValueData =
(Get-ItemProperty `
("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\" + `
"CommandStore\shell\Windows.taskbarpin")
).ExplorerCommandHandler
$Key2 = (Get-Item $KeyPath1).OpenSubKey($KeyPath2, $true)
$Key3 = $Key2.CreateSubKey($KeyPath3, $true)
$Key4 = $Key3.CreateSubKey($KeyPath4, $true)
$Key4.SetValue($ValueName, $ValueData)
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
$Item.InvokeVerb("{:}")
$Key3.DeleteSubKey($KeyPath4)
if ($Key3.SubKeyCount -eq 0 -and $Key3.ValueCount -eq 0)
{$Key2.DeleteSubKey($KeyPath3)}
}
Add-AppToTaskbar -Target 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
BTW, these pinned things live in two places on your system:
Here:
$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
Registry:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband
Both are required.
Update based on OP's comment
I just ran this locally and remotely, both are successful. See results below.
The local host I am using - WS2012R2 set as a workstation role
I don't have any W10 systems in my lab. The earlier test was on a local W10 host.
Executed in the console host, ISE and VSCode.
PS C:\Windows\system32> $env:COMPUTERNAME
LabWS01
# PS Version
PS C:\Windows\system32> $PSVersionTable
Name Value
---- -----
PSVersion 4.0
WSManStackVersion 3.0
SerializationVersion 1.1.0.1
CLRVersion 4.0.30319.42000
BuildVersion 6.3.9600.18968
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion 2.2
# the current user profile pinned location filtered for notepad*
PS C:\Windows\system32> Get-ChildItem -Path "$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\Notepad*"
# Tested path to remote share
PS C:\Windows\system32> Test-path -Path '\\Server\ShareName\Add-AppToTaskbar.ps1'
True
# Ran the script from that remote share
PS C:\Windows\system32> \\Server\ShareName\Add-AppToTaskbar.ps1 'c:\windows\notepad.exe'
or this way...
Start-process -FilePath Powershell -ArgumentList '\\Server\ShareName\Add-AppToTaskbar.ps1 -Target C:\Windows\notepad.exe'
# Review pinned item location, filtered for notepad*
PS C:\Windows\system32> Get-ChildItem -Path "$env:USERPROFILE\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar\Notepad*"
Directory: C:\Users\Labuser001\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 8/9/2018 8:48 PM 791 Notepad.lnk
Shortcut shows pinned to taskbar.
So, this sounds environmental on your side. Now you can pin apps using GPO if this issue continues.

I've modified your function so that selectively pins or unpins items to the taskbar. Previously, the problem is that the pin command was not exclusive, it would unpin the application if it was already pinned. With further detection of what was pinned in a binary registry value, it has been possible to determine that the item has already been pinned and it will not attempt to pin the item twice.
Set-AppPinTaskbarCsv is a function that was customized for our environment, I only include it as an example only, if someone wanted to roll this out in a login script to ensure the users have all of the apps they need pinned, it would need a good deal of modification and simplification. It has some functions which are not included that check group membership and reformat strings to expand variables, which are not required. After pinning the applications, it displays more reliably if explorer is restarted, and the Csv function will restart explorer if any items are pinned.
Function Set-PinTaskbar {
Param (
[parameter(Mandatory=$True, HelpMessage="Target item to pin")]
[ValidateNotNullOrEmpty()]
[string] $Target
,
[Parameter(Mandatory=$False, HelpMessage="Target item to unpin")]
[switch]$Unpin
)
If (!(Test-Path $Target)) {
Write-Warning "$Target does not exist"
Break
}
$Reg = #{}
$Reg.Key1 = "*"
$Reg.Key2 = "shell"
$Reg.Key3 = "{:}"
$Reg.Value = "ExplorerCommandHandler"
$Reg.Data = (Get-ItemProperty ("HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\CommandStore\shell\Windows.taskbarpin")).ExplorerCommandHandler
$Reg.Path1 = "HKCU:\SOFTWARE\Classes"
$Reg.Path2 = Join-Path $Reg.Path1 $Reg.Key1
$Reg.Path3 = Join-Path $Reg.Path2 $Reg.Key2
$Reg.Path4 = Join-Path $Reg.Path3 $Reg.Key3
If (!(Test-Path -LiteralPath $Reg.Path2)) {
New-Item -ItemType Directory -Path $Reg.Path1 -Name [System.Management.Automation.WildcardPattern]::Escape($Reg.Key1)
}
If (!(Test-Path -LiteralPath $Reg.Path3)) {
New-Item -ItemType Directory -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path2)) -Name $Reg.Key2
}
If (!(Test-Path -LiteralPath $Reg.Path4)) {
New-Item -ItemType Directory -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path3)) -Name $Reg.Key3
}
Set-ItemProperty -Path ([System.Management.Automation.WildcardPattern]::Escape($Reg.Path4)) -Name $Reg.Value -Value $Reg.Data
$Shell = New-Object -ComObject "Shell.Application"
$Folder = $Shell.Namespace((Get-Item $Target).DirectoryName)
$Item = $Folder.ParseName((Get-Item $Target).Name)
# Registry key where the pinned items are located
$RegistryKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband"
# Binary registry value where the pinned items are located
$RegistryValue = "FavoritesResolve"
# Gets the contents into an ASCII format
$CurrentPinsProperty = ([system.text.encoding]::ASCII.GetString((Get-ItemProperty -Path $RegistryKey -Name $RegistryValue | Select-Object -ExpandProperty $RegistryValue)))
# Filters the results for only the characters that we are looking for, so that the search will function
[string]$CurrentPinsResults = $CurrentPinsProperty -Replace '[^\x20-\x2f^\x30-\x3a\x41-\x5c\x61-\x7F]+', ''
# Globally Unique Identifiers for common system folders, to replace in the pin results
$Guid = #{}
$Guid.FOLDERID_ProgramFilesX86 = #{
"ID" = "{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}"
"Path" = ${env:ProgramFiles(x86)}
}
$Guid.FOLDERID_ProgramFilesX64 = #{
"ID" = "{6D809377-6AF0-444b-8957-A3773F02200E}"
"Path" = $env:ProgramFiles
}
$Guid.FOLDERID_ProgramFiles = #{
"ID" = "{905e63b6-c1bf-494e-b29c-65b732d3d21a}"
"Path" = $env:ProgramFiles
}
$Guid.FOLDERID_System = #{
"ID" = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}"
"Path" = Join-Path $env:WINDIR "System32"
}
$Guid.FOLDERID_Windows = #{
"ID" = "{F38BF404-1D43-42F2-9305-67DE0B28FC23}"
"Path" = $env:WINDIR
}
ForEach ($GuidEntry in $Guid.Keys) {
$CurrentPinsResults = $CurrentPinsResults -replace $Guid.$GuidEntry.ID,$Guid.$GuidEntry.Path
}
$Split = $CurrentPinsResults -split ('C:')
$SplitOutput = #()
# Process each path entry, remove invalid characters, test to determine if the path is valid
ForEach ($Entry in $Split) {
If ($Entry.Substring(0,1) -eq '\') {
# Get a list of invalid path characters
$InvalidPathCharsRegEx = [IO.Path]::GetInvalidPathChars() -join ''
$InvalidPathChars = "[{0}]" -f [RegEx]::Escape($InvalidPathCharsRegEx)
$EntryProcessedPhase1 = "C:" + ($Entry -replace $InvalidPathChars)
$EntryProcessedPhase2 = $null
# Remove characters from the path until it is resolvable
ForEach ($Position in $EntryProcessedPhase1.Length .. 1) {
If (Test-Path $EntryProcessedPhase1.Substring(0,$Position)) {
$EntryProcessedPhase2 = $EntryProcessedPhase1.Substring(0,$Position)
Break
}
}
# If the path resolves, add it to the array of paths
If ($EntryProcessedPhase2) {
$SplitOutput += $EntryProcessedPhase2
}
}
}
$PinnedItems = #()
$Shell = New-Object -ComObject WScript.Shell
ForEach ($Path in $SplitOutput) {
# Determines if the entry in the registry is a link in the standard folder, if it is, resolve the path of the shortcut and add it to the array of pinnned items
If ((Split-Path $Path) -eq (Join-Path $env:USERPROFILE "AppData\Roaming\Microsoft\Internet Explorer\Quick Launch\User Pinned\TaskBar")) {
$Shell.CreateShortcut($Path).TargetPath
$PinnedItems += $Shell.CreateShortcut($Path).TargetPath
}
Else {
# If the link or executable is not in the taskbar folder, add it directly
$PinnedItems += $Path
}
}
# Unpin if the application is pinned
If ($Unpin.IsPresent) {
If ($PinnedItems -contains $Target) {
$Item.InvokeVerb("{:}")
Write-Host "Unpinning application $Target"
}
}
Else {
# Only pin the application if it hasn't been pinned
If ($PinnedItems -notcontains $Target) {
$Item.InvokeVerb("{:}")
Write-Host "Pinning application $Target"
}
}
# Remove the registry key and subkeys required to pin the application
If (Test-Path $Reg.Path3) {
Remove-Item -LiteralPath $Reg.Path3 -Recurse
}
}
Function Set-PinTaskbarCsv {
Param (
[Parameter(Mandatory=$true)]
$PinHashTable
,
[Parameter(Mandatory=$true)]
$UnpinHashTable
)
$Organization = "LIHC"
$RootRegistry = "HKCU:\Software\" + $Organization
$RootRegistryPinned = Join-Path $RootRegistry "Pinned"
# Unpin applications from taskbar
ForEach ($Entry in $UnpinHashTable.Keys) {
$Location = Format-VariablesString -String $UnpinHashTable.$Entry.Location
Add-Log "Taskbar app unpinned" $Location
Set-PinTaskbar -Target $Location -Unpin
}
# Pin applications to taskbar
$Groups = #("Group1","Group2","Group3","Group4","Group5")
ForEach ($Entry in $PinHashTable.Keys) {
$Entry
$Location = Format-VariablesString -String $PinHashTable.$Entry.Location
$ToTaskbar = [string]::IsNullOrWhiteSpace($PinHashTable.$Entry.Group1)
ForEach ($Group in $Groups) {
If (!([string]::IsNullOrWhiteSpace($PinHashTable.$Entry.$Group))) {
$ToTaskbar = (Get-UserGroups -Username $env:USERNAME -Group $PinHashTable.$Entry.$Group) -or $ToTaskbar
}
}
If (!([string]::IsNullOrWhiteSpace($PinHashTable.$Entry.TestPath))) {
$ToTaskbar = ((Test-Path $PinHashTable.$Entry.TestPath) -or (Test-Path $PinHashTable.$Entry.TestPath2)) -and $true
}
If ($ToTaskbar -and (Test-Path $Location) -and (!(Get-ItemProperty $RootRegistryPinned $Location -ErrorAction SilentlyContinue))) {
#Set-AppPinTaskbar -Application $Location
Set-PinTaskbar -Target $Location
Add-Log "Taskbar app Pinned" $Location
New-ItemProperty -Path $RootRegistryPinned -Name $Location 2>&1 > $null
$Status = $true
}
}
If ($Status) {
Get-Process -Name explorer | Stop-Process
Start-Process -FilePath explorer.exe
}
}

Related

Problem with downloading language local experience file using Powershell script

I use a fantastic script to download LXP files from microsoft and install them. This script works fine with languages like: en-us, pl-pl, zh-tw, zh-cn, fr-fr, ja-jp and maybe more BUT there is a strange issue with languages like italian (it-it), spanish (es-es) and portuguese (pt-pt). The script shows an error that the Invoke-WebRequest can't connect to the server and the rest of script fails because next variables are empty. Maybe sb will have an idea how to solve it.
There is a script I am talking about with es-es language:
# the language we want as new default
$language = "es-ES"
Start-Transcript -Path "$env:TEMP\LXP-SystemContext-Installer-$language.log" | Out-Null
# https://www.microsoft.com/de-de/p/deutsch-local-experience-pack/9p6ct0slw589
$applicationId = "9NWVGWLHPB1Z" # spanish
$webpage = Invoke-WebRequest -UseBasicParsing -Uri "https://bspmts.mp.microsoft.com/v1/public/catalog/Retail/Products/$applicationId/applockerdata"
$packageFamilyName = ($webpage | ConvertFrom-JSON).packageFamilyName
$skuId = 0016
$geoId = 217 # Spain
$inputLanguageID = "0c0a:0000040a" # es-es
# custom folder for temp scripts
"...creating custom temp script folder"
$scriptFolderPath = "$env:SystemDrive\ProgramData\CustomTempScripts"
New-Item -ItemType Directory -Force -Path $scriptFolderPath
"`n"
$languageXmlPath = $(Join-Path -Path $scriptFolderPath -ChildPath "MUI.xml")
# language xml definition for intl.cpl call to switch the language 'welcome screen' and 'new user' defaults
$languageXml = #"
<gs:GlobalizationServices xmlns:gs="urn:longhornGlobalizationUnattend">
<!-- user list -->
<gs:UserList>
<gs:User UserID="Current" CopySettingsToDefaultUserAcct="true" CopySettingsToSystemAcct="true"/>
</gs:UserList>
<!-- GeoID -->
<gs:LocationPreferences>
<gs:GeoID Value="$geoId"/>
</gs:LocationPreferences>
<!-- UI Language Preferences -->
<gs:MUILanguagePreferences>
<gs:MUILanguage Value="$language"/>
</gs:MUILanguagePreferences>
<!-- system locale -->
<gs:SystemLocale Name="$language"/>
<!-- input preferences -->
<gs:InputPreferences>
<gs:InputLanguageID Action="add" ID="$inputLanguageID" Default="true"/>
</gs:InputPreferences>
<!-- user locale -->
<gs:UserLocale>
<gs:Locale Name="$language" SetAsCurrent="true" ResetAllSettings="false"/>
</gs:UserLocale>
</gs:GlobalizationServices>
"#
$userConfigScriptPath = $(Join-Path -Path $scriptFolderPath -ChildPath "UserConfig.ps1")
# we could encode the complete script to prevent the escaping of $, but I found it easier to maintain
# to not encode. I do not have to decode/encode all the time for modifications.
$userConfigScript = #"
`$language = "$language"
Start-Transcript -Path "`$env:TEMP\LXP-UserSession-Config-`$language.log" | Out-Null
`$geoId = $geoId
"explicitly register the LXP in current user session (Add-AppxPackage -Register ...)"
`$appxLxpPath = (Get-AppxPackage | Where-Object Name -Like *LanguageExperiencePack`$language).InstallLocation
Add-AppxPackage -Register -Path "`$appxLxpPath\AppxManifest.xml" -DisableDevelopmentMode
# important for regional change like date and time...
"Set-WinUILanguageOverride = `$language"
Set-WinUILanguageOverride -Language `$language
"Set-WinUserLanguageList = `$language"
#Set-WinUserLanguageList `$language -Force
# changed handling due to new knowledge :-)
# https://oliverkieselbach.com/2021/01/13/company-portal-stuck-in-a-different-language/
`$OldList = Get-WinUserLanguageList
`$UserLanguageList = New-WinUserLanguageList -Language `$language
`$UserLanguageList += `$OldList | where { `$_.LanguageTag -ne `$language }
"Setting new user language list:"
`$UserLanguageList | select LanguageTag
""
"Set-WinUserLanguageList -LanguageList ..."
Set-WinUserLanguageList -LanguageList `$UserLanguageList -Force
"Set-WinSystemLocale = `$language"
Set-WinSystemLocale -SystemLocale `$language
"Set-Culture = `$language"
Set-Culture -CultureInfo `$language
"Set-WinHomeLocation = `$geoId"
Set-WinHomeLocation -GeoId `$geoId
Stop-Transcript -Verbose
"#
Set-WinUserLanguageList -LanguageList $language -Force
$userConfigScriptHiddenStarterPath = $(Join-Path -Path $scriptFolderPath -ChildPath "UserConfigHiddenStarter.vbs")
$userConfigScriptHiddenStarter = #"
sCmd = "powershell.exe -ex bypass -file $userConfigScriptPath"
Set oShell = CreateObject("WScript.Shell")
oShell.Run sCmd,0,true
"#
"...set reg key: BlockCleanupOfUnusedPreinstalledLangPacks = 1"
& REG add "HKLM\Software\Policies\Microsoft\Control Panel\International" /v BlockCleanupOfUnusedPreinstalledLangPacks /t REG_DWORD /d 1 /f /reg:64
"`n"
$packageName = "Microsoft.LanguageExperiencePack$language"
# check for installed Language Experience Pack (LXP), maybe already installed?
$status = $(Get-AppxPackage -AllUsers -Name $packageName).Status
# create CIM session here as we need it for Store updates as well
$session = New-CimSession
try {
if ($status -ne "Ok")
{
try {
$namespaceName = "root\cimv2\mdm\dmmap"
$omaUri = "./Vendor/MSFT/EnterpriseModernAppManagement/AppInstallation"
$newInstance = New-Object Microsoft.Management.Infrastructure.CimInstance "MDM_EnterpriseModernAppManagement_AppInstallation01_01", $namespaceName
$property = [Microsoft.Management.Infrastructure.CimProperty]::Create("ParentID", $omaUri, "string", "Key")
$newInstance.CimInstanceProperties.Add($property)
$property = [Microsoft.Management.Infrastructure.CimProperty]::Create("InstanceID", $packageFamilyName, "String", "Key")
$newInstance.CimInstanceProperties.Add($property)
$flags = 0
$paramValue = [Security.SecurityElement]::Escape($('<Application id="{0}" flags="{1}" skuid="{2}"/>' -f $applicationId, $flags, $skuId))
$params = New-Object Microsoft.Management.Infrastructure.CimMethodParametersCollection
$param = [Microsoft.Management.Infrastructure.CimMethodParameter]::Create("param", $paramValue, "String", "In")
$params.Add($param)
# we create the MDM instance and trigger the StoreInstallMethod to finally download the LXP
$instance = $session.CreateInstance($namespaceName, $newInstance)
$result = $session.InvokeMethod($namespaceName, $instance, "StoreInstallMethod", $params)
"...Language Experience Pack install process triggered via MDM/StoreInstall method"
}
catch [Exception] {
write-host $_ | out-string
$exitcode = 1
}
}
if (($result.ReturnValue.Value -eq 0) -or ($status -eq "Ok")) {
if ($status -ne "Ok")
{
"...busy wait until language pack found, max 15 min."
$counter=0
do {
Start-Sleep 10
$counter++
# check for installed Language Experience Pack (LXP)
$status = $(Get-AppxPackage -AllUsers -Name $packageName).Status
} while ($status -ne "Ok" -and $counter -ne 90) # 90x10s sleep => 900s => 15 min. max wait time!
}
# print some LXP package details for the log
Get-AppxPackage -AllUsers -Name $packageName
if ($status -eq "Ok") {
"...found Microsoft.LanguageExperiencePack$language with Status=Ok"
"...trigger install for language FOD packages"
# add Windows capabilities / FODs to avoid UAC dialog after restart
# Parameter -Online will reach out to Windows Update to get the latest correct source files
Get-WindowsCapability -Online | Where-Object Name -ilike "Language.*~~~$($language)~*" | ForEach-Object {
if ($_.State -ine "Installed") {
"Adding windows capability '$($_.Name)'..."
$_ | Add-WindowsCapability -Online | Out-Null
}
else {
"Windows capability '$($_.Name)' is already installed.`n"
}
}
"`n"
"...trigger language change for current user session via ScheduledTask = LXP-UserSession-Config-$language"
Out-File -FilePath $userConfigScriptPath -InputObject $userConfigScript -Encoding ascii
Out-File -FilePath $userConfigScriptHiddenStarterPath -InputObject $userConfigScriptHiddenStarter -Encoding ascii
# REMARK: usag of wscript as hidden starter may be blocked because of security restrictions like AppLocker, ASR, etc...
# switch to PowerShell if this represents a problem in your environment.
$taskName = "LXP-UserSession-Config-$language"
$action = New-ScheduledTaskAction -Execute "wscript.exe" -Argument "$userConfigScriptHiddenStarterPath"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId (Get-CimInstance –ClassName Win32_ComputerSystem | Select-Object -expand UserName)
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Register-ScheduledTask $taskName -InputObject $task
Start-ScheduledTask -TaskName $taskName
Start-Sleep -Seconds 30
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
# trigger 'LanguageComponentsInstaller\ReconcileLanguageResources' otherwise 'Windows Settings' need a long time to change finally
"...trigger ScheduledTask = LanguageComponentsInstaller\ReconcileLanguageResources"
Start-ScheduledTask -TaskName "\Microsoft\Windows\LanguageComponentsInstaller\ReconcileLanguageResources"
Start-Sleep 10
# change 'welcome screen' and 'new user' language defaults
"...trigger language change for welcome screen and new user defaults"
Out-File -FilePath $languageXmlPath -InputObject $languageXml -Encoding ascii
# check eventlog 'Microsoft-Windows-Internationl/Operational' for troubleshooting
& $env:SystemRoot\System32\control.exe "intl.cpl,,/f:`"$languageXmlPath`""
# trigger store updates, there might be new app versions due to the language change
"...trigger MS Store updates for app updates"
Get-CimInstance -Namespace "root\cimv2\mdm\dmmap" -ClassName "MDM_EnterpriseModernAppManagement_AppManagement01" | Invoke-CimMethod -MethodName "UpdateScanMethod"
$exitcode = 0
}
}
else {
$exitcode = 1
}
"...cleanup and finish"
try {
$session.DeleteInstance($namespaceName, $instance) | Out-Null
Remove-CimSession -CimSession $session
} catch {}
Remove-Item -Path $scriptFolderPath -Force -Recurse -ErrorAction SilentlyContinue
}
catch [Exception] {
# maybe a left over to clean, but prevent aditional errors and set exitcode
try {
$session.DeleteInstance($namespaceName, $instance) | Out-Null
} catch {}
$exitcode = 1
}
if ($exitcode -eq 0) {
$installed = 1
}
else {
$installed = 0
}
# IME app install runs by default in 32-bit so we write explicitly to 64-bit registry
& REG add "HKLM\Software\MyIntuneApps" /v "SetLanguage-$language" /t REG_DWORD /d $installed /f /reg:64 | Out-Null
Stop-Transcript -Verbose
exit $exitcode
I tried to find my mistakes in ID etc also checking if the requested language is available (and it is!). I tried to run the invoke-webrequest alone and it also works.
The error msg:
PS>TerminatingError(Invoke-WebRequest): "Unable to connect to remote server"
Invoke-WebRequest : Unable to connect to remote server
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:47 char:12
+ $webpage = Invoke-WebRequest -UseBasicParsing -Uri "https://bspmts.mp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebExcep
tion
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
Invoke-WebRequest : Unable to connect to remote server
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:47 char:12
+ $webpage = Invoke-WebRequest -UseBasicParsing -Uri "https://bspmts.mp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebExc
eption
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
ConvertFrom-Json : Cannot bind argument to parameter 'InputObject' because it is null.
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:48 char:34
+ $packageFamilyName = ($webpage | ConvertFrom-JSON).packageFamilyName
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [ConvertFrom-Json], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFromJs
onCommand
ConvertFrom-Json : Cannot bind argument to parameter 'InputObject' because it is null.
At C:\Windows\IMECache\ffa6ea3b-604e-41c3-98b9-dd30e2f9c714_1\languagechange.ps1:48 char:34
+ $packageFamilyName = ($webpage | ConvertFrom-JSON).packageFamilyName
+ ~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [ConvertFrom-Json], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFrom
JsonCommand

specified cast is not valid Powershell when changing automatically detect settings and auto config script for LAN settings in Internet Properties

I'm trying to enable and disable the automatically detect settings and automatic configuration script for LAN settings in Internet Explorer(IE). The script is working fine on windows 7 32,64 bit, Windows 10 32,64 bit but on windows 8 64bit it's throwing error specified cast is not valid when disabling the automatically detect settings and enabling/disabling automatic configuration script for LAN settings.
[string]$ProxyRegRoot = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
#this setting is affected by multiple options, so fetch once here
[string]$DefaultConnectionSettingsPath = (Join-Path $ProxyRegRoot 'Connections')
[byte[]]$DefaultConnectionSettings = Get-ItemProperty -Path $DefaultConnectionSettingsPath -Name 'DefaultConnectionSettings' | Select-Object -ExpandProperty 'DefaultConnectionSettings'
[byte]$MaskProxyEnabled = 2
[byte]$MaskUseAutomaticConfigurationScript = 4
[byte]$MaskAutomaticDetect = 8
[int]$ProxyConnectionSettingIndex = 8
#Auto-ConfigScript for LAN Settings
function Auto-ConfigScript {
#region config script
$UseAutomaticConfigurationScriptInput = Read-Host "Enable Automatic Configuration Script [y]Yes [n]No"
if ($UseAutomaticConfigurationScriptInput -eq "yes" -or $UseAutomaticConfigurationScriptInput -eq "y") {
$UseAutomaticConfigurationScript = $true
$UseAutomaticConfigurationScriptAddress = Read-Host "Enter Address "
Set-ItemProperty -Path $ProxyRegRoot -Name AutoConfigURL -Value $UseAutomaticConfigurationScriptAddress
$DefaultConnectionSettings[$ProxyConnectionSettingIndex] = $DefaultConnectionSettings[$ProxyConnectionSettingIndex] -bor $MaskUseAutomaticConfigurationScript
}
else {
$UseAutomaticConfigurationScript = $false
$DefaultConnectionSettings[$ProxyConnectionSettingIndex] = $DefaultConnectionSettings[$ProxyConnectionSettingIndex] -band (-bnot $MaskUseAutomaticConfigurationScript)
}
#endregion
#persist the updates made above
Set-ItemProperty -Path $DefaultConnectionSettingsPath -Name 'DefaultConnectionSettings' -Value $DefaultConnectionSettings
}
function Auto-DetectSettings {
#region auto detect
$AutomaticDetect = Read-Host "Enable Automatically detect settings [y]Yes [n]No"
if ($AutomaticDetect -eq $true -or $AutomaticDetect -eq "yes" -or $AutomaticDetect -eq "y") {
Set-ItemProperty -Path $ProxyRegRoot -Name AutoDetect -Value 1
$DefaultConnectionSettings[$ProxyConnectionSettingIndex] = $DefaultConnectionSettings[$ProxyConnectionSettingIndex] -bor $MaskAutomaticDetect
}
else {
Set-ItemProperty -Path $ProxyRegRoot -Name AutoDetect -Value 0
$DefaultConnectionSettings[$ProxyConnectionSettingIndex] = $DefaultConnectionSettings[$ProxyConnectionSettingIndex] -band (-bnot $MaskAutomaticDetect)
}
#endregion
#persist the updates made above
Set-ItemProperty -Path $DefaultConnectionSettingsPath -Name 'DefaultConnectionSettings' -Value $DefaultConnectionSettings
}
Error Traceback
Specified cast is not valid.
At C:\Users\afs.prithvi\Downloads\Internet Properties Win7 32-64 bit working.ps1:23 char:9
+ $DefaultConnectionSettings[$ProxyConnectionSettingIndex] = $DefaultConne ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], InvalidCastException
+ FullyQualifiedErrorId : System.InvalidCastException
Update
error is occuring at line number 23

How to Load Component Services/DCOM Config SnapIn

I have a PS script to do some DCOM configuration. It works fine as long as I have the Component Services/DCOM Config snapin loaded. I want to load that programmatically so I can do all of this as part of an install package. Does anyone know how to do it? I don't know the name of the snapin to add/import.
To load the snapin I run comexp.msc -32 and click Component Services, Computers, My Computer, DCOM Configuration.
Thanks
I faced a similar problem. I couldn't find a way of loading Component services on the DCOM Config spapIn. But I found a workaround to add the user the Default DCOM Launch and Activation permissions using this powershell script:
https://www.peppercrew.nl/index.php/2012/03/set-dcom-remote-access-via-powershell/
That way, you don't need to assign the user to that particular DCOM App.
Hope this help
This is the powershell script:
PARAM(
[string]$Principal = $(throw "`nMissing -Principal DOMAIN\Group"),
$Computers = $(throw "`nMissing -Computers ('server01','server02')"))
# USAGE:
# .\Set-RemotePermission-DCOM.ps1 -Principal "DOMAIN\" -Computers ('', '',...)
#
# EXAMPLE:
# .\Set-RemotePermission-DCOM.ps1 -Principal "DOMAIN\LG-Citrix-Admins" -Computers ('CTX_DC001', 'CTX_DC002')
#
# Inspired by Karl Mitschke's post:
# http://unlockpowershell.wordpress.com/2009/11/20/script-remote-dcom-wmi-access-for-a-domain-user/
#
# And inspired Brad Turner's post:
# http://social.technet.microsoft.com/Forums/en-US/ilm2/thread/5db2707c-87c9-4bb2-a0eb-912363e2814a/
function get-sid
{
PARAM ($DSIdentity)
$ID = new-object System.Security.Principal.NTAccount($DSIdentity)
return $ID.Translate( [System.Security.Principal.SecurityIdentifier] ).toString()
}
$sid = get-sid $Principal
#DefaultLaunchPermission - Local Launch, Remote Launch, Local Activation, Remote Activation
$DCOMSDDLDefaultLaunchPermission = "A;;CCDCLCSWRP;;;$sid"
#DefaultAccessPermision - Local Access, Remote Access
$DCOMSDDLDefaultAccessPermision = "A;;CCDCLC;;;$sid"
#PartialMatch
$DCOMSDDLPartialMatch = "A;;\w+;;;$sid"
foreach ($strcomputer in $computers)
{
write-host "`nWorking on $strcomputer with principal $Principal ($sid):"
# Get the respective binary values of the DCOM registry entries
$Reg = [WMIClass]"\\$strcomputer\root\default:StdRegProv"
$DCOMDefaultLaunchPermission = $Reg.GetBinaryValue(2147483650,"software\microsoft\ole","DefaultLaunchPermission").uValue
$DCOMDefaultAccessPermission = $Reg.GetBinaryValue(2147483650,"software\microsoft\ole","DefaultAccessPermission").uValue
# Convert the current permissions to SDDL
write-host "`tConverting current permissions to SDDL format..."
$converter = new-object system.management.ManagementClass Win32_SecurityDescriptorHelper
$CurrentDCOMSDDLDefaultLaunchPermission = $converter.BinarySDToSDDL($DCOMDefaultLaunchPermission)
$CurrentDCOMSDDLDefaultAccessPermission = $converter.BinarySDToSDDL($DCOMDefaultAccessPermission)
# Build the new permissions
if (($CurrentDCOMSDDLDefaultLaunchPermission.SDDL -match $DCOMSDDLPartialMatch) -and ($CurrentDCOMSDDLDefaultLaunchPermission.SDDL -notmatch $DCOMSDDLDefaultLaunchPermission))
{
$NewDCOMSDDLDefaultLaunchPermission = $CurrentDCOMSDDLDefaultLaunchPermission.SDDL -replace $DCOMSDDLPartialMatch, $DCOMSDDLDefaultLaunchPermission
}
else
{
$NewDCOMSDDLDefaultLaunchPermission = $CurrentDCOMSDDLDefaultLaunchPermission.SDDL + "(" + $DCOMSDDLDefaultLaunchPermission + ")"
}
if (($CurrentDCOMSDDLDefaultAccessPermission.SDDL -match $DCOMSDDLPartialMatch) -and ($CurrentDCOMSDDLDefaultAccessPermission.SDDL -notmatch $DCOMSDDLDefaultAccessPermision))
{
$NewDCOMSDDLDefaultAccessPermission = $CurrentDCOMSDDLDefaultAccessPermission.SDDL -replace $DCOMSDDLPartialMatch, $DCOMSDDLDefaultAccessPermision
}
else
{
$NewDCOMSDDLDefaultAccessPermission = $CurrentDCOMSDDLDefaultAccessPermission.SDDL + "(" + $DCOMSDDLDefaultAccessPermision + ")"
}
# Convert SDDL back to Binary
write-host "`tConverting SDDL back into binary form..."
$DCOMbinarySDDefaultLaunchPermission = $converter.SDDLToBinarySD($NewDCOMSDDLDefaultLaunchPermission)
$DCOMconvertedPermissionDefaultLaunchPermission = ,$DCOMbinarySDDefaultLaunchPermission.BinarySD
$DCOMbinarySDDefaultAccessPermission = $converter.SDDLToBinarySD($NewDCOMSDDLDefaultAccessPermission)
$DCOMconvertedPermissionsDefaultAccessPermission = ,$DCOMbinarySDDefaultAccessPermission.BinarySD
# Apply the changes
write-host "`tApplying changes..."
if ($CurrentDCOMSDDLDefaultLaunchPermission.SDDL -match $DCOMSDDLDefaultLaunchPermission)
{
write-host "`t`tCurrent DefaultLaunchPermission matches desired value."
}
else
{
$result = $Reg.SetBinaryValue(2147483650,"software\microsoft\ole","DefaultLaunchPermission", $DCOMbinarySDDefaultLaunchPermission.binarySD)
if($result.ReturnValue='0'){write-host " Applied DefaultLaunchPermission complete."}
}
if ($CurrentDCOMSDDLDefaultAccessPermission.SDDL -match $DCOMSDDLDefaultAccessPermision)
{
write-host "`t`tCurrent DefaultAccessPermission matches desired value."
}
else
{
$result = $Reg.SetBinaryValue(2147483650,"software\microsoft\ole","DefaultAccessPermission", $DCOMbinarySDDefaultAccessPermission.binarySD)
if($result.ReturnValue='0'){write-host " Applied DefaultAccessPermission complete."}
}
}
#----------------------------------------------------------------------------------------------------------
trap
{
$exMessage = $_.Exception.Message
if($exMessage.StartsWith("L:"))
{write-host "`n" $exMessage.substring(2) "`n" -foregroundcolor white -backgroundcolor darkblue}
else {write-host "`nError: " $exMessage "`n" -foregroundcolor white -backgroundcolor darkred}
Exit
}
#----------------------------------------------------------------------------------------------------------
I faced the same issue and, I believe, it's because there's no equivalent 64-bit registry entry so PowerShell doesn't see it. Launching mmc compexp.msc /32 and expanding DCOM Config seems to create the entry in the background.
The work-around is to manually add the 64-bit AppID yourself which is simply done by the following code,
$appGUID = 'YOUR_APPNAME_OR_GUID'
New-PSDrive -PSProvider Registry -Name HKCR -Root HKEY_CLASSES_ROOT
New-Item -Path HKCR:\AppID\$appGUID -Value $appGUID
#New-Item -Path HKCR:\Wow6432Node\AppID\$appGUID -Value $appGUID
Remove-PSDrive HKCR
I've left the 32-bit location in the above code too although that should already exist. Once you run the above then PowerShell should be able to see the COM component,
Get-WMIObject -query ('SELECT * FROM Win32_DCOMApplicationSetting WHERE AppID = "' + $appGUID + '"') -EnableAllPrivileges
Hope this helps someone as it was driving me bananas for hours!

Get datetaken attribute on file

I am trying to write a script that will get the DATETAKEN attribute from a photo and create a folder structure based on that and move the file to this new location. I have found scripts on google that I'm trying to use but when I running it, it returns:
PS C:\Temp> C:\Temp\MovePhoto.ps1
GAC Version Location
--- ------- -------- True v4.0.30319
C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Drawing\v4.0_4.0.0....
Move-Item : The process cannot access the file because it is being
used by another process. At C:\Temp\MovePhoto.ps1:43 char:5
+ Move-Item $FileFullName "$FileDirFull\$FileBaseNameNU"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\NoBackup\TES...RA\IMG_1372.JPG:FileInfo) [Move- Item],
IOException
+ FullyQualifiedErrorId : MoveFileInfoItemIOError,Microsoft.PowerShell.Commands.MoveItemCommand
If I do the script without the SystemDrawing line it works. But then I can't get the DATETAKEN attribute. I just can't figure out what I am missing.
Here is the script
[reflection.assembly]::LoadWithPartialName("System.Drawing")
$FileAll = (Get-ChildItem $SourcePath -Recurse | where {!$_.psiscontainer} | Select-Object Name,Fullname,BaseName,Extension,CreationTime,LastWriteTime,Length,#{Name="MD5";Expression={Get-Md5Hash $_.fullname}} | group MD5 | Where {$_.Count -gt 1 } | %{$_.Group} | sort MD5)
foreach ($File in $FileAll) {
$FileBaseName = $File.BaseName
$FileExtension = $File.Extension
$FileFullName = $File.FullName
$FileBaseNameNu = $FileBaseName + $FileExtension
$FileName = $File.Name
}
$foo = New-Object -TypeName System.Drawing.Bitmap -ArgumentList $FileFullName
$date = $foo.GetPropertyItem(36867).Value[0..9]
$arYear = [Char]$date[0],[Char]$date[1],[Char]$date[2],[Char]$date[3]
$arMonth = [Char]$date[5],[Char]$date[6]
$arDay = [Char]$date[8],[Char]$date[9]
$strYear = [String]::Join("",$arYear)
$strMonth = [String]::Join("",$arMonth)
$strDay = [String]::Join("",$arDay)
$DateTaken = $strYear + "-" + $strMonth + "-" + $strDay
$FileLastWriteTime = $File.LastWriteTime
$FileDirYear = $FileLastWriteTime.Year
$FileDirDate = $FileLastWriteTime.ToShortDateString()
$FileDirFull = "$DestinationPath\DUBLETTER\$FileDirYear\$DateTaken"
# Create destination path
if ((Test-Path $FileDirFull) -eq $false) {
New-Item -Path $FileDirFull -ItemType Directory
}
if (Test-Path (Join-Path $FileDirFull $File.Name)) {
$n = 0
while ((Test-Path (Join-Path $FileDirFull $FileBaseNameNU)) -eq $true){
$FileBaseNameNU = $FileBaseName + "-" + ++$n + $FileExtension
}
}
Move-Item $FileFullName "$FileDirFull\$FileBaseNameNU"
}
Can you try to replace
[reflection.assembly]::LoadWithPartialName("System.Drawing")
by
Add-Type -AssemblyName "system.drawing"
Forget it, your trouble is with your file C:\NoBackup\TES...RA\IMG_1372.JPG wich can't be moved because it's open (seems to be open for usage in $foo var). Try first to copy it. You perhaps can use $foo.Dispose() before Move-Item $FileFullName "$FileDirFull\$FileBaseNameNU"
I've done this in VBScript, using GetDetailsOf()... see MSDN
This can be done using PowerShell, see... Scripting Guys
There may be a .NET method way of doing this, i.e.: more "native" to PS.

What’s in your PowerShell `profile.ps1` file? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
What essential things (functions, aliases, start up scripts) do you have in your profile?
I often find myself needing needing some basic agregates to count/sum some things., I've defined these functions and use them often, they work really nicely at the end of a pipeline :
#
# useful agregate
#
function count
{
BEGIN { $x = 0 }
PROCESS { $x += 1 }
END { $x }
}
function product
{
BEGIN { $x = 1 }
PROCESS { $x *= $_ }
END { $x }
}
function sum
{
BEGIN { $x = 0 }
PROCESS { $x += $_ }
END { $x }
}
function average
{
BEGIN { $max = 0; $curr = 0 }
PROCESS { $max += $_; $curr += 1 }
END { $max / $curr }
}
To be able to get time and path with colors in my prompt :
function Get-Time { return $(get-date | foreach { $_.ToLongTimeString() } ) }
function prompt
{
# Write the time
write-host "[" -noNewLine
write-host $(Get-Time) -foreground yellow -noNewLine
write-host "] " -noNewLine
# Write the path
write-host $($(Get-Location).Path.replace($home,"~").replace("\","/")) -foreground green -noNewLine
write-host $(if ($nestedpromptlevel -ge 1) { '>>' }) -noNewLine
return "> "
}
The following functions are stolen from a blog and modified to fit my taste, but ls with colors is very nice :
# LS.MSH
# Colorized LS function replacement
# /\/\o\/\/ 2006
# http://mow001.blogspot.com
function LL
{
param ($dir = ".", $all = $false)
$origFg = $host.ui.rawui.foregroundColor
if ( $all ) { $toList = ls -force $dir }
else { $toList = ls $dir }
foreach ($Item in $toList)
{
Switch ($Item.Extension)
{
".Exe" {$host.ui.rawui.foregroundColor = "Yellow"}
".cmd" {$host.ui.rawui.foregroundColor = "Red"}
".msh" {$host.ui.rawui.foregroundColor = "Red"}
".vbs" {$host.ui.rawui.foregroundColor = "Red"}
Default {$host.ui.rawui.foregroundColor = $origFg}
}
if ($item.Mode.StartsWith("d")) {$host.ui.rawui.foregroundColor = "Green"}
$item
}
$host.ui.rawui.foregroundColor = $origFg
}
function lla
{
param ( $dir=".")
ll $dir $true
}
function la { ls -force }
And some shortcuts to avoid really repetitive filtering tasks :
# behave like a grep command
# but work on objects, used
# to be still be allowed to use grep
filter match( $reg )
{
if ($_.tostring() -match $reg)
{ $_ }
}
# behave like a grep -v command
# but work on objects
filter exclude( $reg )
{
if (-not ($_.tostring() -match $reg))
{ $_ }
}
# behave like match but use only -like
filter like( $glob )
{
if ($_.toString() -like $glob)
{ $_ }
}
filter unlike( $glob )
{
if (-not ($_.tostring() -like $glob))
{ $_ }
}
This iterates through a scripts PSDrive and dot-sources everything that begins with "lib-".
### ---------------------------------------------------------------------------
### Load function / filter definition library
### ---------------------------------------------------------------------------
Get-ChildItem scripts:\lib-*.ps1 | % {
. $_
write-host "Loading library file:`t$($_.name)"
}
To setup my Visual Studio build environment from PowerShell I took the VsVars32 from here. and use it all the time.
###############################################################################
# Exposes the environment vars in a batch and sets them in this PS session
###############################################################################
function Get-Batchfile($file)
{
$theCmd = "`"$file`" & set"
cmd /c $theCmd | Foreach-Object {
$thePath, $theValue = $_.split('=')
Set-Item -path env:$thePath -value $theValue
}
}
###############################################################################
# Sets the VS variables for this PS session to use
###############################################################################
function VsVars32($version = "9.0")
{
$theKey = "HKLM:SOFTWARE\Microsoft\VisualStudio\" + $version
$theVsKey = get-ItemProperty $theKey
$theVsInstallPath = [System.IO.Path]::GetDirectoryName($theVsKey.InstallDir)
$theVsToolsDir = [System.IO.Path]::GetDirectoryName($theVsInstallPath)
$theVsToolsDir = [System.IO.Path]::Combine($theVsToolsDir, "Tools")
$theBatchFile = [System.IO.Path]::Combine($theVsToolsDir, "vsvars32.bat")
Get-Batchfile $theBatchFile
[System.Console]::Title = "Visual Studio " + $version + " Windows Powershell"
}
start-transcript. This will write out your entire session to a text file. Great for training new hires on how to use Powershell in the environment.
My prompt contains:
$width = ($Host.UI.RawUI.WindowSize.Width - 2 - $(Get-Location).ToString().Length)
$hr = New-Object System.String #('-',$width)
Write-Host -ForegroundColor Red $(Get-Location) $hr
Which gives me a divider between commands that's easy to see when scrolling back. It also shows me the current directory without using horizontal space on the line that I'm typing on.
For example:
C:\Users\Jay ----------------------------------------------------------------------------------------------------------
[1] PS>
# ----------------------------------------------------------
# msdn search for win32 APIs.
# ----------------------------------------------------------
function Search-MSDNWin32
{
$url = 'http://search.msdn.microsoft.com/?query=';
$url += $args[0];
for ($i = 1; $i -lt $args.count; $i++) {
$url += '+';
$url += $args[$i];
}
$url += '&locale=en-us&refinement=86&ac=3';
Open-IE($url);
}
# ----------------------------------------------------------
# Open Internet Explorer given the url.
# ----------------------------------------------------------
function Open-IE ($url)
{
$ie = new-object -comobject internetexplorer.application;
$ie.Navigate($url);
$ie.Visible = $true;
}
I rock a few functions, and since I'm a module author I typically load a console and desperately need to know what's where.
write-host "Your modules are..." -ForegroundColor Red
Get-module -li
Die hard nerding:
function prompt
{
$host.UI.RawUI.WindowTitle = "ShellPower"
# Need to still show the working directory.
#Write-Host "You landed in $PWD"
# Nerd up, yo.
$Str = "Root#The Matrix"
"$str> "
}
The mandatory anything I can PowerShell I will functions go here...
# Explorer command
function Explore
{
param
(
[Parameter(
Position = 0,
ValueFromPipeline = $true,
Mandatory = $true,
HelpMessage = "This is the path to explore..."
)]
[ValidateNotNullOrEmpty()]
[string]
# First parameter is the path you're going to explore.
$Target
)
$exploration = New-Object -ComObject shell.application
$exploration.Explore($Target)
}
I am STILL an administrator so I do need...
Function RDP
{
param
(
[Parameter(
Position = 0,
ValueFromPipeline = $true,
Mandatory = $true,
HelpMessage = "Server Friendly name"
)]
[ValidateNotNullOrEmpty()]
[string]
$server
)
cmdkey /generic:TERMSRV/$server /user:$UserName /pass:($Password.GetNetworkCredential().Password)
mstsc /v:$Server /f /admin
Wait-Event -Timeout 5
cmdkey /Delete:TERMSRV/$server
}
Sometimes I want to start explorer as someone other than the logged in user...
# Restarts explorer as the user in $UserName
function New-Explorer
{
# CLI prompt for password
taskkill /f /IM Explorer.exe
runas /noprofile /netonly /user:$UserName explorer
}
This is just because it's funny.
Function Lock-RemoteWorkstation
{
param(
$Computername,
$Credential
)
if(!(get-module taskscheduler))
{
Import-Module TaskScheduler
}
New-task -ComputerName $Computername -credential:$Credential |
Add-TaskTrigger -In (New-TimeSpan -Seconds 30) |
Add-TaskAction -Script `
{
$signature = #"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"#
$LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru
$LockWorkStation::LockWorkStation() | Out-Null
} | Register-ScheduledTask TestTask -ComputerName $Computername -credential:$Credential
}
I also have one for me, since Win + L is too far away...
Function llm # Lock Local machine
{
$signature = #"
[DllImport("user32.dll", SetLastError = true)]
public static extern bool LockWorkStation();
"#
$LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru
$LockWorkStation::LockWorkStation() | Out-Null
}
A few filters? I think so...
filter FileSizeBelow($size){if($_.length -le $size){ $_ }}
filter FileSizeAbove($size){if($_.Length -ge $size){$_}}
I also have a few I can't post yet, because they're not done but they're basically a way to persist credentials between sessions without writing them out as an encrypted file.
Here's my not so subtle profile
#==============================================================================
# Jared Parsons PowerShell Profile (jaredp#rantpack.org)
#==============================================================================
#==============================================================================
# Common Variables Start
#==============================================================================
$global:Jsh = new-object psobject
$Jsh | add-member NoteProperty "ScriptPath" $(split-path -parent $MyInvocation.MyCommand.Definition)
$Jsh | add-member NoteProperty "ConfigPath" $(split-path -parent $Jsh.ScriptPath)
$Jsh | add-member NoteProperty "UtilsRawPath" $(join-path $Jsh.ConfigPath "Utils")
$Jsh | add-member NoteProperty "UtilsPath" $(join-path $Jsh.UtilsRawPath $env:PROCESSOR_ARCHITECTURE)
$Jsh | add-member NoteProperty "GoMap" #{}
$Jsh | add-member NoteProperty "ScriptMap" #{}
#==============================================================================
#==============================================================================
# Functions
#==============================================================================
# Load snapin's if they are available
function Jsh.Load-Snapin([string]$name) {
$list = #( get-pssnapin | ? { $_.Name -eq $name })
if ( $list.Length -gt 0 ) {
return;
}
$snapin = get-pssnapin -registered | ? { $_.Name -eq $name }
if ( $snapin -ne $null ) {
add-pssnapin $name
}
}
# Update the configuration from the source code server
function Jsh.Update-WinConfig([bool]$force=$false) {
# First see if we've updated in the last day
$target = join-path $env:temp "Jsh.Update.txt"
$update = $false
if ( test-path $target ) {
$last = [datetime] (gc $target)
if ( ([DateTime]::Now - $last).Days -gt 1) {
$update = $true
}
} else {
$update = $true;
}
if ( $update -or $force ) {
write-host "Checking for winconfig updates"
pushd $Jsh.ConfigPath
$output = #(& svn update)
if ( $output.Length -gt 1 ) {
write-host "WinConfig updated. Re-running configuration"
cd $Jsh.ScriptPath
& .\ConfigureAll.ps1
. .\Profile.ps1
}
sc $target $([DateTime]::Now)
popd
}
}
function Jsh.Push-Path([string] $location) {
go $location $true
}
function Jsh.Go-Path([string] $location, [bool]$push = $false) {
if ( $location -eq "" ) {
write-output $Jsh.GoMap
} elseif ( $Jsh.GoMap.ContainsKey($location) ) {
if ( $push ) {
push-location $Jsh.GoMap[$location]
} else {
set-location $Jsh.GoMap[$location]
}
} elseif ( test-path $location ) {
if ( $push ) {
push-location $location
} else {
set-location $location
}
} else {
write-output "$loctaion is not a valid go location"
write-output "Current defined locations"
write-output $Jsh.GoMap
}
}
function Jsh.Run-Script([string] $name) {
if ( $Jsh.ScriptMap.ContainsKey($name) ) {
. $Jsh.ScriptMap[$name]
} else {
write-output "$name is not a valid script location"
write-output $Jsh.ScriptMap
}
}
# Set the prompt
function prompt() {
if ( Test-Admin ) {
write-host -NoNewLine -f red "Admin "
}
write-host -NoNewLine -ForegroundColor Green $(get-location)
foreach ( $entry in (get-location -stack)) {
write-host -NoNewLine -ForegroundColor Red '+';
}
write-host -NoNewLine -ForegroundColor Green '>'
' '
}
#==============================================================================
#==============================================================================
# Alias
#==============================================================================
set-alias gcid Get-ChildItemDirectory
set-alias wget Get-WebItem
set-alias ss select-string
set-alias ssr Select-StringRecurse
set-alias go Jsh.Go-Path
set-alias gop Jsh.Push-Path
set-alias script Jsh.Run-Script
set-alias ia Invoke-Admin
set-alias ica Invoke-CommandAdmin
set-alias isa Invoke-ScriptAdmin
#==============================================================================
pushd $Jsh.ScriptPath
# Setup the go locations
$Jsh.GoMap["ps"] = $Jsh.ScriptPath
$Jsh.GoMap["config"] = $Jsh.ConfigPath
$Jsh.GoMap["~"] = "~"
# Setup load locations
$Jsh.ScriptMap["profile"] = join-path $Jsh.ScriptPath "Profile.ps1"
$Jsh.ScriptMap["common"] = $(join-path $Jsh.ScriptPath "LibraryCommon.ps1")
$Jsh.ScriptMap["svn"] = $(join-path $Jsh.ScriptPath "LibrarySubversion.ps1")
$Jsh.ScriptMap["subversion"] = $(join-path $Jsh.ScriptPath "LibrarySubversion.ps1")
$Jsh.ScriptMap["favorites"] = $(join-path $Jsh.ScriptPath "LibraryFavorites.ps1")
$Jsh.ScriptMap["registry"] = $(join-path $Jsh.ScriptPath "LibraryRegistry.ps1")
$Jsh.ScriptMap["reg"] = $(join-path $Jsh.ScriptPath "LibraryRegistry.ps1")
$Jsh.ScriptMap["token"] = $(join-path $Jsh.ScriptPath "LibraryTokenize.ps1")
$Jsh.ScriptMap["unit"] = $(join-path $Jsh.ScriptPath "LibraryUnitTest.ps1")
$Jsh.ScriptMap["tfs"] = $(join-path $Jsh.ScriptPath "LibraryTfs.ps1")
$Jsh.ScriptMap["tab"] = $(join-path $Jsh.ScriptPath "TabExpansion.ps1")
# Load the common functions
. script common
. script tab
$global:libCommonCertPath = (join-path $Jsh.ConfigPath "Data\Certs\jaredp_code.pfx")
# Load the snapin's we want
Jsh.Load-Snapin "pscx"
Jsh.Load-Snapin "JshCmdlet"
# Setup the Console look and feel
$host.UI.RawUI.ForegroundColor = "Yellow"
if ( Test-Admin ) {
$title = "Administrator Shell - {0}" -f $host.UI.RawUI.WindowTitle
$host.UI.RawUI.WindowTitle = $title;
}
# Call the computer specific profile
$compProfile = join-path "Computers" ($env:ComputerName + "_Profile.ps1")
if ( -not (test-path $compProfile)) { ni $compProfile -type File | out-null }
write-host "Computer profile: $compProfile"
. ".\$compProfile"
$Jsh.ScriptMap["cprofile"] = resolve-path ($compProfile)
# If the computer name is the same as the domain then we are not
# joined to active directory
if ($env:UserDomain -ne $env:ComputerName ) {
# Call the domain specific profile data
write-host "Domain $env:UserDomain"
$domainProfile = join-path $env:UserDomain "Profile.ps1"
if ( -not (test-path $domainProfile)) { ni $domainProfile -type File | out-null }
. ".\$domainProfile"
}
# Run the get-fortune command if JshCmdlet was loaded
if ( get-command "get-fortune" -ea SilentlyContinue ) {
get-fortune -timeout 1000
}
# Finished with the profile, go back to the original directory
popd
# Look for updates
Jsh.Update-WinConfig
# Because this profile is run in the same context, we need to remove any
# variables manually that we don't want exposed outside this script
i add this function so that i can see disk usage easily:
function df {
$colItems = Get-wmiObject -class "Win32_LogicalDisk" -namespace "root\CIMV2" `
-computername localhost
foreach ($objItem in $colItems) {
write $objItem.DeviceID $objItem.Description $objItem.FileSystem `
($objItem.Size / 1GB).ToString("f3") ($objItem.FreeSpace / 1GB).ToString("f3")
}
}
apropos.
Although I think this has been superseded by a recent or upcoming release.
##############################################################################
## Search the PowerShell help documentation for a given keyword or regular
## expression.
##
## Example:
## Get-HelpMatch hashtable
## Get-HelpMatch "(datetime|ticks)"
##############################################################################
function apropos {
param($searchWord = $(throw "Please specify content to search for"))
$helpNames = $(get-help *)
foreach($helpTopic in $helpNames)
{
$content = get-help -Full $helpTopic.Name | out-string
if($content -match $searchWord)
{
$helpTopic | select Name,Synopsis
}
}
}
I keep a little bit of everything. Mostly, my profile sets up all the environment (including calling scripts to set up my .NET/VS and Java development environment).
I also redefine the prompt() function with my own style (see it in action), set up several aliases to other scripts and commands. and change what $HOME points to.
Here's my complete profile script.
Set-PSDebug -Strict
You will benefit i you ever searched for a stupid Typo eg. outputting $varsometext instead $var sometext
##############################################################################
# Get an XPath Navigator object based on the input string containing xml
function get-xpn ($text) {
$rdr = [System.IO.StringReader] $text
$trdr = [system.io.textreader]$rdr
$xpdoc = [System.XML.XPath.XPathDocument] $trdr
$xpdoc.CreateNavigator()
}
Useful for working with xml, such as output from svn commands with --xml.
This creates a scripts: drive and adds it to your path. Note, you must create the folder yourself. Next time you need to get back to it, just type "scripts:" and hit enter, just like any drive letter in Windows.
$env:path += ";$profiledir\scripts"
New-PSDrive -Name Scripts -PSProvider FileSystem -Root $profiledir\scripts
This will add snapins you have installed into your powershell session. The reason you may want to do something like this is that it's easy to maintain, and works well if you sync your profile across multiple systems. If a snapin isn't installed, you won't see an error message.
---------------------------------------------------------------------------
Add third-party snapins
---------------------------------------------------------------------------
$snapins = #(
"Quest.ActiveRoles.ADManagement",
"PowerGadgets",
"VMware.VimAutomation.Core",
"NetCmdlets"
)
$snapins | ForEach-Object {
if ( Get-PSSnapin -Registered $_ -ErrorAction SilentlyContinue ) {
Add-PSSnapin $_
}
}
I put all my functions and aliases in separate script files and then dot source them in my profile:
. c:\scripts\posh\jdh-functions.ps1
The function to view the entire history of typed command (Get-History, and his alias h show default only 32 last commands):
function ha {
Get-History -count $MaximumHistoryCount
}
You can see my PowerShell profile at http://github.com/jamesottaway/windowspowershell
If you use Git to clone my repo into your Documents folder (or whatever folder is above 'WindowsPowerShell' in your $PROFILE variable), you'll get all of my goodness.
The main profile.ps1 sets the subfolder with the name Addons as a PSDrive, and then finds all .ps1 files underneath that folder to load.
I quite like the go command, which stores a dictionary of shorthand locations to visit easily. For example, go vsp will take me to C:\Visual Studio 2008\Projects.
I also like overriding the Set-Location cmdlet to run both Set-Location and Get-ChildItem.
My other favourite is being able to do a mkdir which does Set-Location xyz after running New-Item xyz -Type Directory.
amongst many other things:
function w {
explorer .
}
opens an explorer window in the current directory
function startover {
iisreset /restart
iisreset /stop
rm "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\*.*" -recurse -force -Verbose
iisreset /start
}
gets rid of everything in my temporary asp.net files (useful for working on managed code that has dependencies on buggy unmanaged code)
function edit($x) {
. 'C:\Program Files (x86)\Notepad++\notepad++.exe' $x
}
edits $x in notepad++
I actually keep mine on github.
Function funcOpenPowerShellProfile
{
Notepad $PROFILE
}
Set-Alias fop funcOpenPowerShellProfile
Only a sagaciously-lazy individual would tell you that fop is so much easier to type than Notepad $PROFILE at the prompt, unless, of course, you associate "fop" with a 17th century English ninny.
If you wanted, you could take it a step further and make it somewhat useful:
Function funcOpenPowerShellProfile
{
$fileProfileBackup = $PROFILE + '.bak'
cp $PROFILE $fileProfileBackup
PowerShell_ISE $PROFILE # Replace with Desired IDE/ISE for Syntax Highlighting
}
Set-Alias fop funcOpenPowerShellProfile
For satisfying survivalist-paranoia:
Function funcOpenPowerShellProfile
{
$fileProfilePathParts = #($PROFILE.Split('\'))
$fileProfileName = $fileProfilePathParts[-1]
$fileProfilePathPartNum = 0
$fileProfileHostPath = $fileProfilePathParts[$fileProfilePathPartNum] + '\'
$fileProfileHostPathPartsCount = $fileProfilePathParts.Count - 2
# Arrays start at 0, but the Count starts at 1; if both started at 0 or 1,
# then a -1 would be fine, but the realized discrepancy is 2
Do
{
$fileProfilePathPartNum++
$fileProfileHostPath = $fileProfileHostPath + `
$fileProfilePathParts[$fileProfilePathPartNum] + '\'
}
While
(
$fileProfilePathPartNum -LT $fileProfileHostPathPartsCount
)
$fileProfileBackupTime = [string](date -format u) -replace ":", ""
$fileProfileBackup = $fileProfileHostPath + `
$fileProfileBackupTime + ' - ' + $fileProfileName + '.bak'
cp $PROFILE $fileProfileBackup
cd $fileProfileHostPath
$fileProfileBackupNamePattern = $fileProfileName + '.bak'
$fileProfileBackups = #(ls | Where {$_.Name -Match $fileProfileBackupNamePattern} | `
Sort Name)
$fileProfileBackupsCount = $fileProfileBackups.Count
$fileProfileBackupThreshold = 5 # Change as Desired
If
(
$fileProfileBackupsCount -GT $fileProfileBackupThreshold
)
{
$fileProfileBackupsDeleteNum = $fileProfileBackupsCount - `
$fileProfileBackupThreshold
$fileProfileBackupsIndexNum = 0
Do
{
rm $fileProfileBackups[$fileProfileBackupsIndexNum]
$fileProfileBackupsIndexNum++;
$fileProfileBackupsDeleteNum--
}
While
(
$fileProfileBackupsDeleteNum -NE 0
)
}
PowerShell_ISE $PROFILE
# Replace 'PowerShell_ISE' with Desired IDE (IDE's path may be needed in
# '$Env:PATH' for this to work; if you can start it from the "Run" window,
# you should be fine)
}
Set-Alias fop funcOpenPowerShellProfile
Jeffrey Snover's Start-NewScope because re-launching the shell can be a drag.
I never got comfortable with the diruse options, so:
function Get-FolderSizes { # poor man's du
[cmdletBinding()]
param(
[parameter(mandatory=$true)]$Path,
[parameter(mandatory=$false)]$SizeMB,
[parameter(mandatory=$false)]$ExcludeFolders,
[parameter(mandatory=$false)][switch]$AsObject
) #close param
# http://blogs.technet.com/b/heyscriptingguy/archive/2013/01/05/weekend-scripter-sorting-folders-by-size.aspx
# uses Christoph Schneegans' Find-Files https://schneegans.de/windows/find-files/ because "gci -rec" follows junctions in "special" folders
$pathCheck = test-path $path
if (!$pathcheck) { Write-Error "Invalid path. Wants gci's -path parameter."; return }
if (!(Get-Command Find-Files)) { Write-Error "Required function Find-Files not found"; return }
$fso = New-Object -ComObject scripting.filesystemobject
$parents = Get-ChildItem $path -Force | where { $_.PSisContainer -and $ExcludeFolders -notContains $_.name -and !$_.LinkType }
$folders = Foreach ($folder in $parents)
{
$getFolder = $fso.getFolder( $folder.fullname.tostring() )
if (!$getFolder.Size)
{
#for "special folders" like appdata
# maybe "-Attributes !ReparsePoint" works in v6? https://stackoverflow.com/a/59952913/
# what about https://superuser.com/a/650476/ ?
# abandoned because it follows junctions, distorting results # $length = gci $folder.FullName -Recurse -Force -EA SilentlyContinue | Measure -Property Length -Sum
$length = Find-Files $folder.FullName -EA SilentlyContinue | Measure -Property Length -Sum -EA SilentlyContinue
$sizeMBs = "{0:N0}" -f ($length.Sum /1mb)
} #close if size property is null
else { $sizeMBs = "{0:N0}" -f ($getFolder.size /1mb) }
New-Object -TypeName psobject -Property #{
Name = $getFolder.Path
SizeMB = $sizeMBs
} #close new obj property
} #close foreach folder
#here's the output
$foldersObj = $folders | Sort #{E={[decimal]$_.SizeMB}} -Descending | ? {[Decimal]$_.SizeMB -gt $SizeMB}
if (!$AsObject) { $foldersObj | Format-Table -AutoSize } else { $foldersObj }
#calculate the total including contents
$sum = $folders | Select -Expand SizeMB | Measure -Sum | Select -Expand Sum
$sum += ( gci $path | where {!$_.psIsContainer} | Measure -Property Length -Sum | Select -Expand Sum ) / 1mb
$sumString = "{0:n2}" -f ($sum /1kb)
$sumString + " GB total"
} #end function
Set-Alias gfs Get-FolderSizes
function Find-Files
{
<# by Christoph Schneegans https://schneegans.de/windows/find-files/ - used in Get-FolderSizes aka gfs
.SYNOPSIS
Lists the contents of a directory. Unlike Get-ChildItem, this function does not recurse into symbolic links or junctions in order to avoid infinite loops.
#>
param (
[Parameter( Mandatory=$false )]
[string]
# Specifies the path to the directory whose contents are to be listed. By default, the current working directory is used.
$LiteralPath = (Get-Location),
[Parameter( Mandatory=$false )]
# Specifies a filter that is applied to each file or directory. Wildcards ? and * are supported.
$Filter,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if file objects should be returned. By default, all file system objects are returned.
$File = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if directory objects should be returned. By default, all file system objects are returned.
$Directory = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if reparse point objects should be returned. By default, all file system objects are returned.
$ReparsePoint = $true,
[Parameter( Mandatory=$false )]
[boolean]
# Specifies if the top directory should be returned. By default, all file system objects are returned.
$Self = $true
)
function Enumerate( [System.IO.FileSystemInfo] $Item ) {
$Item;
if ( $Item.GetType() -eq [System.IO.DirectoryInfo] -and ! $Item.Attributes.HasFlag( [System.IO.FileAttributes]::ReparsePoint ) ) {
foreach ($ChildItem in $Item.EnumerateFileSystemInfos() ) {
Enumerate $ChildItem;
}
}
}
function FilterByName {
process {
if ( ( $Filter -eq $null ) -or ( $_.Name -ilike $Filter ) ) {
$_;
}
}
}
function FilterByType {
process {
if ( $_.GetType() -eq [System.IO.FileInfo] ) {
if ( $File ) { $_; }
} elseif ( $_.Attributes.HasFlag( [System.IO.FileAttributes]::ReparsePoint ) ) {
if ( $ReparsePoint ) { $_; }
} else {
if ( $Directory ) { $_; }
}
}
}
$Skip = if ($Self) { 0 } else { 1 };
Enumerate ( Get-Item -LiteralPath $LiteralPath -Force ) | Select-Object -Skip $Skip | FilterByName | FilterByType;
} # end function find-files
The most valuable bit above is Christoph Schneegans' Find-Files https://schneegans.de/windows/find-files
For pointing at stuff:
function New-URLfile {
param( [parameter(mandatory=$true)]$Target, [parameter(mandatory=$true)]$Link )
if ($target -match "^\." -or $link -match "^\.") {"Full paths plz."; break}
$content = #()
$header = '[InternetShortcut]'
$content += $header
$content += "URL=" + $target
$content | out-file $link
ii $link
} #end function
function New-LNKFile {
param( [parameter(mandatory=$true)]$Target, [parameter(mandatory=$true)]$Link )
if ($target -match "^\." -or $link -match "^\.") {"Full paths plz."; break}
$WshShell = New-Object -comObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut($link)
$Shortcut.TargetPath = $target
$shortCut.save()
} #end function new-lnkfile
Poor man's grep? For searching large txt files.
function Search-TextFile {
param(
[parameter(mandatory=$true)]$File,
[parameter(mandatory=$true)]$SearchText
) #close param
if ( !(Test-path $File) )
{
Write-Error "File not found: $file"
return
}
$fullPath = Resolve-Path $file | select -Expand ProviderPath
$lines = [System.IO.File]::ReadLines($fullPath)
foreach ($line in $lines) { if ($line -match $SearchText) {$line} }
} #end function Search-TextFile
Set-Alias stf Search-TextFile
Lists programs installed on a remote computer.
function Get-InstalledProgram { [cmdletBinding()] #http://blogs.technet.com/b/heyscriptingguy/archive/2011/11/13/use-powershell-to-quickly-find-installed-software.aspx
param( [parameter(mandatory=$true)]$Comp,[parameter(mandatory=$false)]$Name )
$keys = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
TRY { $RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Comp) }
CATCH {
$rrSvc = gwmi win32_service -comp $comp -Filter {name='RemoteRegistry'}
if (!$rrSvc) {"Unable to connect. Make sure that this computer is on the network, has remote administration enabled, `nand that both computers are running the remote registry service."; break}
#Enable and start RemoteRegistry service
if ($rrSvc.State -ne 'Running') {
if ($rrSvc.StartMode -eq 'Disabled') { $null = $rrSvc.ChangeStartMode('Manual'); $undoMe2 = $true }
$null = $rrSvc.StartService() ; $undoMe = $true
} #close if rrsvc not running
else {"Unable to connect. Make sure that this computer is on the network, has remote administration enabled, `nand that both computers are running the remote registry service."; break}
$RegBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine,$Comp)
} #close if failed to connect regbase
$out = #()
foreach ($key in $keys) {
if ( $RegBase.OpenSubKey($Key) ) { #avoids errors on 32bit OS
foreach ( $entry in $RegBase.OpenSubKey($Key).GetSubkeyNames() ) {
$sub = $RegBase.OpenSubKey( ($key + '\' + $entry) )
if ($sub) { $row = $null
$row = [pscustomobject]#{
Name = $RegBase.OpenSubKey( ($key + '\' + $entry) ).GetValue('DisplayName')
InstallDate = $RegBase.OpenSubKey( ($key + '\' + $entry) ).GetValue('InstallDate')
Version = $RegBase.OpenSubKey( ($key + '\' + $entry) ).GetValue('DisplayVersion')
} #close row
$out += $row
} #close if sub
} #close foreach entry
} #close if key exists
} #close foreach key
$out | where {$_.name -and $_.name -match $Name}
if ($undoMe) { $null = $rrSvc.StopService() }
if ($undoMe2) { $null = $rrSvc.ChangeStartMode('Disabled') }
} #end function
Going meta, spreading the gospel, whatnot
function Copy-ProfilePS1 ($Comp,$User) {
if (!$User) {$User = $env:USERNAME}
$targ = "\\$comp\c$\users\$User\Documents\WindowsPowershell\"
if (Test-Path $targ)
{
$cmd = "copy /-Y $profile $targ"
cmd /c $cmd
} else {"Path not found! $targ"}
} #end function CopyProfilePS1
$MaximumHistoryCount=1024
function hist {get-history -count 256 | %{$_.commandline}}
New-Alias which get-command
function guidConverter([byte[]] $gross){ $GUID = "{" + $gross[3].ToString("X2") + `
$gross[2].ToString("X2") + $gross[1].ToString("X2") + $gross[0].ToString("X2") + "-" + `
$gross[5].ToString("X2") + $gross[4].ToString("X2") + "-" + $gross[7].ToString("X2") + `
$gross[6].ToString("X2") + "-" + $gross[8].ToString("X2") + $gross[9].ToString("X2") + "-" +`
$gross[10].ToString("X2") + $gross[11].ToString("X2") + $gross[12].ToString("X2") + `
$gross[13].ToString("X2") + $gross[14].ToString("X2") + $gross[15].ToString("X2") + "}" $GUID }
I keep my profile empty. Instead, I have folders of scripts I can navigate to load functionality and aliases into the session. A folder will be modular, with libraries of functions and assemblies. For ad hoc work, I'll have a script to loads aliases and functions. If I want to munge event logs, I'd navigate to a folder scripts\eventlogs and execute
PS > . .\DotSourceThisToLoadSomeHandyEventLogMonitoringFunctions.ps1
I do this because I need to share scripts with others or move them from machine to machine. I like to be able to copy a folder of scripts and assemblies and have it just work on any machine for any user.
But you want a fun collection of tricks. Here's a script that many of my "profiles" depend on. It allows calls to web services that use self signed SSL for ad hoc exploration of web services in development. Yes, I freely mix C# in my powershell scripts.
# Using a target web service that requires SSL, but server is self-signed.
# Without this, we'll fail unable to establish trust relationship.
function Set-CertificateValidationCallback
{
try
{
Add-Type #'
using System;
public static class CertificateAcceptor{
public static void SetAccept()
{
System.Net.ServicePointManager.ServerCertificateValidationCallback = AcceptCertificate;
}
private static bool AcceptCertificate(Object sender,
System.Security.Cryptography.X509Certificates.X509Certificate certificate,
System.Security.Cryptography.X509Certificates.X509Chain chain,
System.Net.Security.SslPolicyErrors policyErrors)
{
Console.WriteLine("Accepting certificate and ignoring any SSL errors.");
return true;
}
}
'#
}
catch {} # Already exists? Find a better way to check.
[CertificateAcceptor]::SetAccept()
}
Great question. Because I deal with several different PowerShell hosts, I do a little logging in each of several profiles, just to make the context of any other messages clearer. In profile.ps1, I currently only have that, but I sometimes change it based on context:
if ($PSVersionTable.PsVersion.Major -ge 3) {
Write-Host "Executing $PSCommandPath"
}
My favorite host is the ISE, in Microsoft.PowerShellIse_profile.ps1, I have:
if ($PSVersionTable.PsVersion.Major -ge 3) {
Write-Host "Executing $PSCommandPath"
}
if ( New-PSDrive -ErrorAction Ignore One FileSystem `
(Get-ItemProperty hkcu:\Software\Microsoft\SkyDrive UserFolder).UserFolder) {
Write-Host -ForegroundColor Green "PSDrive One: mapped to local OneDrive/SkyDrive folder"
}
Import-Module PSCX
$PSCX:TextEditor = (get-command Powershell_ISE).Path
$PSDefaultParameterValues = #{
"Get-Help:ShowWindow" = $true
"Help:ShowWindow" = $true
"Out-Default:OutVariable" = "0"
}
#Script Browser Begin
#Version: 1.2.1
Add-Type -Path 'C:\Program Files (x86)\Microsoft Corporation\Microsoft Script Browser\System.Windows.Interactivity.dll'
Add-Type -Path 'C:\Program Files (x86)\Microsoft Corporation\Microsoft Script Browser\ScriptBrowser.dll'
Add-Type -Path 'C:\Program Files (x86)\Microsoft Corporation\Microsoft Script Browser\BestPractices.dll'
$scriptBrowser = $psISE.CurrentPowerShellTab.VerticalAddOnTools.Add('Script Browser', [ScriptExplorer.Views.MainView], $true)
$scriptAnalyzer = $psISE.CurrentPowerShellTab.VerticalAddOnTools.Add('Script Analyzer', [BestPractices.Views.BestPracticesView], $true)
$psISE.CurrentPowerShellTab.VisibleVerticalAddOnTools.SelectedAddOnTool = $scriptBrowser
#Script Browser End
Of everything not already listed, Start-Steroids has to be my favorite, except for maybe Start-Transcript.
(http://www.powertheshell.com/isesteroids2-2/)