Powershell: how to implement standard switches? - debugging

For things like -WhatIf, we have $PSCmdlet.ShouldProcess() given to us by the [CmdletBinding] attribute. Are there other such tools or practices for implementing common command line arguments such as -Verbose, -Debug, -PassThru, etc?

Write-Debug and Write-Verbose handle the -Debug and -Verbose parameters automatically.
-PassThru isn't technically a common parameter, but you can implement it like:
function PassTest {
param(
[switch] $PassThru
)
process {
if($PassThru) {$_}
}
}
1..10|PassTest -PassThru
And this is an example of using your function's PassThru value on a cmdlet:
function Add-ScriptProperty {
param(
[string] $Name,
[ScriptBlock] $Value,
[switch] $PassThru
)
process{
# Use ":" to explicitly set the value on a switch parameter
$_| Add-Member -MemberType ScriptProperty -Name $Name -Value $Value `
-PassThru:$PassThru
}
}
$calc = Start-Process calc -PassThru|
Add-ScriptProperty -Name SecondsOld `
-Value {((Get-Date)-$this.StartTime).TotalSeconds} -PassThru
sleep 5
$calc.SecondsOld
Have a look at Get-Help about_CommonParameters for more information.

Related

How can avoid an error inline PowerShell script The process cannot access the file because it is being used by another process?

$signalsciencesAgent= Get-Item -Path "C:\Users\Shamim Reza\Desktop\zipfolderpath\sigsci-agent_latest.msi"
if (!(Test-Path $signalsciencesAgent.FullName)) {
throw "File '{0}' does not exist" -f $signalsciencesAgent.FullName
}
try {
$windowsInstaller = New-Object -com WindowsInstaller.Installer
$database = $windowsInstaller.GetType().InvokeMember(
"OpenDatabase", "InvokeMethod", $Null,
$windowsInstaller, #($signalsciencesAgent.FullName, 0)
)
$q = "SELECT Value FROM Property WHERE Property = 'ProductVersion'"
$View = $database.GetType().InvokeMember(
"OpenView", "InvokeMethod", $Null, $database, ($q)
)
$View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)
$record = $View.GetType().InvokeMember( "Fetch", "InvokeMethod", $Null, $View, $Null )
$signalsciencesagentversion = $record.GetType().InvokeMember( "StringData", "GetProperty", $Null, $record, 1 )
} catch {
throw "Failed to get MSI file version: {0}." -f $_
}
Finally
{
Remove-Item $signalsciencesAgent
}
Above script I use for view .msi file version it's working well but when I use another command like Remove-Item or Invoke-RestMethod Then getting this error how can I handle this error
Remove-Item : Cannot remove item C:\Users\Shamim Reza\Desktop\zipfolderpath\sigsci-agent_latest.msi: The process cannot access
the file 'C:\Users\Shamim Reza\Desktop\zipfolderpath\sigsci-agent_latest.msi' because it is being used by another process.
At line:30 char:1
+ Remove-Item $signalsciencesAgent
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\Users\Shamim...gent_latest.msi:FileInfo) [Remove-Item], IOException
+ FullyQualifiedErrorId : RemoveFileSystemItemIOError,Microsoft.PowerShell.Commands.RemoveItemCommand
It's becaeu the file is locked. Menaing still has a file handle to it. You need to release the lock.
This is not uncommon, PS notwithstanding, and there are lots of articles on the topic right here on Stackoverflow (use the search box to find them), as well as all over the internet. Use your favorite search engine to find them.
PowerShell 'because it is being used by another process'
For Example:
Because it is being used by another process: Why There Isn't a No-Dependency, One Line Powershell Solution
$MethodDefinition = #'
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
public static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);
'#
$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -Namespace 'Win32' -PassThru
# You may now call the CopyFile function
# Copy calc.exe to the user’s desktop
$Kernel32::CopyFile("$($Env:SystemRoot)\System32\calc.exe", "$($Env:USERPROFILE)\Desktop\calc.exe", $False)
Move-item: The process cannot access the file because it is being used by another process
Function Wait-FileUnlock{
Param(
[Parameter()]
[IO.FileInfo]$File,
[int]$SleepInterval=500
)
while(1){
try{
$fs=$file.Open('open','read', 'Read')
$fs.Close()
Write-Verbose "$file not open"
return
}
catch{
Start-Sleep -Milliseconds $SleepInterval
Write-Verbose '-'
}
}
}
If you do not close the stream you will have one big memory leak.
Here is a function I built, and call as needed to release stuff when I'm done with it. Of course, this is a complete clear/release of everything, so, you need to tweak it for your needs.
Function Clear-ResourceEnvironment
{
[CmdletBinding(SupportsShouldProcess)]
[Alias('cre')]
Param
(
[switch]$AdminCredStore
)
# Clear only variables created / used during the session
Compare-Object -ReferenceObject (Get-Variable) -DifferenceObject $AutomaticVariables -Property Name -PassThru |
Where -Property Name -ne 'AutomaticVariables' |
Remove-Variable -Verbose -Force -Scope 'global' -ErrorAction SilentlyContinue
Remove-Variable -Name AdminCredStore -Verbose -Force -ErrorAction SilentlyContinue
# Clear only modules loaded during the session
Compare-Object -ReferenceObject (Get-Module) -DifferenceObject $AutomaticVModules -Property Name -PassThru |
Where -Property Name -ne 'AutomaticVModules' |
Remove-Module -Force -ErrorAction SilentlyContinue
# Clear only Aliases loaded during the session
Compare-Object -ReferenceObject (Get-Alias) -DifferenceObject $AutomaticAliases -Property Name -PassThru |
Where -Property Name -ne 'AutomaticAliases' |
Remove-Alias -Force -ErrorAction SilentlyContinue
# Clear only functions loaded during the session
Compare-Object -ReferenceObject (Get-Command -CommandType Function) -DifferenceObject $AutomaticFunctions -Property Name -PassThru |
Where -Property Name -ne 'AutomaticFunctions' |
Remove-Item -Force -ErrorAction SilentlyContinue
# Clear all PSSessions
Get-PSSession |
Remove-PSSession -ErrorAction SilentlyContinue
# Clear static credential store, if switch is used
If ($AdminCredStore)
{Remove-Item -Path "$env:USERPROFILE\Documents\AdminCredSet.xml" -Force}
Else
{
Write-Warning -Message "
`n`t`tYou decided not to delete the custom Admin credential store.
This store is only valid for this host and and user $env:USERNAME"
}
Write-Warning -Message "
`n`t`tRemoving the displayed session specific variable and module objects"
# Clear instantiate reasource interop
$null = [System.Runtime.InteropServices.Marshal]::
ReleaseComObject([System.__ComObject]$Shell)
# instantiate .Net garbage collection
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}

PowerShell terminate all RDP sessions of a user

I need a script that terminates all RDP sessions of an AD user.
Only the username should be given, whereupon the script terminates all RDP sessions of this user (if necessary also enforces them).
Unfortunately, the Get-RDUserSession cmdlet does not work (the ConnectionBroker cannot be found).
Unfortunately, I cannot process the result of the CMD command qwinsta in PowerShell.
Any ideas or tips?
Thank you.
You can create custom objects from qwinsta's output, filter them and use rwinsta to kill the session.
Function Get-TSSessions
{
param (
[Parameter(Mandatory = $true, Position = 0 )]
[String]$ComputerName
) # End Parameter Block
qwinsta /server:$ComputerName |
ForEach-Object{
If($_ -notmatch "SESSIONNAME")
{
New-Object -TypeName PSObject -Property `
#{
"ID" = [Int]$_.SubString(41,05).Trim()
"ComputerName" = $Computer
"User" = $_.SubString(19,22).Trim()
"State" = $_.SubString(47,08).Trim()
}
}
}
} # End Function Get-TSSessions
Get-TSSessions -ComputerName <ServerName> |
Where-Object{$_.User -eq "SomeUser"} |
ForEach{ & "rwinsta /Server:$($_.ComputerName) $($_.ID)" }
Obviously, you can improve by wrapping up the rwinsta command in its own function. At the moment I only have reporting work written around this sort of thing, so in the spirit of answering the question without writing the whole thing, this should get you through.
Also, I believe there are a number of scripts and functions available for this on the PowerShell Gallery. In fact, I think there were functions Get/Stop-TerminalSession in the PowerShell Community Extensions, which you can install as a module.
param
(
[Parameter(Mandatory = $false,
HelpMessage = 'Specifies the user name (SamAccountName).',
DontShow = $false)]
[SupportsWildcards()]
[ValidateNotNullOrEmpty()]
[ValidateScript({
Import-Module -Name 'ActiveDirectory' -Force
if (Get-ADUser -Filter "sAMAccountName -eq '$_'") {
return $true
} else {
return $false
}
})]
[string]$Username = $env:USERNAME
)
$ErrorActionPreference = 'SilentlyContinue'
Import-Module -Name 'ActiveDirectory' -Force
foreach ($system in (Get-ADComputer -Filter ("Name -ne '$env:COMPUTERNAME' -and OperatingSystem -like 'Windows Server*'"))) {
[string]$system = $system.Name
$session = ((quser /server:$system | Where-Object {
$_ -match $Username
}) -split ' +')[3]
if ($session) {
logoff $session /server:$system
}
}

Retrieve a specific field from the result of Get-ItemProperty in Powershell

I've the below powershell script:
$registrypath = "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard"
$Name = "EnableVirtualizationBasedSecurity"
$ExpectedValue = "1"
$value = Get-ItemProperty -Path $registrypath -Name $Name
Write-Host($value)
Its output is:
#{EnableVirtualizationBasedSecurity=1; PSPath=Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard; PSParentPath=Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control; PSChildName=DeviceGuard; PSDrive=HKLM;
PSProvider=Microsoft.PowerShell.Core\Registry}
I want to get the value of EnableVirtualizationBasedSecurity field in it to a variable in my powershell script.
Like $SpecificFieldValue = $value.get(EnableVirtualizationBasedSecurity);
How do i do it in powershell ?
Get-ItemProperty offers you an PSCustomObject as response.
This means you can directly get the value of the property like this:
$value.EnableVirtualizationBasedSecurity
or directly save the Value in the Get-ItemProperty-call like this:
(Get-ItemProperty -Path $registrypath -Name $Name).EnableVirtualizationBasedSecurity
or like this
Get-ItemProperty -Path $registrypath -Name $Name | Select-Object -Expandproperty EnableVirtualizationBasedSecurity
I think the problem is, that you expect the response to be an hashtable instead of an PSCustomObject.
You can get informations about the ObjectType of an Response by just adding () around a call and calling the getType() methode:
(Get-ItemProperty -Path $registrypath -Name $Name).GetType()

PowerShell Could Not Find Item - Path With Spaces IOException

# ---------------------------------------------------------
# ScriptingGamesBeginnerEvent8_PS1.ps1
# ed wilson, msft 8/21/2009
# PS1 version of HSG-08-19-09 http://bit.ly/1d8Rww
#
# ---------------------------------------------------------
Param(
[string]$path = 'C:\',
[int]$first = 50
)# end param
# *** Function Here ***
function Get-DirSize ($path){
BEGIN {}
PROCESS{
$size = 0
$folders = #()
foreach ($file in (Get-ChildItem $path -Force -ea SilentlyContinue)) {
if ($file.PSIsContainer) {
$subfolders = #(Get-DirSize $file.FullName)
$size += $subfolders[-1].Size
$folders += $subfolders
} else {
$size += $file.Length
}
}
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name Folder -Value (Get-Item $path).fullname
$object | Add-Member -MemberType NoteProperty -Name Size -Value $size
$folders += $object
Write-Output $folders
}
END {}
} # end function Get-DirSize
Function Get-FormattedNumber($size)
{
IF($size -ge 1GB)
{
"{0:n2}" -f ($size / 1GB) + " GigaBytes"
}
ELSEIF($size -ge 1MB)
{
"{0:n2}" -f ($size / 1MB) + " MegaBytes"
}
ELSE
{
"{0:n2}" -f ($size / 1KB) + " KiloBytes"
}
} #end function Get-FormattedNumber
# *** Entry Point to Script ***
if(-not(Test-Path -Path $path))
{
Write-Host -ForegroundColor red "Unable to locate $path"
Help $MyInvocation.InvocationName -full
exit
}
Get-DirSize -path $path |
Sort-Object -Property size -Descending |
Select-Object -Property folder, size -First $first |
Format-Table -Property Folder,
#{ Label="Size of Folder" ; Expression = {Get-FormattedNumber($_.size)} }
So I have this script which I got from
http://gallery.technet.microsoft.com/scriptcenter/36bf0988-867f-45be-92c0-f9b24bd766fb#content
I've been playing around with it and created a batch file to help handle the log output of this file and such. However, I'm noticing that paths with spaces in them don't get read. For example ..Documents\My Music
Get-Item : Could not find item C:\Users\MyUser\Documents\My Music.
At C:\test.ps1:32 char:80
+ $object | Add-Member -MemberType NoteProperty -Name Folder -Value (Get-It
em <<<< $path).fullname
+ CategoryInfo : ObjectNotFound: (C:\Users\MyUser\Documents\My
Music:String) [Get-Item], IOException
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetIt
emCommand
On the TechNet page for the code, someone brings the issue up but no solution is given. I'm not sure how to fix it here. I've played with the $path argument, surrounding it in " " or ' ' and such.
Here is part of the batch file to execute it:
C:\WINDOWS\system32\WindowsPowerShell\v1.0\powershell.exe -noe -command "& 'C:\test.ps1' -path "'C:\Users\MyUser\'""
Might be a bit late for answer here, but, as Aaron mentioned, this is not due to spaces in the path.
If you read the documentation for Get-Item cmdlet, there is a -Force switch, which allows the cmdlet to get items that cannot otherwise be accessed, such as hidden items.
Moreover, it seems from your code that you are not expecting to pass a wildcard pattern to the cmdlet, so instead of (Get-Item $path).FullName you should use
(Get-Item -force -LiteralPath $path).FullName
That should resolve this issue.
It's not the spaces in the path. If it was, the error would say path C:\Users\MyUser\Documents\My couldn't be found. Get-ChildItem and Get-Item behave... strangely... with certain files/directories, returning errors like you're seeing. That's why Get-ChildItem has an -ErrorAction SilentlyContinue parameter on it. I would add the same to the call to Get-Item, i.e. change
(Get-Item $path).FullName
to
(Get-Item $path -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName
or even forgo the call to Get-Item completely:
$path
As suggested by TheTrowser in a comment above: The problem may be resolved if you replace the double-quotes with single quotes surrounding the file directory with spaces. This is what solved it for me.
Using the command below didn't work for me.
get-item 'some path with two spaces.txt'
Enclosing the filename in double quotes within the single quotes, forces Powershell to use the filename as written.
get-item '"some path with two spaces.txt"'
Note: I'm totally cringing at my origal message (cleaned up a bit above). Below is a better example of what I was seeing.
$exampleA = "c:\temp\weird path\blah.txt"
$exampleB = "c:\temp\normal path\blah.txt"
# Works
get-item '$exampleA'
get-item $exampleB
# Fails
get-item $exampleA

Disable Automatic Updates with PowerShell

I would like to know how to disable Automatic Updates with PowerShell on a windows machine.
Thanks in advance!
Here's a couple functions to set and get Windows Update configurations
$SCRIPT:AutoUpdateNotificationLevels= #{
0="Not configured";
1="Disabled";
2="Notify before download";
3="Notify before installation";
4="Scheduled installation"
}
$SCRIPT:AutoUpdateDays=#{
0="Every Day";
1="Every Sunday";
2="Every Monday";
3="Every Tuesday";
4="Every Wednesday";
5="Every Thursday";
6="Every Friday";
7="Every Saturday"
}
Function Get-WindowsUpdateConfig
{
$AUSettings = (New-Object -com "Microsoft.Update.AutoUpdate").Settings
$AUObj = New-Object -TypeName System.Object
Add-Member -inputObject $AuObj -MemberType NoteProperty -Name "NotificationLevel" `
-Value $AutoUpdateNotificationLevels[$AUSettings.NotificationLevel]
Add-Member -inputObject $AuObj -MemberType NoteProperty -Name "UpdateDays" `
-Value $AutoUpdateDays[$AUSettings.ScheduledInstallationDay]
Add-Member -inputObject $AuObj -MemberType NoteProperty -Name "UpdateHour" `
-Value $AUSettings.ScheduledInstallationTime
Add-Member -inputObject $AuObj -MemberType NoteProperty -Name "Recommended updates" `
-Value $(IF ($AUSettings.IncludeRecommendedUpdates) {"Included"} else {"Excluded"})
$AuObj
}
Function Set-WindowsUpdateConfig
{
Param (
[Parameter()]
[ValidateRange(0,4)]
[int]
$NotificationLevel ,
[Parameter()]
[ValidateRange(0,7)]
[int]
$Day,
[Parameter()]
[ValidateRange(0,24)]
[int]
$hour,
[Parameter()]
[bool]
$IncludeRecommended
)
$AUSettings = (New-Object -com "Microsoft.Update.AutoUpdate").Settings
if ($NotificationLevel) {$AUSettings.NotificationLevel =$NotificationLevel}
if ($Day) {$AUSettings.ScheduledInstallationDay =$Day}
if ($hour) {$AUSettings.ScheduledInstallationTime=$hour}
if ($IncludeRecommended) {$AUSettings.IncludeRecommendedUpdates=$IncludeRecommended}
$AUSettings.Save()
}
Here is a link and how to set the registry settings
# http://support.microsoft.com/kb/328010
New-Item HKLM:\SOFTWARE\Policies\Microsoft\Windows -Name WindowsUpdate
New-Item HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate -Name AU
New-ItemProperty HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU -Name NoAutoUpdate -Value 1
you can use CMD command:
sc stop wuauserv

Resources