Skip to content

How To Deploy On-Premise Windows Scheduled Tasks Automatically using a Git Repository

Published: at 05:21 PM

If you work for a large enterprise that still runs automated workflows using Window’s Scheduled Tasks feature, then look no further. This guide will demonstrate how you can use a Github repository to manage not only the scripts triggered by a Scheduled Task but also the Scheduled Tasks themselves! It makes for a very clean implementation that will ease the administration of the tasks anytime you need to create new tasks, update existing ones, or even migrate the tasks to a new host!

Traditionally, these scheduled task servers are managed in the following way:

  1. Admin develops script using powershell/python on their local machine.
  2. Admin copies script to windows task server
  3. Admin configures a new scheduled task to run the script at a specified interval
  4. If changes are needed, admin typically has to log back into the server and make the changes to the local copy of the script or scheduled task

Using the approach detailed here, the scheduled task will be managed like this instead:

  1. Admin clones the win-schtask-cicd repository and creates a new feature branch
  2. Admin develops the script on their local machine
  3. Admin defines the scheduled task as an XML file on their local machine
  4. Admin creates a pull request to merge their feature branch with main
  5. Github Actions automatically deploys the new/updated scheduled task to the windows task server

Table of contents

Open Table of contents

Requirements

  1. A cloned version of JPSOAR/win-schtask-cicd that is pushed to your own remote private Github repository
  2. A Windows Server
    • Administrative access to the server
    • SSH Server installed on your Windows Server
    • (Optional) Python installed on your Windows Server
  3. A Linux based server where we’ll install the Github Self-Hosted Runner
    • Github supports the installation of the Self-Hosted Runner on Linux/Windows/MacOS but I’ve chosen to showcase Linux for the purposes of this guide
  4. If you have a firewall between your Windows Server and your Self-Hosted Runner, you’ll need ports open for both SSH and SMB

Setting up the Github Repository

Let’s get this started! To begin, we’ll create a new repository based off the JPSOAR/win-schtask-cicd template and then define some Github Actions Secrets so it works in your environment. We’ll also clone the new repository to your local machine so we can start developing additional scheduled tasks outside of the 2 examples ones provided for you in the template.

  1. Login to Github and then go to JPSOAR/win-schtask-cicd

  2. Select Use this template

  3. Give your repository a name and be sure set it to private. Then select create repository.

  4. On Github, in your new repository, Go to Settings -> Secrets and variables -> Actions

  5. Here you’ll need to configure 4 new repository secrets with values that will be unique to your environment. The secrets are referenced by the .github/workflows/deploy.yml file that is responsible for deploying the scheduled tasks to your server!

    If you’re not sure of what values to specify at this time, don’t worry! Just create the variables for now and continue on with the guide. You can come back and update the values as their mentioned throughout the process.

    Secret NameValue (Example values that I’ve used for my environment)Description
    WIN_GH_ACTIONS_PATHC:\\GithubActionsThe local path to the shared GithubActions folder on your windows server
    WIN_HOSTNAMEWinserver2022The hostname of your Windows server
    WIN_USERsa_gh_actionsThe local user account on the Windows server that will be used to for transferring files and configuring the Scheduled Tasks
    WIN_PW**The password for sa_gh_actions

win-schtask-12

Configuring the Windows Server

I’m assuming you’re reading this guide because you’ve already got a suitable Windows Server :) I’m running Windows Server 2022 myself but I think anything 2012 or newer would do just fine. Hop onto your server with an administrator account and let’s get started!

  1. Create a new service account (user) on your windows server. We’ll use this user account to transfer files from the self-hosted runner to a file share we create in the next step. win-schtask-1

  2. The service account will need to have the Log on as batch job right assigned to it in order to run scheduled tasks without being logged in. This can either be accomplished by adding the account to the local administrators group on the server (do so at your own risk) or by assigning the rights specifically to the account through group policy. I’ve opted to go the local group policy method (shown below). If your server is part of a domain, this group policy setting will have to be applied at the domain group policy level. win-schtask-8

  3. Create a new folder called GithubActions somewhere on your server. I put mine at: C:\GithubActions

  4. Enable Sharing on the folder by Right Clicking the Folder -> Properties -> Sharing -> Share. Give your new service account Read/Write access and then click Share win-schtask-2

  5. Install OpenSSH Server so we can access this server over SSH from the Github self-hosted runner

    1. Open the Add an optional feature menu in Windows System Settings
    2. Click Add a Feature -> Select OpenSSH Server win-schtask-4
    3. Once installed, Open Services and start both the OpenSSH SSH Server and OpenSSH Authentication Agent services and then in Properties change the Startup type to Automatic win-schtask-5
  6. (Optional) If you plan to run Python Scripts as a scheduled task, install Python on the server by going to https://www.python.org/downloads/ and running the executable.

    1. Be sure to check the boxes for installing as admin, adding python to path, and to install Python for all users.

Configuring Public Key Authentication - Windows Server

If you’ve configured your windows server up to this point, you will be able to SSH into the server using a username and password but the ideal scenario is to set up public key authentication.

I found this great resource on YouTube that walks you through configuring the settings required: https://www.youtube.com/watch?v=9dhQIa8fAXU

You should follow the instructions in the video to a tee (cutting corners does not work, I tried lol.) but here’s the gist of what’s going on:

As long as you follow along with the video you should be able to successfully ssh to your windows server from the Self-Hosted Github Runner using Public Key Authentication

Configuring the Self-Hosted Runner

There are serious security implications when using a self-hosted runner on Github. Untrusted workflows running on a self-hosted runner can result in malicious code execution, exposing access to an internal network, and much more. Please read up on this before continuing.

To stay safe, you should always ensure your version of the repository is Private. I’ve deliberately not configured a self-hosted runner for my public version of this repository for this exact reason. :)

I have a Linux VM dedicated to being a Github Self Hosted Runner in my home lab which is why I needed to enable an SMB file share on the windows server so I could transfer the files. You could technically just install the Self-Hosted runner on the Windows Server to avoid the hassle of transfering the files but that violates best practices

  1. In your cloned repository on github go to Settings -> Actions -> Runners -> New self-hosed runner

  2. Follow the instructions from github to install the self-hosted runner on the OS of your choosing.

  3. You can optionally run the self-hosted runner as a service so its always running. See the Github docs here: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service

  4. If you’re like me and your runner is installed on a linux system we’re going to use rsync to transfer the files. Install rsync and supporting tools using sudo apt-get install cifs-utils

  5. Excellent, now the schtask_deploy.yml file will run on your own self hosted runner! win-schtask-3

  6. Optionally test the ability to transfer files to your windows server:

    echo "Hello Internet!" > test.txt #create simple test file
    smbclient //windowservername/GithubActions -U sa_gh_actions%YOURPASSWORDHERE -c "put test.txt test.txt" #Transfer test.txt using smbclient
    putting file test.txt as \test.txt (1.6 kb/s) (average 1.6 kb/s) #Success!
    

Configuring Public Key Authentication - Github Runner

Our goal is to be able to SSH from the Self-Hosted Github Runner to the Windows Server using Public Key Authentication so that we’re not prompted for a username and password combination.

  1. Copy the private key you created earlier to your Github Runner to /Home/YOURUSERNAME/.ssh/YOURPRIVATEKEYFILEHERE

  2. In the .ssh folder, run the following commands to create an SSH Config file:

    touch config
    nano config
    
  3. Add the following content to your config file:

    Host YOURWINDOWSHOSTNAME
       HostName YOURWINDOWSFQDN
       User YOURWINDOWSUSERNAME
       IdentityFile ~/.ssh/YOURPRIVATEKEYFILENAME
    
  4. Save your changes to config

  5. Test your ability to SSH to your Windows Server:

    ssh sa_gh_actions@winserver2022
    
  6. Success! win-schtask-11

Adding your own Scheduled Tasks

Now that all the prerequisites are configured, we’ll cover how to start developing your own automation scripts in the repo so they will be automatically deployed to your server as scheduled tasks

Cloning the Repository

  1. Clone your newly created repository to your local machine using either Github or SSH and open the repo in an editor like VS Code

  2. For local python development a virtual environment must be created and activated

    Python -m venv venv
    #If developing on Linux/MacOS run the following to activate the venv environment
    source /venv/Scripts/activate
    #If developing on Windows run the following to activate the venv environment
    /venv/Scripts/activate.ps1
    #Install Python Requirements
    pip install -r /setup/requirements.txt
    
  3. Its best practice to add new content to the repository via a separate branch and then use a pull request to commit the changes to the main branch. Create a new branch by running

    git checkout -b YOURBRANCHNAME
    

Folder Structure

Scheduled Tasks are deployed to your Windows Server using the following Folder Structure:

.
└── Scripts/
    ├── ScheduledTaskName1/
   ├── AutomationScript1.py
   └── ScheduledTask1.xml
    └── ScheduledTaskName2/
        ├── AutomationScript2.ps1
        └── ScheduledTask2.xml

Each scheduled task has 3 components:

  1. A separate folder within Scripts/. The name given to the folder will become the name of the scheduled task once deployed to the server
  2. An automation script to be executed by the scheduled task (currently I have examples for both Python and Powershell).
  3. An XML file that defines the scheduled task itself.
    • The XML file is the result of exporting a scheduled task from the Windows Task Scheduler UI (but more on that below)

Scheduled Task XMLs

Generating a Scheduled Task XML

Windows Task Scheduler has an export feature that exports a task’s configuration as an XML file. I’ve found that using the Windows Task Schedule UI to create the first task and then export it is the simplest way to get started. From there, we can just copy and tweak the XML for every other task we want to define in our repository.

This section demonstrates how I created the schtask_catfacts.xml file to run the script in the repo at /Scripts/CatFacts/get-CatFacts.py. If you cloned the repo, you’ll obviously already have the finished product but I wanted to showcase how it works so you can apply it to your usecase.

  1. On your Windows Server, open Windows Task Schedule
  2. Create a new folder in the Task Scheduler Library called Githib
  3. Right click the new folder and select Create Task win-schtask-6
  4. Adjust the settings so the task runs as the sa_gh_actions action we set up earlier
  5. Toggle on the option to Run whether the user is logged on or not win-schtask-7
  6. In the trigger tab, adjust it to your liking. I’ve set mine to trigger every 8 hours starting at midnight.

The Actions tab is where all the magic happens. Here we’ll specify the task to run the get-CatFacts.py script using the venv python environment created for us by the github actions deploy.yml script. Again, we’re doing this manually through the UI for this first go, but once we can export the scheduled task as an XML, we won’t need to do this for future tasks.

  1. Select New and set the dropdown to start a program. Fill in the following information:

    Field NameValueDescription
    Program/ScriptC:\GithubActions\win-schtask-cicd\venv\Scripts\python.exeWe run python scripts from the command line by first specifying python.exe. In this case, we don’t want server’s default python.exe, we want the specific one that’s in our virtual environment
    Add argumentsC:\GithubActions\win-schtask-cicd\Scripts\CatFacts\get-CatFact.py -outpath C:\GithubData\CatFacts\CatFacts.jsonHere we’re specifiying the path to the python script we want to execute, in this case get-CatFact.py and then passing in where we want the output of the script to be saved via the command line variable

    win-schtask-9

    Get an error message about Log on as batch job required? Scroll back up to the top where I mentioned this in the create user step

  2. Click Ok to save your task. Next, let’s test it. Right click the task and select Run. If it worked, you should see the file CatFacts.json in the C:\GithubData\CatFacts folder win-schtask-10

  3. Phew!! Finally, we’ve created a working scheduled task using the script within the cloned repository… All of this just so we can export that task as an XML and include it in the /Scripts/CatFacts folder of the repository.

Yes, the repo on Github already includes this XML but I felt it was imported to showcase how I generated the XML so anyone could reproduce my steps.

Without further delay, In Windows Task Scheduler, Right click the task and select Export and save the file somewhere.

  1. Copy the exported XML file into the /Scripts/CatFacts folder of the local repository to complete this section.

Editing a Scheduled Task XML

It’s often easier to just edit an existing Scheduled Task XML as supposed to creating one in the UI and exporting it. The XML schema is simple enough so I’ll cover some basics but a full explanation can be found from Microsoft here.

win-schtask-13

The above screenshot highlights what attributes will need to be edited should you choose to copy my SchTask_CatFacts.xml to serve your own purposes.

Deploying Your Scheduled Tasks

Congratulations… You’ve made it to the end of this extremely long guide. First of all, thank you for reading. I had a lot of fun making it so I hope it was enjoyable for you as well :)

  1. Review .github/workflows/deploy.yml to get a sense of how the script and XML file are deployed as a Scheduled Task to your server.

    You’ll notice that the build script runs whenever changes are pushed to main

    name: Deploy to Windows Server
    on:
    push:
      branches: [main]
    workflow_dispatch:
    
    jobs:
    build:
      name: Build
      runs-on: self-hosted
      env:
        WIN_HOSTNAME: ${{ secrets.WIN_HOSTNAME }}
        WIN_USER: ${{ secrets.WIN_USER }}
        WIN_PW: ${{ secrets.WIN_PW }}
        WIN_GH_ACTIONS_PATH: ${{ secrets.WIN_GH_ACTIONS_PATH }}
    

    You’ll also notice that some variables are pulled in from your repository secrets. So, if you missed the step at the beginning to add those in your repository settings, now is a great time to go back and do that.

  2. Add, commit and push your new branch containing your changes to Github

    #Stage all files with changes
    git add .
    #Commit changes
    git commit -m "Some Descriptive Message Here"
    #Push your local branch to Github
    git push --set-upstream origin YOURBRANCHNAMEHERE
    

    You’ll only need to add the --set-upstream origin YOURBRANCHNAMEHERE when you push a branch for the first time. If you push the same branch multiple times, you can just use git push

  3. In Github you should see that your branch had recent changes. Select the big green button to Compare & pull request win-schtask-14

  4. On the next screen, add a nice description if you’d like, otherwise select Create pull request

  5. Some checks will run and if all looks good, you should have the option to Merge pull request. Select that and then push confirm win-schtask-15

  6. In the Actions tab you’ll notice a new workflow should have started. Click into it to view details. If everything worked successfully you should see lots of checkmarks! :) win-schtask-16

  7. Login to your server to verify that the scheduled tasks are there and configured correctly. win-schtask-17

  8. Rinse and repeat this step as many times as you need… Make some changes to your code, push the code, test it on the server, etc. This was a game changer for me and made the process of managing scheduled tasks so much easier. Happy coding!