Wednesday, April 16, 2014

I just ran into a case where I needed to remove the PC side client from BMC TrackIt! from an entire domain.  I created a little script to do it for me.  It happily ran through and removed it from every workstation.  It's quick and dirty but it does work.


clear-host

function Run-RemoteCMD {
    param(
    [Parameter(Mandatory=$true,valuefrompipeline=$true)]
    [string]$computer,
    [string]$command
    )
    begin {
    #$computer
        #$command = "ipconfig /registerdns" #Read-Host " Enter command to run"
        [string]$cmd = "CMD.EXE /C " +$command
                        }
    process {
        $newproc = Invoke-WmiMethod -class Win32_process -name Create -ArgumentList ($cmd) -ComputerName $computer
        sleep -Seconds 1
        if ($newproc.ReturnValue -eq 0 )
                { Write-Output " Command $($command) invoked Sucessfully on $($computer)" }
                # if command is sucessfully invoked it doesn't mean that it did what its supposed to do
                #it means that the command only sucessfully ran on the cmd.exe of the server
                #syntax errors can occur due to user input
    }
}
      
$servers = Get-ADComputer -filter {*}

foreach ($server in $servers){
    Write-Host "========================================"
    $pc = $server.name
    if (Test-Connection -ComputerName $pc){
    Write-Host $pc
#------------------------------------------------------
    if (get-service -ComputerName $pc -Name TIRmtSvc){
        Write-Host "service found on $pc" -ForegroundColor yellow
        (get-service -ComputerName $pc -Name TIRmtSvc).Stop()
        While (!((Get-Service -ComputerName $pc -Name TIRmtSvc).Status = "Stopped")){
            sleep -Seconds 2
        }
    }else{
    Write-Host "service not found on $pc" -ForegroundColor cyan
}
#-------------------------------------------------------
    if (test-path -Path "\\$pc\c$\Windows\tiremote" ){
        Write-Host "folder found on $pc" -ForegroundColor yellow
        Run-RemoteCMD $pc "c:\Windows\tiremote\TIREMOTE.EXE /UNINSTALL -w 100000 > nul"
        sleep -Seconds 5
        if (get-service -ComputerName $pc -Name TIRmtSvc){
            Write-Host "service removed from $pc" -ForegroundColor green
        }else{
            Write-Host "service NOT removed from $pc" -ForegroundColor red
        }
        Run-RemoteCMD $pc "rd c:\Windows\TIREMOTE /s /q"
        if (test-path -Path "\\$pc\c$\Windows\tiremote" ){
            Write-Host "folder removed from $pc" -ForegroundColor green
        }else{
            Write-Host "folder NOT removed from $pc`ntrying again" -ForegroundColor red
            sleep -Seconds 5
            Run-RemoteCMD $pc "rd c:\Windows\TIREMOTE /s /q"
            if (test-path -Path "\\$pc\c$\Windows\tiremote" ){
                Write-Host "folder removed from $pc" -ForegroundColor green
            }else{
                Write-Host "folder NOT removed from $pc" -ForegroundColor red
            }
        }
    }else{
        Write-Host "folder not found on $pc" -ForegroundColor cyan
}
#---------------------------------------------------------
      
}else{
    Write-Host "no connection to $pc" -ForegroundColor Red  
}
}
Write-Host "completed"   

Friday, April 11, 2014

How to use PowerShell to find what computer a user is CURRENTLY logged into.

I recently had an issue where I needed the ability to identify the PC(s) where a user was logged into.  I found part of the answer here: 

http://deployhappiness.com/find-out-what-computer-a-user-logged-into/#comment-5068

I won't reprint it here but the jist is by using a logon/logoff VB script, and a GPO, you can populate a field in the Active Directory record of each PC in a domain.

That part works really well.  After that you would need to search AD for the username to identify the PC.

What I've done is created a PowerShell script with a GUI to do it for you.  Now, it's not finished yet, but it does work.  The code below is dirty and as soon as I get it the way I want I'll update this posting, but for now, it works pretty well.  Don't complain about bugs,I know it has them, I'm working on it.  And, yes, I have no doubt some things could be done better or more efficiently, feel free to post improvements.

What it has (or will have):
  • Self relocates to c:/scripts and drops an icon on the current users desktop for future launches
  • Self relocation is optional
  • Colorized output
  • GUI driven for simplicity
  • Various levels of debugging options
  • Reports items located in both AD "Description" field, and "Managed By" field.

Watch for word wrapping...

<#======================================================================================
         File Name : Find-User.ps1
   Original Author : Kenneth C. Mazie (kcmjr AT kcmjr.com)
                   :
       Description : Will locate the PC a user is currently logged on to.
                   :
             Notes : Normal operation is with no command line options. Requires PowerShell AD module.
                   :
          Warnings :
                   :
             Legal : Public Domain. Modify and redistribute freely. No rights reserved.
                   : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF
                   : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED.
                   :
           Credits : Code snippets and/or ideas came from many sources including but
                   :   not limited to the following:
                   : http://deployhappiness.com/find-out-what-computer-a-user-logged-into/#comment-5068
                   : https://stackoverflow.com/questions/2688547/muliple-foreground-colors-in-powershell-in-one-command
                   :
    Last Update by : Kenneth C. Mazie
   Version History : v1.0 - 04-11-14 - Original
    Change History : v1.1 - 00-00-00 -  
                   :
=======================================================================================#>

clear-host

#-------------------------------[ Begin ]---------------------------------------#>

$Debug1 = $false                                                                    #--[ Set to true to have extra messages to console during run  
$Debug2 = $false                                                                    #--[ Set to true to have extra messages to console during run  
$CurrentVersion = "1.0"
if (!(Get-Module -Name ActiveDirectory)){Import-Module ActiveDirectory}
$ErrorActionPreference = "SilentlyContinue"
$TargetPath = "C:\Scripts"                                                             #--[ Where the script "should" live
$ScriptFullName = ($MyInvocation.MyCommand).Name                                       #--[ EX: script.ps1
$ScriptFullPath = ($MyInvocation.MyCommand).Path                                       #--[ EX: C:\Scripts\script.ps1
$ScriptHomeDir = split-path -parent $ScriptFullPath                                    #--[ EX: C:\Scripts, where the script resides
$ScriptWorkingDir = $pwd.path                                                          #--[ EX: C:\Scripts or C:\temp, where the script executes from
$ScriptShortName = [system.io.path]::GetFilenameWithoutExtension($ScriptFullPath)      #--[ EX: script (no extention)
$osv = [environment]::osversion.VersionString                                          #--[ Get Windows version, not required, just for convenience
$windir = [System.Environment]::ExpandEnvironmentVariables("%WINDIR%")                 #--[ Get %windir% environment variable
#$windir = $env:windir                                                                #--[ Alternate format
$Home = [System.Environment]::ExpandEnvironmentVariables("%USERPROFILE%")           #--[ Get %userprofile% environment variable
$ThisComputer1 = [System.Net.Dns]::GetHostName()                                      #--[ NOTE: No reason for both forms, just because.
$ThisComputer2 = $env:COMPUTERNAME
$Relocate = $false                                                                  #--[ Change to $true to enable automatic relocation of the script

Function LocateUser {
  Param ([string]$TargetUser)
  $TargetUserSAM = $Null
  $TargetUserName = $Null
  $ResultA = @()
  $ResultB = @()
  $FullArray = @()
  $SubArray = @()
  $Computers = Get-ADComputer -Filter * -Properties ManagedBy,Description | Select Name, ManagedBy, Description | Sort -Property Name

  If ($TargetUser.contains(" ")){                                                   #--[ Get name or ID depending on what uer entered.
    [string]$TargetUserSAM = (Get-ADUser -Filter "Name -eq '$TargetUser'" | Select-Object name, samaccountname).samaccountname
    [string]$TargetUserName = $TargetUser.ToUpper()
  }Else{
    [string]$TargetUserName = (Get-ADUser -Filter "samAccountName -eq '$TargetUser'" | Select-Object name, samaccountname).name
    [string]$TargetUserSAM = $TargetUser.ToUpper()
  }
  If ($TargetUserName -eq ""){$TargetUserName = "UNKNOWN"}
  $TargetUser = (Get-Culture).TextInfo.ToTitleCase($TargetUser.ToLower())
  If ($Debug2){Write-Host "Checkpoint 1:  TUname= " $TargetUserName  " TUsam=" $TargetUserSAM " TU=" $TargetUser}

  ForEach ($Computer in $Computers){  #--[ Sort through AD Computers, extract ManagedBy and Description ]--
    $ManagedBy = ""
    $ManagedBy = (((($Computer.managedby).split(",")).split("="))[1]).ToUpper()
    $Description = ""
    $Description = ($Computer.description).ToUpper()
    #If ($Debug2){Write-Host "Checkpoint 2: Comp=" $Computer.name   " ManagedBy=" $ManagedBy   " Description="$Description }  #--[ computer, description, & who is its manager in AD
    If ($Debug2){color-Write Checkpoint 2: -Red Comp = $Computer.name -Yellow LoggedOn = $ManagedBy -Green Description = $Description }  #--[ computer, description, & who is its manager in AD
    $SubArray = $Computer.name,$ManagedBy,$Description
    $FullArray += ,$SubArray    #--[ Fullarray will contain Computer,ManagedBy,Description ]--
  }

  $count = $FullArray.count


 # write-host "B = " $ResultA.count
 # write-host "A = " $ResultB.count

   $ResultA += $TargetUserName + "  (" + $TargetUserSAM + ")`n`nLogged on to:`n"
  $ResultB += "Assigned to:  `n" #$TargetUserName + "  (" + $TargetUserSAM + ")`n"

  While ($count -gt 0){
    #If ($Debug2){Write-Host "Checkpoint 3:  $count   DetectedUser=" $FullArray[$count][1] " TargetUser="  $TargetUserName  "PC-Descr="  $Description}
    If ($Debug2){$FullArrayCount1 = $FullArray[$count][1];color-Write Checkpoint 3: -Magenta $count -Yellow DetectedUser = $FullArrayCount1 -Cyan TargetUser = $TargetUserName -Green PC-Descr = $Description}
  
    If ($FullArray[$count][1] -eq $TargetUserName){
     # If ($Debug2){Write-Host "Checkpoint 4:  FAcount$count-0=" $FullArray[$count][0] "  FAcount$count-1="  $FullArray[$count][1] " User=" $TargetUser }
      $ResultA += $FullArray[$count][0] + "`n"
    }
      
    If ($FullArray[$count][2] -eq $TargetUserName){
      $ResultB += $FullArray[$count][0] + "`n"
      }#write-host $Description}
  
    #If ($Debug2){$FullArrayCount1 = $FullArray[$count][1];$X = $count-1;Write-Host "Checkpoint 5:  FullArrayCount $X = " $FullArrayCount1}
    $count --
  }


  If ($Debug2){$x = $ResultA.count;color-Write Checkpoint 6: -Red ResultACount = $x (includes target)}
    If ($Debug2){color-Write -Red ResultBCount = $ResultB.count (includes target)}

  #write-host "A = " $ResultA.count
  #write-host "B = " $ResultB.count
  #write-host $ResultA[0]  $ResultA[1]
  #write-host $ResultB[0]  $ResultB[1]




  If ($ResultA.count -eq 2){  #-[  If results plus target > 2 then display multiples ]--
    $objLabel.Text = "Results for:   $ResultA[0]"    #--[ Overwrite the text with the username ]--
    $objLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
    $objLabel.Refresh()
  
    $objForm.Controls.remove($objTextBox)
  
    #--[ Add Result 1 Lable ]--
    If ($Debug1){Write-Host "Adding result label 1"}
    $objResult1 = New-Object System.Windows.Forms.Label
    $objResult1.Location = New-Object System.Drawing.Size(10,50)
    $objResult1.Size = New-Object System.Drawing.Size(315,20)
    $objResult1.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
    $objResult1.Text = "Currently logged onto:   " + $ResultA[1]
    $objForm.Controls.Add($objResult1)

    #--[ Add Result 2 Label ]--
    If ($ResultB[1] -eq $Null){$Assigned = "N/A"}Else{$Assigned = $ResultB[1]}
    If ($Debug1){Write-Host "Adding result label 2"}
    $objResult2 = New-Object System.Windows.Forms.Label
    $objResult2.Location = New-Object System.Drawing.Size(10,70)
    $objResult2.Size = New-Object System.Drawing.Size(315,20)
    $objResult2.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter  
    $objResult2.Text = "Assigned to:   $Assigned"
    $objForm.Controls.Add($objResult2)


  
  }ElseIf ($ResultA.count -gt 2){
    $objTextBox.Enabled = $false
    $objTextBox.Text = "       ... More Than One Found ...     "
    $objTextBox.Refresh()
   # $objForm.close()
    [System.Windows.Forms.MessageBox]::Show("$ResultA `n $ResultB","Multiple Logons Found")
    # $objTextBox.Text = "        ... More Than One Found ...     "
  }Else{
    $objTextBox.Text = "          ......  NOT FOUND  ......          "
    $objTextBox.Refresh()
  }
      If ($Debug1){Write-Host "Adding locate another"}
    #$objResult1.Text = "Currently logged onto: "
      $objProcessButton.Text = "Locate Another"
    $objProcessButton.tabindex = 1
    #$objTextBox.Text = " "
    $objTextBox.Enabled = $false
    $objTextBox.Refresh()
    $objProcessButton.Add_Click({$objTextBox.Text = "          ......  RELOADING  ......          ";$objTextBox.Refresh();ResetGUI})





 # $objCloseButton.Location = new-object System.Drawing.Size(($ButtonLeft+60),$ButtonTop)
 # $objForm.Controls.Remove($objProcessButton)
  
  $TargetUserSAM = $Null
  $TargetUserName = $Null
  $Result.Clear()
  $FullArray.Clear()
  $SubArray.Clear()
  $IterationResult.Clear()
  $TargetUser = $False
} #--[ End LocateUser Function ]--


function color-Write
{
    $allColors = ("-Black",   "-DarkBlue","-DarkGreen","-DarkCyan","-DarkRed","-DarkMagenta","-DarkYellow","-Gray",
                  "-Darkgray","-Blue",    "-Green",    "-Cyan",    "-Red",    "-Magenta",    "-Yellow",    "-White")
    $foreground = (Get-Host).UI.RawUI.ForegroundColor # current foreground
    $color = $foreground
    [bool]$nonewline = $false
    $sofar = ""
    $total = ""

    foreach($arg in $args)
    {
        if ($arg -eq "-nonewline") {
        $nonewline = $true
        }elseif ($arg -eq "-foreground"){
            if ($sofar) { Write-Host $sofar -foreground $color -nonewline }
            $color = $foregrnd
            $sofar = ""
        }elseif ($allColors -contains $arg){
            if ($sofar) { Write-Host $sofar -foreground $color -nonewline }
            $color = $arg.substring(1)
            $sofar = ""
        }else{
            $sofar += "$arg "
            $total += "$arg "
        }
    }
    Write-Host $sofar -foreground $color -nonewline:$nonewline
}

Function ResetGUI {
    $objForm.Close()
    $objForm.Dispose()
    CreateGUI
}

Function IsThereText{
  if ($objTextBox.Text.Length -ne 0){
    $objProcessButton.Enabled = $true
  }else{
    $objProcessButton.Enabled = $false
  }
}

Function Relocate {#--------------------[ Assure things run from C:\scripts ]----------------------
  If ($Relocate = False){Break}
  If (!($ScriptHomeDir -eq $TargetPath)){                                                #--[ Paths are NOT same ]--
    If (!(Test-Path -path $TargetPath)){New-Item $TargetPath -type directory}        #--[ Does target path exist?  If not, create it ]--
    If (!(Test-Path "$TargetPath\ScriptFullName" -pathType Leaf )){Copy-Item $ScriptFullPath $TargetPath\$ScriptFullName} #--[ Is the script there?  If not, copy it there ]--

    If (!(Test-Path -path "$Profile\Desktop\$ScriptShortName.lnk")){
    $WshShell = New-Object -comObject WScript.Shell                                    #--[ Place a shortcut on the desktop of current user
    $Shortcut = $WshShell.CreateShortcut("$Profile\Desktop\$ScriptShortName.lnk")
    $Shortcut.TargetPath = 'powershell.exe'
    $Shortcut.Arguments = "-WindowStyle Hidden –Noninteractive -NoLogo -Command `"$TargetPath\$ScriptFullName`""
    $Shortcut.Save()
    If ($Debug1){If (!(Test-Path -path "$Profile\Desktop\$ScriptShortName.lnk")){write-host "FAILED to create icon"}}
    }

    iex -command "$Profile\Desktop\$ScriptShortName.lnk"                                 #--[ re-invoke the script from the new location ]--
    #Remove-Item $MyINvocation.InvocationName                                          #--[ Delete the old copy of this script ]--
    EXIT                                                                              #--[ Force termination ]--
  }Else{                                                                                #--[ Paths ARE good, script exists, OK to execute ]--
    #if (!(Test-Path "$ScriptPath\ThisScriptName"))                                   #--[ an optional additional check ]--
  }
}

Function CreateGUI{
#--------------------------[ Create The GUI ]-----------------------------------
[void] [reflection.assembly]::loadwithpartialname("System.Windows.Forms")
[void] [reflection.assembly]::loadwithpartialname("System.Drawing")
[int]$Width = (Get-WmiObject -Class Win32_DesktopMonitor | Select-Object ScreenWidth,ScreenHeight).ScreenWidth
[int]$FormWidth = 350
[int]$FormHeight = 170
[int]$Center = ($Width / 2)
[int]$ButtonLeft = 55
[int]$ButtonTop = 99

#--[ Create Form ]--
If ($Debug1){Write-Host "Creating form"}
$objForm = new-object System.Windows.Forms.form
$objForm.Text = "User Locatinator.  v$CurrentVersion"
$objForm.size = new-object System.Drawing.Size($FormWidth,$FormHeight)
$objForm.minimumSize = New-Object System.Drawing.Size($FormWidth,$FormHeight)
$objForm.maximumSize = New-Object System.Drawing.Size($FormWidth,$FormHeight)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $true
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter"){$TargetUser=$objTextBox.Text;$objTextBox.Text = "           ......  Working  ......";$objTextBox.Refresh();LocateUser $TargetUser}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape"){$objForm.Close()}})

#--[ Add Form Lable ]--
If ($Debug1){Write-Host "Creating label 1"}
$objFormLabelBox = new-object System.Windows.Forms.Label
$objFormLabelBox.Font = new-object System.Drawing.Font("New Times Roman",9,[System.Drawing.FontStyle]::Bold)
$objFormLabelBox.Location = new-object System.Drawing.Size(3,5)
$objFormLabelBox.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$objFormLabelBox.size = new-object System.Drawing.Size(325,20)
$objFormLabelBox.Text = "This tool will locate the PC a user is logged on to:"
$objForm.Controls.Add($objFormLabelBox)

#--[ Add Form Lable ]--
If ($Debug1){Write-Host "Creating label 2"}
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Point(25,25)
$objLabel.Size = New-Object System.Drawing.Size(280,20)
$objLabel.Text = "Enter the First && Last Name, or Logon ID of the user:"
$objLabel.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
$objForm.Controls.Add($objLabel)

#--[ Add Text Input Box ]--
If ($Debug1){Write-Host "Creating input box"}
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(78,58)
$objTextBox.Size = New-Object System.Drawing.Size(175,20)
$objTextBox.TabIndex = 0
$objTextBox.add_TextChanged({IsThereText})
$objForm.Controls.Add($objTextBox)

#--[ Add LOCATE Button ]--
If ($Debug1){Write-Host "Adding LOCATE button"}
$objProcessButton = new-object System.Windows.Forms.Button
$objProcessButton.Location = new-object System.Drawing.Size($ButtonLeft,$ButtonTop)
$objProcessButton.Size = new-object System.Drawing.Size(100,25)
$objProcessButton.Enabled = $false
$objProcessButton.Text = "Locate The User"
$objProcessButton.tabindex = 1
$objForm.Controls.Add($objProcessButton)
$objProcessButton.Add_Click({$TargetUser=$objTextBox.Text;$objTextBox.Text = "             ......  Working  ......";$objTextBox.Refresh();LocateUser $TargetUser})

#--[ Add STOP Button ]--
If ($Debug1){Write-Host "Adding STOP button"}
$objCloseButton = new-object System.Windows.Forms.Button
$objCloseButton.Location = new-object System.Drawing.Size(($ButtonLeft+125),$ButtonTop)
$objCloseButton.Size = new-object System.Drawing.Size(100,25)
$objCloseButton.Text = "Cancel/Close"
$objCloseButton.Add_Click({$objForm.close()})
$objCloseButton.tabindex = 2
$objForm.Controls.Add($objCloseButton)

#--[ Open Form ]--
If ($Debug1){Write-Host "Opening form"}
$objForm.topmost = $true
$objForm.Add_Shown({$objForm.Activate();$objTextBox.Focus})
[void]$objForm.ShowDialog()
}

CreateGui