• Waking a sleeping Mac Pro upon opening a folder

    Scenario

    I have two macs at home, a MacPro and a MacMini. The MacMini is attached to our TV. I put my MacPro to sleep when I leave for work in the morning. My wife comes home and tries to play videos for my son on the Mac Mini. The videos are actually on the Mac Pro, but it is transparent to her, when the machine is on. And that is the problem. When she clicks on the symbolic lynk and the MacPro is sleeping she can’t find the videos she is looking for.

    I needed a way to wake the MacPro when she is looking for the videos.

    This is longer post describing my whole Wake/Sleep setup. Requirements are MacPorts, and a Wake on LAN (WOL) utility. I use DDWRT, so there is one on my home router.

    I am a big fan of MacPorts. I used to use Fink, but I switched, and I don’t remember why. There are two utilities in MacPorts that are useful for sleeping macs, Sleepwatcher and wakeonlan. You could install Sleepwatcher via source, but I prefer a Package management system.

    Sleep

    Sleepwatcher is the most important part of this system. I used to put my Mac to sleep every night at 11 pm, but if I enabled “Wake for network access” in the energy saver preference, the machine would wake up every two hours. This article describes the problem and a solution – sleepwatcher.

    So I installed sleepwatcher via MacPorts. Then I added the following two lines to my /opt/local/etc/rc.sleep ( I could not get it working in my “$home/.sleep” file)

    /bin/sleep 1
    /usr/sbin/systemsetup -setwakeonnetworkaccess on >/dev/null
    

    Then I added the following to my /opt/local/etc/rc.wakeup (again I could not get my “$home/.wakeup” to work)

    /usr/sbin/systemsetup -setwakeonnetworkaccess off >/dev/null
    

    This allows the machine to go to sleep and not wake until it receives a WOL packet.

    That takes care of the sleep part.

    Wake

    Now my machines are sleeping (properly), and they can be woken from a WOL packet. Since I use DDWRT, I can go to the web interface and wake a machine (I have OpenVPN tunnels going all over the place, so i can access the web interface internally). It occurred to me that if there is a web interface, there has to be a WOL executable on the router. With public key authentication, I can connect to my DDWRT router with the following command and wake a machine:

    ssh homerouter "/usr/sbin/wol -i 192.168.X.255 xx:xx:xx:xx:xx:xx"
    

    That takes care of the wake part.

    Folder Actions

    To have a machine wake when I access a folder, I add the following applescript to a “Folder Actions”:

    on opening folder this_folder
    	try
    		tell application "Finder"
    			activate
    			try
    				set ping_result to (do shell script "ping -c 1 machine.trying.towake;echo -n")
    				if "100.0% packet loss" is in ping_result then
    					do shell script "ssh homerouter "/usr/sbin/wol -i 192.168.X.255 xx:xx:xx:xx:xx:xx" "
    				end if
    			end try
    		end tell
    	on error errmsg
    	end try
    end opening folder
    

    If the machine does not answer a ping, the script will ssh to the ddwrt router and launch the wol executable to wake the sleeping machine.

    A complex system, but it works.


  • A second addition to my PowerShell install script

    I added even more functionality in my PowerShell install script (original script, and first update). I wanted the ability to display an informational popup to let the user know what we were up to. The function below takes the text to display from the config xml file and displays it in a windows form, with an “OK” button.

    This script is starting to be fun!

    
    Function DisplayWindowsForm ($Step){
    	if ($Step.TextToDisplay.Value -ne "") {
        	$TextToDisplay = $Step.TextToDisplay.Value
    		[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 
    		[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") 
    		
    		$objForm = New-Object System.Windows.Forms.Form 
    		$objForm.Text = "Title bar"
    		$objForm.Size = New-Object System.Drawing.Size(600,500) 
    		$objForm.StartPosition = "CenterScreen"
    		
    		$OKButton = New-Object System.Windows.Forms.Button
    		$OKButton.Location = New-Object System.Drawing.Size(250,400)
    		$OKButton.Size = New-Object System.Drawing.Size(75,23)
    		$OKButton.Text = "OK"
    		$OKButton.Add_Click({$x=$objTextBox.Text;$objForm.Close()})
    		$objForm.Controls.Add($OKButton)
    		
    		$objLabel = New-Object System.Windows.Forms.Label
    		$objLabel.Location = New-Object System.Drawing.Size(10,20) 
    		$objLabel.Size = New-Object System.Drawing.Size(500,400) 
    		$objLabel.Text = $TextToDisplay
    		$objForm.Controls.Add($objLabel) 
    		
    		$objForm.Topmost = $True
    		
    		$objForm.Add_Shown({$objForm.Activate()})
    		[void] $objForm.ShowDialog()
    	}
    }
    
    

  • An update to my new PowerShell install script

    I needed new functionality in my PowerShell install script (previously mentioned here). I needed the ability to make sure a process is not running, and if it is running, I could prompt the user to close it.

    The new function takes the process name value from the config xml file (as I mentioned in previos post) and if it is running, displays a message box until the program is no longer running.

    
    Function WaitForProcessToStop ($Step){
    	$ProcessName= ($Step.ProcessName.Value).split(".")[0]
        if ($ProcessName -ne "") {
    		[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
    		While (get-process $ProcessName -ea SilentlyContinue | select -Property Responding)
    		{
    			[System.Windows.Forms.MessageBox]::Show(
    			"Please close $ProcessName", 
    			"Please close $ProcessName", 
    			[System.Windows.Forms.MessageBoxButtons]::OK, 
    			[System.Windows.Forms.MessageBoxIcon]::Information,
    			[System.Windows.Forms.MessageBoxDefaultButton]::Button1,   
    			[System.Windows.Forms.MessageBoxOptions]::ServiceNotification
    			)
    		}
    	}
    }
    
    

  • My new PowerShell install script

    I wanted to write a PowerShell script that can execute common activities involved in deploying software. We require signed PowerShell scripts, so it was not practical to rewrite the script for every piece of software. Instead, I moved the configuration to an XML file.

    The first function below takes an object (pulled from the XML config file) that contains the uninstall information. The scenario would be that you want to uninstall a piece of software before you install something else. First I try to remove the software via WMI. If that fails, I lookup the uninstall string and use msiexe.exe to try and uninstall the software bassed on the GUID of the software.

    function UninstallStep ($Step)
    {
        $CurrentDisplayName= $Step.CurrentDisplayName.Value
        $CurrentVersion= $Step.CurrentVersion.Value
          gwmi win32_product -filter "Name like '%$CurrentDisplayName%'" | foreach {
            $InstalledVersion = $_.Version
            if ($InstalledVersion -ne  $CurrentVersion) {
                write-host "Trying to uninstall $CurrentDisplayName $InstalledVersion via WMI"
    	        if ($_.uninstall().returnvalue -eq 0) { write-host "Successfully uninstalled $CurrentDisplayName $InstalledVersion via WMI" }
    	        else {
                    write-host "WMI uninstall retured an error, Trying to uninstall $CurrentDisplayName $InstalledVersion from registry entries"
    	            if (-not(Test-Path ("Uninstall:"))){New-PSDrive -name Uninstall -psprovider registry -root HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall | Out-Null}
    	            Get-ChildItem -Path Uninstall: | Where-Object -FilterScript { $_.GetValue("DisplayName") -like "*$CurrentDisplayName*"} | ForEach-Object -Process {
    	                $CommandToRun = "msiexec"
    	                $UNSTring = $_.GetValue("UninstallString").split("{")
    	                $Parameters = "/X /Q {" + $UNSTring[1]
    	                write-host "Running Command: " $CommandToRun $Parameters
    	                Start-Process $CommandToRun $Parameters -wait -workingdirectory $WorkingDirectory | out-null
    	            }
    	            }
            }
        }
    }
    

    Second piece of code is a generic command to run script. This is basically just a wrapper for the Start-Process command. It can be used to run any command, but mostly I use this to start the msiexec.exe program with parameters. I can also use this command to start an setup.exe. Again, this is read from the config.xml. If there are arguments, then the second part of the conditional runs.

    Function CommandToRunStep ($Step)
    {
      if ($Step.Command.Value -ne "") {
        $Command = $Step.Command.Value
        if ($Step.Arguments.Value -ne "") {
          $Arguments = $Step.Arguments.Value
          write-host "Running Command: " "$Command" "$Arguments"
          Start-Process "$Command" -ArgumentList "$Arguments" -wait -workingdirectory $WorkingDirectory | out-null
        }
        Else {
          write-host "Running Command: " "$Command"
          Start-Process -FilePath "$Command" -wait -workingdirectory $WorkingDirectory | out-null
        }
      }
    }
    

    The final function takes a process name from the config xml file and kills it.

    function KillStep ($Step)
    {
        $ProcessName= ($Step.ProcessName.Value).split(".")[0]
        if ($ProcessName -ne "") {
    
        write-host "Killing: " "$ProcessName"
        Stop-Process -force -processname "$ProcessName" -ea SilentlyContinue
        }
    }
    

    The main part of this script loops through the xml file and call the correct function. The xml file can contain any number of the three types of functions above, and they are run in sequential order. This gives me the ability to create a “task sequence” in an xml file that will be run with a PowerShell script in an SCCM advertised program.

    #  Main
    $WorkingDirectory = Split-Path -parent $MyInvocation.MyCommand.Definition
    [ xml ]$s = Get-Content $WorkingDirectory\Install.xml
    
    foreach ($Step in $s.Install.Steps.Step)
    {
    switch ($Step.StepType.Value)
        {
        "UninstallOlderThan" {UninstallStep ($Step)}
        "CommandToRun" {  CommandToRunStep ($Step)}
        "KillProcess" { KillStep ($Step) }
        }
    }
    

    Updated
    And an example of the XML file would be:

    <Install>
    <Steps>
      <Step>
    	<StepType Value="UninstallOlderThan" />
    	<CurrentDisplayName value="Apple Application Support" />
        	<CurrentVersion value="1.3.3" />
      </Step>
      <Step>
    	<StepType Value="CommandToRun" />
    	<Command Value="msiexec" />
    	<Arguments Value="/i AppleApplicationSupport.msi /quiet /norestart" />
      </Step>
      <Step>
    	<StepType Value="CommandToRun" />
    	<Command Value="iTunesSetup.exe" />
    	<Arguments Value="/quiet DESKTOP_SHORTCUTS=0" />
      </Step>
      <Step>
    	<StepType Value="KillProcess" />
    	<ProcessName Value="Process.exe" />
      </Step>
    </Steps
    </Install>
    

  • SCCM afterbackup.bat

    Our current SCCM backup is targeted to DFS share pointed to our backup server. We dump files to this share and the local files are backed up to tape (really to disk then replicated to tape.) I never sat down and figured out how to get multiple backups rotating in SCCM until I saw this post. It reminded me that I needed to setup an afterbackup.bat script. If you add a script named afterbackup.bat to c:\Program Files\Microsoft Configuration Manager\inboxes\smsbkup.box (or where ever you installed it), it will be executed after the backup is finished. I added the following code to afterbackup.bat

    setlocal enabledelayedexpansion
    set target=\\Network\DFS\BACKUP\SCCM\%date:~0,3%
    
    If not exist %target% goto datamove 
    RD %target% /s /q 
      
    :datamove 
    move "\\Network\DFS\Backup\SCCM\Current" "%target%"
    

    All this does is move the backup folder to a folder named the day of the week. If the destination already exists, then it is deleted first. Resulting in 7 days of backup.

    To test, I started the SMS_STIE_BACKUP service, and in the SCCM logs, I see :
    SMS Site Backup task is starting.

    SMS Site Backup is starting to copy the files from the snapshot.

    SMS Site Backup successfully initiated command file “afterbackup.bat”.

    Now I see a folder named “Tues” that contains last night’s backup.

    Scratch that off the todo list. Finally.


  • Finding diffs between clean and modified versions of WordPress

    We have an issue where our developers try to update the core WordPress files. I wanted to find a way to keep them honest. here is my script:

    
    # get WordPress
    cd ~/src/
    rm -f ~/src/latest.tar.gz
    rm -rf ~/src/wordpress/
    wget -q http://wordpress.org/latest.tar.gz
    tar -xzf latest.tar.gz
    CURRENTVERSION=$(grep "wp_version =" ~/src/wordpress/wp-includes/version.php | cut -f 3 -d " " | sed "s/;//" | sed "s/'//g")
    # find WordPress installs and compare
    for installpath in $(find $SEARCHPATH -name wp-config.php)
    	do
    		BASEDIR=$(dirname $installpath)
    		INSTALLEDVERSION=$(grep "wp_version =" $BASEDIR/wp-includes/version.php | cut -f 3 -d " " | sed "s/;//" | sed "s/'//g")
    		if [ $CURRENTVERSION == $INSTALLEDVERSION ]; then
    		echo "====Comparing $BASEDIR to Source====" 
    		diff -rq --exclude="wp-content" ~/src/wordpress  $BASEDIR #| grep differ
    		fi
    done
    

  • Pseudo workflow approval with out SharePoint Workflows.

    I wanted to have a form that a person would submit (for an example a request of some type) and then it would be approved by the appropriate people. I find the built in workflow functionality too limiting and too difficult for users, so this is what I came up with:

    I would have one list that contained fields for both the user to enter data and for each approver to approve or disapprove. In addition, there would be a “Final Approval” field that cold only be set once everyone approved or disapproved and an “I agree” to terms checkbox.

    First up, I hid all the fileds on the NewForm.aspx that were related to the approval process. Just the data that was to be collected was shown. I did this with jquery and a CEWP:

    <script type="text/javascript">
    $(document).ready(function(){
    $('tr:has(select[title="User 1"])').not('tr:has(tr)').hide();
    $('tr:has(select[title="User 2"])').not('tr:has(tr)').hide();
    $('tr:has(select[title="User 3"])').not('tr:has(tr)').hide();
    $('tr:has(select[title="User 4"])').not('tr:has(tr)').hide();
    $('tr:has(select[title="User 5"])').not('tr:has(tr)').hide();
    $('tr:has(select[title="Final Status"])').not('tr:has(tr)').hide();
    $('nobr:contains("User 1 Comments")').closest('tr').hide();
    $('nobr:contains("User 2 Comments")').closest('tr').hide();
    $('nobr:contains("User 3 Comments")').closest('tr').hide();
    $('nobr:contains("User 4 Comments")').closest('tr').hide();
    $('nobr:contains("User 5 Comments")').closest('tr').hide();
    });
    </script>
    

    Once this is submitted by the user, and email is sent by a SharePoint workflow (I still don’t like them) telling the approvers to look at the request and approve it (link to the edit page in the email)

    Then on the edit page, I used the jquery below to hide the “I agree” so the user could not go back and change it. Also I hid the final approval until all drop downs (Approvals) have changed from “Pending” to Approved or Declined. Obviously there would be security issues, but this is a small use case and we weren’t worried about malicious users (famous last words) – people were on their honor.

    <script type="text/javascript">
    $(document).ready(function(){
    $('tr:has(select[title=IAgree])').not('tr:has(tr)').hide();
    $('tr:has(select[title=IAgree])').not('tr:has(tr)').append('<tr><td nowrap="true" valign="top" width="190px" class="ms-formlabel"><H3 class="ms-standardheader">I Agree</H3></TD><td valign="top" class="ms-formbody" width="400px">'+$('select[title=IAgree] :selected').text()+'</td></tr>');
    
    $('select :selected').each(function(){
    if($(this).text() == "Pending") {
    $('tr:has(select[title=Final Status])').not('tr:has(tr)').hide();
    }
    });
    
    });
    </script
    

  • Powershell script to find and run advertisements that are set to run when NoUserLoggedOn

    I have been thinking about how to launch a SCCM package/program before logon. If the user logs on before SCCM executes (like right after a reboot), the NoUserLoggedOn requirement will not be met. GPO software deployment handles this by not allowing the user to log in until the software is installed.

    What happens if you combine both software deployment via GPO and SCCM?

    So my theory is that I could execute a script wrapped in a MSI and deployed via GPO software deployment. This script would then search for advertisements that are set to run “when no one is logged in” and execute them. Below is that script, not sure if the rest of my theory will work, but this is the first piece.

    $AdvNoLogOn = (gwmi -Namespace ROOT\CCM\Policy\Machine\ActualConfig -Class CCM_SoftwareDistribution | where {$_.ADV_MandatoryAssignments -eq $true} | where {$_.PRG_PRF_UserLogonRequirement -eq "NoUserLoggedOn"})
    $SMSCli = [wmiclass] "\root\ccm:SMS_Client"
    foreach ($Adv in $AdvNoLogOn)
    {
    $PKGID= $Adv.PKG_PackageID
    write-host $PKGID
    $SCHEDMESS=(gwmi -Namespace ROOT\CCM\Policy\Machine\ActualConfig -Class CCM_Scheduler_ScheduledMessage | where {$_.ActiveMessage -like "*$PKGID*"})
    write-host $SCHEDMESS.ScheduledMessageID
    $SMSCLI.TriggerSchedule($SCHEDMESS.ScheduledMessageID)
    }